Compare commits

...

356 Commits

Author SHA1 Message Date
takezoe
a791e937b6 Support for revert pull request 2025-08-13 08:06:05 +09:00
Scala Steward
f7027e57df Update HikariCP to 7.0.1 2025-08-09 12:01:30 +09:00
Scala Steward
6a4719469d Update tika-core to 3.2.2 2025-08-08 19:03:31 +09:00
Scala Steward
6ebc865ba5 Update sbt, sbt-dependency-tree, ... to 1.11.4 2025-08-05 07:04:59 +09:00
Naoki Takezoe
0c1e8b932b Reject direct push to branch if branch protection is enabled (#3791) 2025-08-03 11:58:28 +09:00
scala-steward-bot
fda67a32e2 Update scalafmt-core to 3.9.9 (#3815) 2025-08-03 01:31:06 +09:00
Scala Steward
14d7e9ee90 Update commons-net to 3.12.0 2025-08-02 06:56:11 +09:00
Naoki Takezoe
b7b7322cce Fix branch selector in repository viewer (#3813) 2025-08-01 02:33:05 +09:00
Scala Steward
5eb44398d0 Update oauth2-oidc-sdk to 11.27 2025-07-30 10:09:17 +09:00
Scala Steward
be7bb255c3 Update commons-compress to 1.28.0 2025-07-30 09:54:03 +09:00
Scala Steward
cb9522d416 Update github-api to 1.329 2025-07-30 06:56:36 +09:00
Scala Steward
d80afb473b Update scala3-library to 3.7.2 2025-07-30 06:49:49 +09:00
Scala Steward
607d85c661 Update HikariCP to 7.0.0 2025-07-29 05:59:20 +09:00
Scala Steward
a5fab3bc96 Update oauth2-oidc-sdk to 11.26.1 2025-07-25 18:55:51 +09:00
Scala Steward
eb403ada58 Update HikariCP to 6.3.2 2025-07-24 17:34:00 +09:00
Naoki Takezoe
911c102f39 Improve logging in initialization process (#3804) 2025-07-21 12:10:56 +09:00
Naoki Takezoe
bf23e854f8 Exclude sshd-spring-sftp (#3803) 2025-07-21 11:58:22 +09:00
Scala Steward
52427c0a1e Update HikariCP to 6.3.1 2025-07-21 06:15:54 +09:00
Naoki Takezoe
d8e5ac585c Disable blank issue (#3801) 2025-07-20 10:52:14 +09:00
Naoki Takezoe
2fbeef73b0 Move issue_template.yml to .github directory (#3800) 2025-07-20 10:42:25 +09:00
Naoki Takezoe
15e39572dd Update issue template (#3799) 2025-07-20 10:33:40 +09:00
Scala Steward
9eac4f42c5 Update commons-io to 2.20.0 2025-07-20 06:04:11 +09:00
scala-steward-bot
01d18bb5c3 Update typesafe:config to 1.4.4 (#3796) 2025-07-13 08:52:18 +09:00
scala-steward-bot
00258e9125 Update tika-core to 3.2.1 (#3795) 2025-07-13 08:52:02 +09:00
Scala Steward
046b337337 Update java-diff-utils to 4.16 2025-07-08 17:02:18 +09:00
Naoki Takezoe
46cc7b6fd3 Update H2 database migration guide (#3793) 2025-07-06 21:24:28 +09:00
Scala Steward
59344b4f05 Update sbt, sbt-dependency-tree, ... to 1.11.3 2025-07-06 07:11:59 +09:00
Naoki Takezoe
c4d8af02b2 Fix wrong redirect after sign-in when user-defined CSS is used (#3789) 2025-07-05 12:08:20 +09:00
Scala Steward
a10bc3687a Update sbt-twirl, twirl-api to 2.0.9 2025-07-01 06:36:38 +09:00
Scala Steward
b0d21dee42 Update sbt-scalafmt to 2.5.5 2025-06-30 17:35:49 +09:00
Naoki Takezoe
13ea0e7507 Update release procedure doc (#3785) 2025-06-29 13:57:16 +09:00
takezoe
d66fdaede5 Update README and CHANGELOG 2025-06-29 13:33:29 +09:00
takezoe
d6217d89eb Update README and CHANGELOG 2025-06-29 13:01:17 +09:00
Naoki Takezoe
c99ff1cf0f Release 4.43.0 (#3784) 2025-06-29 13:00:21 +09:00
Scala Steward
001b9ae2ae Update mysql, postgresql to 1.21.3 2025-06-29 07:49:01 +09:00
Scala Steward
f63493f1c0 Update scalafmt-core to 3.9.8 2025-06-29 07:48:45 +09:00
Naoki Takezoe
c9095722f8 Redirect from sign-in page to top page if already authenticated (#3781) 2025-06-22 21:23:15 +09:00
Naoki Takezoe
b9d2efa582 UI: Fix bottom margin of branch deletion box in pull request (#3780) 2025-06-21 17:23:23 +09:00
Naoki Takezoe
9c2e09020a Fix warnings in service test cases (#3779) 2025-06-21 17:01:54 +09:00
Naoki Takezoe
1ffcf8c1e9 Bump h2 to 2.3.232 (#3746) 2025-06-21 13:03:48 +09:00
Naoki Takezoe
0124091840 Fix the issue template (#3778) 2025-06-21 12:59:50 +09:00
Naoki Takezoe
2a28a7b35b Migrate templates for issue request (#3777) 2025-06-21 12:48:06 +09:00
Scala Steward
2a68ffc8dc Update mysql, postgresql to 1.21.2 2025-06-19 20:42:59 +09:00
Scala Steward
6b47c49cdd Update oauth2-oidc-sdk to 11.26 2025-06-18 05:15:40 +09:00
Scala Steward
b634967776 Update postgresql to 42.7.7 2025-06-11 19:52:43 +09:00
Scala Steward
172bed760d Update sbt, sbt-dependency-tree, ... to 1.11.2 2025-06-08 07:42:23 +09:00
Naoki Takezoe
1b7eb69083 Migrate from OSSRH to Central Portal (#3770) 2025-06-08 00:17:29 +09:00
scala-steward-bot
185c01db99 Update sbt, sbt-dependency-tree, ... to 1.11.1 (#3767) 2025-06-07 23:56:26 +09:00
Scala Steward
ab548d8c25 Update scala3-library to 3.7.1 2025-06-03 05:24:59 +09:00
kenji yoshida
983975620b Merge pull request #3766 from scala-steward-bot/update/scalafmt-core-3.9.7
Update scalafmt-core to 3.9.7
2025-06-01 08:27:00 +09:00
Scala Steward
b512b08256 Add 'Reformat with scalafmt 3.9.7' to .git-blame-ignore-revs 2025-05-30 14:45:26 +00:00
Scala Steward
a54fb4960f Reformat with scalafmt 3.9.7
Executed command: scalafmt --non-interactive
2025-05-30 14:45:26 +00:00
Scala Steward
93de53d717 Update scalafmt-core to 3.9.7 2025-05-30 14:45:19 +00:00
Scala Steward
f262c0a9eb Update mysql, postgresql to 1.21.1 2025-05-30 17:44:07 +09:00
Scala Steward
e5d15569df Update postgresql to 42.7.6 2025-05-28 20:41:26 +09:00
Scala Steward
369b08eae3 Update tika-core to 3.2.0 2025-05-27 06:49:24 +09:00
Scala Steward
8e6fcb022b Update sbt, sbt-dependency-tree, ... to 1.11.0 2025-05-24 15:52:45 +09:00
Scala Steward
456b1f6571 Update org.eclipse.jgit.archive, ... to 6.10.1.202505221210-r 2025-05-23 11:28:36 +09:00
Scala Steward
6e459ad225 Update oauth2-oidc-sdk to 11.25 2025-05-23 05:51:55 +09:00
Scala Steward
cece4c1c7d Update mockito-core to 5.18.0 2025-05-21 06:47:39 +09:00
Scala Steward
42e7a9fa9f Update ec4j-core to 1.1.1 2025-05-18 06:45:58 +09:00
Scala Steward
f9510aba8e Update scalafmt-core to 3.9.6 2025-05-15 11:47:04 +09:00
Scala Steward
8a7d719025 Update oauth2-oidc-sdk to 11.24 2025-05-07 06:53:07 +09:00
Scala Steward
1293a21450 Update scala3-library to 3.7.0 2025-05-06 06:34:29 +09:00
scala-steward-bot
65dd597ab7 Update scalatra-forms-javax, ... to 3.1.2 (#3752) 2025-05-03 18:42:17 +09:00
Scala Steward
e145b5151e Update scalafmt-core to 3.9.5 2025-04-30 06:38:32 +09:00
Scala Steward
b9684c277b Update mysql, postgresql to 1.21.0 2025-04-24 11:47:26 +09:00
Scala Steward
4accb77533 Update commons-io to 2.19.0 2025-04-12 14:10:18 +09:00
Scala Steward
9eef961025 Update mockito-core to 5.17.0 2025-04-05 07:10:28 +09:00
Scala Steward
546b40cdd1 Update HikariCP to 6.3.0 2025-03-25 06:24:54 +09:00
Scala Steward
274a08c14c Update logback-classic to 1.5.18 2025-03-20 06:35:18 +09:00
Scala Steward
eed4b51189 Update jetty-http, jetty-io, jetty-runner, ... to 10.0.25 2025-03-18 08:00:26 +09:00
Scala Steward
5c2f84367b Update sbt, sbt-dependency-tree, ... to 1.10.11 2025-03-17 19:21:55 +09:00
Scala Steward
d5b625e43f Update mockito-core to 5.16.1 2025-03-16 07:10:45 +09:00
scala-steward-bot
27f9e3dec9 Update scalafmt-core to 3.9.4 (#3737) 2025-03-15 12:53:33 +09:00
Scala Steward
00ef4db9a7 Update testcontainers-scala to 0.43.0 2025-03-12 19:32:26 +09:00
Scala Steward
bf4f814389 Update scalafmt-core to 3.9.3 2025-03-07 19:13:22 +09:00
Scala Steward
23e45afd7f Update scala3-library to 3.6.4 2025-03-07 19:12:26 +09:00
Scala Steward
bfb02eef62 Update mysql, postgresql to 1.20.6 2025-03-05 06:57:03 +09:00
Scala Steward
c129aae73a Update sbt, sbt-dependency-tree, ... to 1.10.10 2025-03-04 16:01:56 +09:00
Scala Steward
a955856cef Update sbt, sbt-dependency-tree, ... to 1.10.9 2025-03-04 07:19:27 +09:00
Scala Steward
a43a3fa55c Update sbt, sbt-dependency-tree, ... to 1.10.8 2025-03-03 20:03:42 +09:00
Scala Steward
a6254ab955 Update mockito-core to 5.16.0 2025-03-03 20:03:13 +09:00
scala-steward-bot
b505c3dc12 Update scalafmt-core to 3.9.2 (#3726) 2025-03-01 15:35:33 +09:00
mnival
9d69b9e980 Add bearer authentification (#3725)
Co-authored-by: mnival <extern.nival_michael@allianz.com>
2025-02-28 09:30:45 +09:00
Scala Steward
44b2320644 Update scalafmt-core to 3.9.1 2025-02-26 19:12:54 +09:00
Scala Steward
8fb9643ea5 Update oauth2-oidc-sdk to 11.23.1 2025-02-26 19:12:31 +09:00
Scala Steward
10ea988298 Update logback-classic to 1.5.17 2025-02-26 04:00:17 +09:00
Scala Steward
7896945519 Update apache-sshd to 2.15.0 2025-02-25 03:36:14 +09:00
Scala Steward
210342d2bc Update sbt-twirl, twirl-api to 2.0.8 2025-02-24 06:45:06 +09:00
Scala Steward
fdacea858b Update sbt-scoverage to 2.3.1 2025-02-21 06:19:43 +09:00
Scala Steward
6825028d37 Update mysql, postgresql to 1.20.5 2025-02-20 07:54:32 +09:00
Scala Steward
2089882d41 Update oauth2-oidc-sdk to 11.23 2025-02-18 06:52:15 +09:00
Scala Steward
8e1d938155 Update github-api to 1.327 2025-02-13 23:30:25 +09:00
Scala Steward
39eb4cef04 Update oauth2-oidc-sdk to 11.22.2 2025-02-13 13:33:44 +09:00
Scala Steward
d4e3adafa6 Update oauth2-oidc-sdk to 11.22.1 2025-02-09 17:45:03 +09:00
Scala Steward
a7b8326499 Update oauth2-oidc-sdk to 11.22 2025-02-07 04:19:11 +09:00
Naoki Takezoe
249f8738d3 Update command to run GitBucket in debug.md (#3708) 2025-02-05 22:52:49 +09:00
Scala Steward
fefe6ef74f Update scalafmt-core to 3.8.6 2025-02-01 17:22:55 +09:00
Scala Steward
9ca6cd1d90 Update tika-core to 3.1.0 2025-02-01 07:18:24 +09:00
Scala Steward
bff7b7c460 Update oauth2-oidc-sdk to 11.21.3 2025-02-01 07:18:01 +09:00
Scala Steward
cfff79758b Update oauth2-oidc-sdk to 11.21.2 2025-01-22 03:38:05 +09:00
Scala Steward
ded8ceb2c6 Update sbt-assembly to 2.3.1 2025-01-21 08:50:51 +09:00
Scala Steward
c502ebfc16 Update testcontainers-scala to 0.41.8 2025-01-21 08:13:08 +09:00
Naoki Takezoe
d3268265cf Release GitBucket 4.42.1 (#3699) 2025-01-20 10:16:31 +09:00
Naoki Takezoe
5eaf59eebb Remove unnecessary setup code for SSL in LDAPUtil (#3697) 2025-01-20 09:41:33 +09:00
Scala Steward
d6d47aa977 Update scalafmt-core to 3.8.5 2025-01-20 07:19:49 +09:00
Naoki Takezoe
dc052e05ce Setup sbt explicitly in GitHub Actions (#3698) 2025-01-20 02:29:51 +09:00
Scala Steward
afb145ca7c Update sbt-scalafmt to 2.5.4 2025-01-17 07:26:49 +09:00
Scala Steward
a8beed33e0 Update scala3-library to 3.6.3 2025-01-16 20:00:10 +09:00
Scala Steward
488599dba5 Update postgresql to 42.7.5 2025-01-15 05:45:47 +09:00
Scala Steward
ef0d08a917 Update sbt-scoverage to 2.3.0 2025-01-14 07:42:48 +09:00
Scala Steward
9fa0d03c99 Update sbt-scalafmt to 2.5.3 2025-01-14 07:36:37 +09:00
kenji yoshida
15b6c5952b update build status badge in README 2025-01-11 16:06:43 +09:00
Scala Steward
cb75e9d312 Update oauth2-oidc-sdk to 11.21 2025-01-11 07:04:42 +09:00
Scala Steward
d542ee3528 Update scala-library to 2.13.16 2025-01-11 06:55:05 +09:00
Scala Steward
608a59cbfd Update oauth2-oidc-sdk to 11.20.2 2025-01-10 07:15:27 +09:00
Scala Steward
768e3a5c91 Update logback-classic to 1.5.16 2025-01-06 10:02:28 +09:00
Naoki Takezoe
f1fc794c0c Fix warnings in JGitUtil (#3683) 2025-01-05 14:35:50 +09:00
kenji yoshida
4cf924bee0 remove unused imports 2025-01-03 12:05:40 +09:00
kenji yoshida
8164efc720 remove unused import 2025-01-03 10:25:25 +09:00
Scala Steward
580a627d9d Update mockito-core to 5.15.2 2025-01-03 07:59:55 +09:00
Naoki Takezoe
1701916209 Update README and CHANGELOG for Java 17 requirement (#3679) 2025-01-02 02:17:06 +09:00
takezoe
811b2bff70 Update README.md and CHANGELOG.md 2024-12-30 05:52:06 +09:00
takezoe
9de9dd8940 Update README.md 2024-12-30 04:59:26 +09:00
Naoki Takezoe
572f83327f Release GitBucket 4.42.0 (#3678) 2024-12-30 04:56:10 +09:00
Naoki Takezoe
2afb37823b Increase max branch name length 100 -> 255 (#3676) 2024-12-30 04:36:24 +09:00
Naoki Takezoe
edc9720a88 Fix warnings in controllers (#3674) 2024-12-25 03:02:45 +09:00
Scala Steward
defc0fa041 Update sbt, sbt-dependency-tree, ... to 1.10.7 2024-12-23 12:51:33 +09:00
Naoki Takezoe
c14b3c5576 Fix warnings in ControllerBase (#3673) 2024-12-22 13:23:31 +09:00
Naoki Takezoe
10fc04cbc9 Return 400 error for invalid query string (#3672) 2024-12-22 12:48:39 +09:00
Scala Steward
67563a8805 Update logback-classic to 1.5.15 2024-12-22 07:13:09 +09:00
Naoki Takezoe
743bdab79b Fix wrong redirection when non-existent branch name is given as default branch (#3669) 2024-12-21 22:13:27 +09:00
Naoki Takezoe
a5a2e4732d Stabilize AccountServiceSpec (#3670) 2024-12-21 18:50:34 +09:00
Scala Steward
3a0cd5df62 Update scalatra-forms-javax, ... to 3.1.1 2024-12-21 14:43:49 +09:00
Naoki Takezoe
ad9a0afc9b Show branch errors when creating pull request (#3667) 2024-12-21 11:54:47 +09:00
Scala Steward
d2fc7a0642 Update logback-classic to 1.5.14 2024-12-20 13:44:08 +09:00
Naoki Takezoe
b1d4a18c9b Fix ref and ssh_url in WebHook payloads (#3656) 2024-12-19 15:19:42 +09:00
Scala Steward
fdfd8ec9b2 Update testcontainers-scala to 0.41.5 2024-12-19 13:31:22 +09:00
Scala Steward
396156c4e3 Update logback-classic to 1.5.13 2024-12-19 06:09:02 +09:00
Naoki Takezoe
8168580d60 Restore the original response of list-repository-tags API (#3662) 2024-12-16 00:27:56 +09:00
Naoki Takezoe
2abdd233e7 Apply user-defined CSS after plugins' JavaScript runs (#3661) 2024-12-15 12:30:07 +09:00
Naoki Takezoe
ef95ce99d2 Suppress Liquibase logs (#3660) 2024-12-14 14:49:38 +09:00
Scala Steward
3a6b2f6f4e Update sbt-pgp to 2.3.1 2024-12-11 07:09:14 +09:00
ziggystar
92304ac8c6 fix: make some methods call JGitUtil.getCommitLog with ObjectId instead of strings (#3653)
Co-authored-by: Thomas Geier <thomas.geier@solidat.de>
2024-12-10 08:25:38 +09:00
Scala Steward
a2242a3cb7 Update scala3-library to 3.6.2 2024-12-10 06:10:34 +09:00
ziggystar
64e8167fcb fix: calculate diffs between commits based on common ancestor; works for force push (#3647)
fixes #3476

Co-authored-by: Thomas Geier <thomas.geier@solidat.de>
2024-12-05 08:51:03 +09:00
Naoki Takezoe
06b93293a6 Update build.md about testing on Jetty from sbt (#3650) 2024-12-01 13:10:20 +09:00
Naoki Takezoe
3dcc0aee3c Use ContainerPlugin instead of JettyPlugin to run newer version of Jetty (#3649) 2024-12-01 12:49:14 +09:00
Scala Steward
c009a39dd7 Update sbt, sbt-dependency-tree, ... to 1.10.6 2024-12-01 07:45:38 +09:00
Scala Steward
b27e8ebb7e Update java-diff-utils to 4.15 2024-11-25 13:39:54 +09:00
Scala Steward
a690d43491 Update mysql, postgresql to 1.20.4 2024-11-21 06:36:53 +09:00
Scala Steward
86bfb68a0c Update commons-io to 2.18.0 2024-11-20 08:13:46 +09:00
Scala Steward
9469801d3d Update HikariCP to 6.2.1 2024-11-19 06:59:42 +09:00
Scala Steward
757a8399c1 Update HikariCP to 6.2.0 2024-11-17 15:49:50 +09:00
Scala Steward
ed00e613c3 Update json4s-jackson to 4.1.0-M8 2024-11-07 16:30:48 +09:00
Scala Steward
6e341116c1 Update HikariCP to 6.1.0 2024-11-06 13:54:48 +09:00
Scala Steward
9ccc2d0f97 Update sbt, sbt-dependency-tree, ... to 1.10.5 2024-11-04 09:16:17 +09:00
Scala Steward
72742306a0 Update sbt, sbt-dependency-tree, ... to 1.10.4 2024-10-30 09:50:35 +09:00
Scala Steward
47c6d61661 Update logback-classic to 1.5.12 2024-10-27 07:13:39 +09:00
Scala Steward
cfc444bcd2 Update sbt-license-report to 1.7.0 2024-10-24 05:24:38 +09:00
Scala Steward
a1f65258ea Update mysql, postgresql to 1.20.3 2024-10-23 10:22:47 +09:00
Scala Steward
5e3b97cf85 Update sbt, sbt-dependency-tree, ... to 1.10.3 2024-10-20 13:31:53 +09:00
kenji yoshida
209e1967d6 fail-fast: false 2024-10-20 13:23:32 +09:00
Scala Steward
4437a4e7a6 Update tika-core to 3.0.0 2024-10-20 09:31:44 +09:00
Scala Steward
e4d92bb494 Update scala3-library to 3.6.1 2024-10-19 19:27:45 +09:00
Scala Steward
d942c8f48a Update scala3-library to 3.6.0 2024-10-19 04:02:52 +09:00
Scala Steward
3a798e152f Update sbt-scoverage to 2.2.2 2024-10-19 04:02:29 +09:00
Scala Steward
91b862970f Update scala3-library to 3.5.2 2024-10-17 04:15:47 +09:00
Scala Steward
2d199e22e7 Update logback-classic to 1.5.11 2024-10-16 08:39:26 +09:00
Scala Steward
757da408d1 Update mockito-core to 5.14.2 2024-10-16 08:01:30 +09:00
Scala Steward
309d6e8b57 Update ec4j-core to 1.1.0 2024-10-13 07:55:33 +09:00
Scala Steward
ec0395a7f3 Update logback-classic to 1.5.10 2024-10-13 06:59:03 +09:00
Scala Steward
7cb4958ca0 Update oauth2-oidc-sdk to 11.20.1 2024-10-11 08:23:57 +09:00
Scala Steward
2d1c917a2c Update logback-classic to 1.5.9 2024-10-09 06:29:26 +09:00
Scala Steward
27dd5120dd Update sbt-assembly to 2.3.0 2024-10-07 12:21:35 +09:00
Scala Steward
9e0f8992c5 Update sbt-pgp to 2.3.0 2024-10-05 21:41:34 +09:00
Scala Steward
5f87a28da6 Update apache-sshd to 2.14.0 2024-10-03 08:07:53 +09:00
Scala Steward
25d5d5eee5 Update mysql, postgresql to 1.20.2 2024-10-01 12:31:19 +09:00
Scala Steward
ec9472b7cc Update mockito-core to 5.14.1 2024-10-01 06:58:02 +09:00
Scala Steward
b967fb93a7 Update oauth2-oidc-sdk to 11.20 2024-09-30 06:14:20 +09:00
Scala Steward
9d1bf3035b Update mockito-core to 5.14.0 2024-09-28 06:34:32 +09:00
Scala Steward
d6cedc2171 Update sbt-scoverage to 2.2.1 2024-09-26 06:39:12 +09:00
Scala Steward
8d8c78966a Update HikariCP to 6.0.0 2024-09-24 06:05:20 +09:00
Scala Steward
cbe1c1c9f6 Update ec4j-core to 1.0.0 2024-09-21 07:48:45 +09:00
Scala Steward
bf946960a7 Update scala-library to 2.13.15 2024-09-21 06:53:23 +09:00
Scala Steward
9677735542 Update github-api to 1.326 2024-09-19 09:30:09 +09:00
Scala Steward
e06c8fe5da Update scala3-library to 3.5.1 2024-09-19 09:27:27 +09:00
Scala Steward
d4ee6c9063 Update commons-io to 2.17.0 2024-09-19 09:26:32 +09:00
Scala Steward
1d0f079bd2 Update sbt, sbt-dependency-tree to 1.10.2 2024-09-16 20:05:08 +09:00
Scala Steward
7cc6972d83 Update github-api to 1.325 2024-09-14 06:52:27 +09:00
kenji yoshida
177da92b39 fix warning 2024-09-11 11:47:54 +09:00
Scala Steward
e1f433573b Update json4s-jackson to 4.1.0-M7 2024-09-10 08:08:14 +09:00
Scala Steward
cd958d7681 Update logback-classic to 1.5.8 2024-09-07 07:12:32 +09:00
Scala Steward
9c8e77f472 Update oauth2-oidc-sdk to 11.19.1 2024-09-06 10:46:21 +09:00
Scala Steward
bf7d3a8f0b Update sbt-scoverage to 2.2.0 2024-09-06 10:46:09 +09:00
Scala Steward
934e17666d Update jetty-http, jetty-io, jetty-security, ... to 10.0.24 2024-09-04 08:20:28 +09:00
Scala Steward
b20c4b3d7d Update oauth2-oidc-sdk to 11.19 2024-08-29 17:39:44 +09:00
Scala Steward
7c4e8b54d0 Update mockito-core to 5.13.0 2024-08-28 08:15:20 +09:00
Scala Steward
1e3794674b Update postgresql to 42.7.4 2024-08-23 10:01:12 +09:00
Scala Steward
5d5706451d Update sbt-scoverage to 2.1.1 2024-08-22 06:28:14 +09:00
Scala Steward
76e8d01846 Update oauth2-oidc-sdk to 11.18 2024-08-21 09:14:20 +09:00
Scala Steward
75e2883f61 Update jetty-http, jetty-io, jetty-security, ... to 10.0.23 2024-08-21 09:14:01 +09:00
Scala Steward
7e7cc1d4c7 Update commons-compress to 1.27.1 2024-08-21 06:25:57 +09:00
Scala Steward
d7623e8b7a Update github-api to 1.324 2024-08-19 19:45:55 +09:00
Scala Steward
7dd1470cd4 Update oauth2-oidc-sdk to 11.17 2024-08-19 06:15:01 +09:00
Scala Steward
9fb8d6fc66 Update oauth2-oidc-sdk to 11.16.1 2024-08-17 06:15:36 +09:00
Scala Steward
4f1869817c Update oauth2-oidc-sdk to 11.16 2024-08-16 07:07:35 +09:00
Scala Steward
6be269c388 Update logback-classic to 1.5.7 2024-08-16 06:32:52 +09:00
Scala Steward
af857853cb Update oauth2-oidc-sdk to 11.15 2024-08-14 07:45:41 +09:00
Scala Steward
1a664c2b6a Update scala3-library to 3.5.0 2024-08-13 06:17:59 +09:00
scala-steward-bot
1feb33fc19 Update commons-compress to 1.27.0 (#3574)
* Update commons-compress to 1.27.0

* Update build.sbt

---------

Co-authored-by: kenji yoshida <6b656e6a69@gmail.com>
2024-08-11 11:32:52 +09:00
Scala Steward
4fc1d9c3b6 Update oauth2-oidc-sdk to 11.14 2024-08-02 05:26:05 +09:00
Scala Steward
7eb77908d4 Update apache-sshd to 2.13.2 2024-08-01 05:58:36 +09:00
Scala Steward
54f9d01c42 Update mysql, postgresql to 1.20.1 2024-08-01 05:57:46 +09:00
Scala Steward
08154af7b1 Update scalafmt-core to 3.8.3 2024-07-27 07:57:11 +09:00
Scala Steward
e7ab376a34 Update mysql, postgresql to 1.20.0 2024-07-18 05:58:08 +09:00
Scala Steward
a680398c54 Update sbt, sbt-dependency-tree to 1.10.1 2024-07-08 08:49:28 +09:00
Scala Steward
6acf1e68ca Update jetty-http, jetty-io, jetty-security, ... to 10.0.22 2024-07-05 16:12:38 +09:00
Scala Steward
fc35e7b83d Update sbt-scoverage to 2.1.0 2024-07-04 02:31:10 +09:00
Scala Steward
1ae6fc7e9e Update github-api to 1.323 2024-07-02 06:12:05 +09:00
scala-steward-bot
884f63e6f4 Update scalatra-forms-javax, ... to 3.1.0 (#3545)
Co-authored-by: kenji yoshida <6b656e6a69@gmail.com>
2024-06-28 09:19:57 +09:00
Scala Steward
4d3e34af8b Update sbt-twirl, twirl-api to 2.0.7 2024-06-28 09:01:31 +09:00
Scala Steward
54426bb456 Update oauth2-oidc-sdk to 11.13 2024-06-26 13:23:20 +09:00
Scala Steward
c86b72bf6b Update oauth2-oidc-sdk to 11.12.1 2024-06-25 19:18:52 +09:00
Scala Steward
e6a5911a39 Update apache-sshd to 2.13.1 2024-06-25 07:08:53 +09:00
Scala Steward
6ea77fa73e Update github-api to 1.322 2024-06-21 19:09:21 +09:00
Scala Steward
32d0b94a7a Update apache-sshd to 2.13.0 2024-06-20 03:37:43 +09:00
scala-steward-bot
4f770adb35 Update sbt-twirl, twirl-api to 2.0.6 (#3553) 2024-06-17 21:32:10 +09:00
kenji yoshida
361f92a08d -Xsource:3-cross 2024-06-17 15:16:42 +09:00
xuwei-k
c2068f58e7 prepare Scala 3 2024-06-17 15:07:53 +09:00
kenji yoshida
67dd604469 Merge pull request #3549 from scala-steward-bot/update/scalafmt-core-3.8.2
Update scalafmt-core to 3.8.2
2024-06-17 11:23:22 +09:00
Scala Steward
d1a2c23cf3 Update json4s-jackson to 4.1.0-M6 2024-06-17 01:26:00 +09:00
Scala Steward
262500508c Add 'Reformat with scalafmt 3.8.2' to .git-blame-ignore-revs 2024-06-14 16:40:55 +00:00
Scala Steward
f1360f44c6 Reformat with scalafmt 3.8.2
Executed command: scalafmt --non-interactive
2024-06-14 16:40:55 +00:00
Scala Steward
1c818f890a Update scalafmt-core to 3.8.2 2024-06-14 16:40:45 +00:00
Scala Steward
16f146b660 Update testcontainers-scala to 0.41.4 2024-06-14 06:12:35 +09:00
Scala Steward
6166eaf0c7 Update org.eclipse.jgit.archive, ... to 6.10.0.202406032230-r 2024-06-13 09:43:42 +09:00
Scala Steward
ad4312cedb Update commons-net to 3.11.1 2024-06-11 05:46:46 +09:00
Scala Steward
15083a5aac Update commons-net to 3.11.0 2024-06-01 07:26:44 +09:00
Scala Steward
9ae420c553 Update commons-compress to 1.26.2 2024-05-25 05:08:23 +09:00
takezoe
4115ea4ab6 Update ChangeLog of 4.41.0 2024-05-18 17:26:23 +09:00
takezoe
024ac313ae Update ChangeLog of 4.41.0 2024-05-18 17:12:06 +09:00
takezoe
598f289311 Update README 2024-05-18 16:16:02 +09:00
Naoki Takezoe
447288db9d Release GitBucket 4.41.0 (#3540) 2024-05-18 16:14:52 +09:00
Naoki Takezoe
2b8c2a5c06 Upgrade json4s to 4.1.0-M5 (#3537) 2024-05-18 11:54:13 +09:00
scala-steward-bot
6fd3a73268 Update mariadb-java-client to 2.7.12 (#3491) 2024-05-18 11:53:10 +09:00
scala-steward-bot
4536e42949 Update sbt-twirl, twirl-api to 2.0.5 (#3525) 2024-05-18 11:52:46 +09:00
scala-steward-bot
fc575388f1 Update solidbase to 1.1.0 (#3538) 2024-05-18 11:48:23 +09:00
Scala Steward
1044d1cc32 Update oauth2-oidc-sdk to 11.12 2024-05-17 21:02:48 +09:00
Scala Steward
253eabf2f2 Update oauth2-oidc-sdk to 11.11 2024-05-17 03:57:20 +09:00
Scala Steward
8219d57b92 Update jetty-http, jetty-io, jetty-security, ... to 10.0.21 2024-05-17 03:53:28 +09:00
Brian Wignall
89381d3314 Fix typo (#3533) 2024-05-15 23:42:48 +09:00
Scala Steward
2e1037e107 Update scala3-library to 3.4.2 2024-05-14 07:00:27 +09:00
Scala Steward
0a77c71278 Update mockito-core to 5.12.0 2024-05-12 07:32:14 +09:00
Scala Steward
275101d11b Update mysql, postgresql to 1.19.8 2024-05-09 11:05:59 +09:00
Scala Steward
bfd7f3b2bc Update sbt, sbt-dependency-tree to 1.10.0 2024-05-06 13:36:40 +09:00
Scala Steward
21fcedf000 Update sbt-scoverage to 2.0.12 2024-05-03 05:13:46 +09:00
Scala Steward
d963070f73 Update scala-library to 2.13.14 2024-04-30 08:05:56 +09:00
Scala Steward
8174c89f9e Update sbt-twirl, twirl-api to 1.6.6 2024-04-26 09:03:01 +09:00
Scala Steward
5bdc74f0a6 Update logback-classic to 1.5.6 2024-04-18 06:50:24 +09:00
Scala Steward
b259386b67 Update logback-classic to 1.5.4 2024-04-10 06:28:37 +09:00
Scala Steward
984b52e7ec Update commons-io to 2.16.1 2024-04-09 08:25:08 +09:00
Scala Steward
d36ef805f6 Update tika-core to 2.9.2 2024-04-03 03:54:23 +09:00
Scala Steward
02bdbd281b Update scalafmt-core to 3.8.1 2024-03-30 07:45:17 +09:00
Scala Steward
b3cca9ea91 Update commons-io to 2.16.0 2024-03-29 09:12:29 +09:00
Scala Steward
7409c08122 Update scala3-library to 3.4.1 2024-03-28 03:43:57 +09:00
Scala Steward
339b54cd4b Update github-api to 1.321 2024-03-21 12:37:31 +09:00
Scala Steward
1bbdf4917b Update github-api to 1.320 2024-03-19 07:43:04 +09:00
Scala Steward
465ed988a9 Update postgresql to 42.7.3 2024-03-15 06:35:07 +09:00
Scala Steward
d5d7fe47d5 Update sbt-assembly to 2.2.0 2024-03-14 15:51:43 +09:00
Scala Steward
81d14dfaf2 Update commons-compress to 1.26.1 2024-03-09 12:29:36 +09:00
Scala Steward
302da30746 Update org.eclipse.jgit.archive, ... to 6.9.0.202403050737-r 2024-03-09 02:29:35 +09:00
Scala Steward
e9110aac80 Update logback-classic to 1.5.3 2024-03-06 06:10:57 +09:00
Scala Steward
6d104bd585 Update mysql, postgresql to 1.19.7 2024-03-06 06:06:10 +09:00
Scala Steward
7c47462de8 Update logback-classic to 1.5.2 2024-03-03 08:22:05 +09:00
Scala Steward
cc8b83cbc5 Update mockito-core to 5.11.0 2024-03-01 20:12:40 +09:00
Scala Steward
07decd00a0 Update logback-classic to 1.5.1 2024-02-29 09:53:45 +09:00
Scala Steward
7c3e82f4dd Update sbt-twirl, twirl-api to 1.6.5 2024-02-28 00:39:35 +09:00
Scala Steward
95c31dd7b7 Update oauth2-oidc-sdk to 11.10.1 2024-02-27 03:36:40 +09:00
Scala Steward
5ea905e3a2 Update sbt-scoverage to 2.0.11 2024-02-24 19:42:15 +09:00
Scala Steward
e40d1f42c7 Update sbt, sbt-dependency-tree to 1.9.9 2024-02-23 15:42:31 +09:00
Scala Steward
aa00351606 Update scala-library to 2.13.13 2024-02-23 08:09:31 +09:00
Scala Steward
6311a1f146 Update mysql, postgresql to 1.19.6 2024-02-22 18:59:34 +09:00
Scala Steward
1517e1f580 Update github-api to 1.319 2024-02-21 22:16:54 +09:00
Scala Steward
833f3584c2 Update postgresql to 42.7.2 2024-02-21 07:05:16 +09:00
Scala Steward
cdc67bffcb Update commons-compress to 1.26.0 2024-02-19 12:01:40 +09:00
Scala Steward
52794aa9ea Update testcontainers-scala to 0.41.3 2024-02-19 01:59:15 +09:00
Scala Steward
9ea3670a56 Update oauth2-oidc-sdk to 11.10 2024-02-18 20:51:42 +09:00
Scala Steward
652ec3e029 Update logback-classic to 1.5.0 2024-02-17 12:19:47 +09:00
Scala Steward
b543bae958 Update apache-sshd to 2.12.1 2024-02-17 07:34:44 +09:00
Scala Steward
4f574cdbcd Update scala3-library to 3.4.0 2024-02-15 06:54:28 +09:00
Scala Steward
29fc2a666b Update sbt-scoverage to 2.0.10 2024-02-15 02:03:44 +09:00
Scala Steward
4498c016a4 Update scala3-library to 3.3.2 2024-02-15 01:51:14 +09:00
Scala Steward
31a1d46175 Update mysql, postgresql to 1.19.5 2024-02-09 04:43:08 +09:00
Scala Steward
e3904ebdfe Update jetty-http, jetty-io, jetty-security, ... to 10.0.20 2024-02-01 03:11:08 +09:00
Scala Steward
6e5ffdf658 Update testcontainers-scala to 0.41.2 2024-01-29 13:46:01 +09:00
Scala Steward
5d0531f00d Update markedj to 1.0.20 2024-01-27 14:06:49 +09:00
Scala Steward
01ea533b97 Update mysql, postgresql to 1.19.4 2024-01-26 04:19:39 +09:00
Scala Steward
86fc492668 Update mockito-core to 5.10.0 2024-01-25 06:51:12 +09:00
dependabot[bot]
3a78356019 Bump actions/cache from 3 to 4
Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-22 12:34:32 +09:00
Scala Steward
44f7931ae6 Update apache-sshd to 2.12.0 2024-01-19 13:05:46 +09:00
Scala Steward
7094f99a85 Update mockito-core to 5.9.0 2024-01-15 08:08:49 +09:00
Scala Steward
550c4a6e60 Update java-semver to 0.10.2 2024-01-15 08:08:03 +09:00
Naoki Takezoe
64aeb422c8 Adjust the default branch automatically when cloning external repo (#3466) 2024-01-14 16:17:29 +09:00
Scala Steward
c2265dc44c Update java-semver to 0.10.1 2024-01-13 07:43:41 +09:00
Scala Steward
507085cd56 Update java-semver to 0.10.0 2024-01-11 02:38:03 +09:00
Scala Steward
36ef7ddf94 Update oauth2-oidc-sdk to 11.9.1 2024-01-07 08:10:55 +09:00
Scala Steward
be1fbc8594 Update markedj to 1.0.19 2024-01-07 02:14:14 +09:00
xuwei-k
d73eacfa5d Update scalafmt setting
use new syntax. prepare Scala 3.4
2023-12-30 08:10:39 +09:00
Scala Steward
5d495cfa12 Update oauth2-oidc-sdk to 11.9 2023-12-28 07:32:54 +09:00
dependabot[bot]
acaadff6cb Bump actions/upload-artifact from 3 to 4
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-24 15:54:17 +09:00
kenji yoshida
e22371e4a5 Update CODEOWNERS 2023-12-24 15:48:07 +09:00
Scala Steward
81c426b112 Update commons-email to 1.6.0 2023-12-23 08:45:55 +09:00
Scala Steward
1481872ddb Update jetty-http, jetty-io, jetty-security, ... to 10.0.19 2023-12-20 07:44:56 +09:00
Scala Steward
523da8afb9 Update sbt, sbt-dependency-tree to 1.9.8 2023-12-17 11:10:19 +09:00
Scala Steward
828dcb7173 Update oauth2-oidc-sdk to 11.8 2023-12-17 08:14:44 +09:00
Scala Steward
eaf7bfb78d Update oauth2-oidc-sdk to 11.7.1 2023-12-08 19:49:02 +09:00
scala-steward-bot
cbcf5ce4b6 Update HikariCP to 5.1.0 (#3413) 2023-12-08 08:01:24 +09:00
Scala Steward
36dce286b5 Update sbt-twirl, twirl-api to 1.6.4 2023-12-08 08:00:56 +09:00
Scala Steward
8ffe75786e Update postgresql to 42.7.1 2023-12-07 08:16:30 +09:00
Scala Steward
5d63bc71aa Update org.eclipse.jgit.archive, ... to 6.8.0.202311291450-r 2023-12-06 10:45:17 +09:00
dependabot[bot]
37f8f7edda Bump actions/setup-java from 3 to 4
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 3 to 4.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-06 10:43:18 +09:00
scala-steward-bot
fd1c85afb2 Update logback-classic to 1.4.14 (#3440) 2023-12-03 21:40:52 +09:00
scala-steward-bot
c08bb6e89f Update mockito-core to 5.8.0 (#3441) 2023-12-03 21:40:35 +09:00
Scala Steward
d25889f6d2 Update json4s-jackson to 4.0.7 2023-12-03 20:03:35 +09:00
ziggystar
dabddf60b1 fix: add GitUtils.findBranchesNoMergeInfo and use it when merge info … (#3436)
* fix: add GitUtils.findBranchesNoMergeInfo and use it when merge info not necessary

* rfr: add some missing override annotations and a return type

---------

Co-authored-by: Thomas Geier <thomas.geier@solidat.de>
2023-11-30 21:01:57 +09:00
Scala Steward
c7ade7ec4d Update commons-io to 2.15.1 2023-11-30 07:43:52 +09:00
scala-steward-bot
9caa73a585 Update logback-classic to 1.4.13 (#3438) 2023-11-29 05:30:27 +09:00
Scala Steward
0ee849c73d Update logback-classic to 1.4.12 2023-11-28 15:51:46 +09:00
Scala Steward
7883d8fd1e Update oauth2-oidc-sdk to 11.7 2023-11-28 15:50:51 +09:00
Naoki Takezoe
783ddfffa3 Make max files and lines limit in showing diff configurable (#3433) 2023-11-26 21:02:40 +09:00
Naoki Takezoe
24b7cade49 Simplify pull request UI (#3417) 2023-11-25 23:48:48 +09:00
Scala Steward
51554d4325 Update github-api to 1.318 2023-11-24 01:53:46 +09:00
Scala Steward
90bcbd212b Update mysql, postgresql to 1.19.3 2023-11-22 03:06:56 +09:00
Naoki Takezoe
4ca3816522 Keyword search for issues and pull requests using search box (#3429) 2023-11-22 01:55:01 +09:00
Scala Steward
fc4819cb29 Update postgresql to 42.7.0 2023-11-21 08:02:24 +09:00
Scala Steward
5291df8e03 Update sbt-assembly to 2.1.5 2023-11-20 17:47:53 +09:00
Scala Steward
5afb0094f6 Update scalafmt-core to 3.7.17 2023-11-17 19:45:20 +09:00
kenji yoshida
e76813cbee remove unnecessary resolvers 2023-11-17 19:39:12 +09:00
kenji yoshida
bc6398ca3b add .git-blame-ignore-revs 2023-11-17 19:29:14 +09:00
xuwei-k
3d5ca44d66 update scalafmt 2023-11-17 19:26:04 +09:00
Scala Steward
8cd7068131 Update sbt-twirl, twirl-api to 1.6.3 2023-11-17 08:13:43 +09:00
xuwei-k
0db23b7f4a commons-compress 1.25.0 2023-11-16 12:07:37 +09:00
Scala Steward
66d9ce328d Update mysql, postgresql to 1.19.2 2023-11-16 04:36:20 +09:00
Naoki Takezoe
36de0b3097 Fix layout of branch selector (#3416) 2023-11-12 22:58:18 +09:00
Scala Steward
4f3cd26cf4 Update oauth2-oidc-sdk to 11.6 2023-11-06 07:35:18 +09:00
Scala Steward
561e1bfec7 Update mockito-core to 5.7.0 2023-11-03 07:25:05 +09:00
Scala Steward
de64206a82 Update jetty-http, jetty-io, jetty-security, ... to 10.0.18 2023-10-31 07:39:16 +09:00
Scala Steward
65ddffde89 Update sbt-assembly to 2.1.4 2023-10-27 17:44:42 +09:00
Scala Steward
1bc6ddc1a3 Update commons-io to 2.15.0 2023-10-26 07:04:36 +09:00
Scala Steward
a404bd0776 Update oauth2-oidc-sdk to 11.5 2023-10-24 07:42:57 +09:00
Scala Steward
ccbdd383ae Update sbt, sbt-dependency-tree to 1.9.7 2023-10-23 07:47:30 +09:00
Naoki Takezoe
acb846e885 Update README.md (#3401) 2023-10-23 02:57:41 +09:00
takezoe
fa23b4c89e Update ChangeLog of GitBucket 4.40.0 2023-10-23 00:09:29 +09:00
Scala Steward
a70e1f53b5 Update sbt-twirl, twirl-api to 1.6.2 2023-10-22 21:04:28 +09:00
156 changed files with 5372 additions and 4647 deletions

8
.git-blame-ignore-revs Normal file
View File

@@ -0,0 +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
View File

@@ -2,3 +2,4 @@
build.sbt @xuwei-k build.sbt @xuwei-k
project/* @xuwei-k project/* @xuwei-k
.github/workflows/* @xuwei-k

View File

@@ -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
View File

@@ -0,0 +1 @@
blank_issues_enabled: false

View 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

View File

@@ -7,12 +7,13 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 30 timeout-minutes: 30
strategy: strategy:
fail-fast: false
matrix: matrix:
java: [11, 21] java: [17, 21]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Cache - name: Cache
uses: actions/cache@v3 uses: actions/cache@v4
env: env:
cache-name: cache-sbt-libs cache-name: cache-sbt-libs
with: with:
@@ -22,10 +23,12 @@ jobs:
~/.cache/coursier/v1 ~/.cache/coursier/v1
key: build-${{ env.cache-name }}-${{ hashFiles('build.sbt') }} key: build-${{ env.cache-name }}-${{ hashFiles('build.sbt') }}
- name: Set up JDK - name: Set up JDK
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
java-version: ${{ matrix.java }} java-version: ${{ matrix.java }}
distribution: adopt distribution: adopt
- name: Setup sbt launcher
uses: sbt/setup-sbt@v1
- name: Run tests - name: Run tests
run: sbt scalafmtSbtCheck scalafmtCheckAll test run: sbt scalafmtSbtCheck scalafmtCheckAll test
- name: Scala 3 - name: Scala 3
@@ -33,7 +36,7 @@ jobs:
- name: Build executable - name: Build executable
run: sbt executable run: sbt executable
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: gitbucket-java${{ matrix.java }}-${{ github.sha }} name: gitbucket-java${{ matrix.java }}-${{ github.sha }}
path: ./target/executable/gitbucket.* path: ./target/executable/gitbucket.*

View File

@@ -1,12 +1,24 @@
version = "1.5.1" version = "3.9.9"
project.git = true project.git = true
maxColumn = 120 maxColumn = 120
docstrings = JavaDoc docstrings.style = keep
align.tokens = ["%", "%%", {code = "=>", owner = "Case"}] align.tokens = ["%", "%%", {code = "=>", owner = "Case"}]
align.openParenCallSite = false align.openParenCallSite = false
align.openParenDefnSite = false align.openParenDefnSite = false
continuationIndent.callSite = 2 continuationIndent.callSite = 2
continuationIndent.defnSite = 2 continuationIndent.defnSite = 2
danglingParentheses = true danglingParentheses.preset = true
runner.dialect = scala213source3
rewrite.trailingCommas.style = keep
fileOverride {
"glob:**/*.sbt" {
runner.dialect = scala212
rewrite.scala3.convertToNewSyntax = false
}
}
rewrite.scala3.convertToNewSyntax = true
runner.dialectOverride.allowSignificantIndentation = false
runner.dialectOverride.allowAsForImportRename = false
runner.dialectOverride.allowStarWildcardImport = false

View File

@@ -1,12 +1,56 @@
# Changelog # Changelog
All changes to the project will be documented in this file. All changes to the project will be documented in this file.
## 4.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
- Performance improvement for listing branches
- Upgrade internal libraries
## 4.40.0 - 22 Oct 2023 ## 4.40.0 - 22 Oct 2023
- Configurable default branch name
- Support custom fields of issues and pull requests in search condition
- Create pull request from default branch of forked repositories
- News feed shows activities of all visible repositories
- Drop Java 8 support - Drop Java 8 support
- Improve git push performance - Improve git push performance
- Show activities of all visible repositories as news feed
- Support custom fields of issues and pull requests in search condition
- Configurable default branch name
## 4.39.0 - 29 Apr 2023 ## 4.39.0 - 29 Apr 2023
- Support enum type in custom fields of Issues and Pull requests - Support enum type in custom fields of Issues and Pull requests

View File

@@ -1,4 +1,4 @@
GitBucket [![Gitter chat](https://badges.gitter.im/gitbucket/gitbucket.svg)](https://gitter.im/gitbucket/gitbucket) [![build](https://github.com/gitbucket/gitbucket/workflows/build/badge.svg?branch=master)](https://github.com/gitbucket/gitbucket/actions?query=workflow%3Abuild+branch%3Amaster) [![gitbucket Scala version support](https://index.scala-lang.org/gitbucket/gitbucket/gitbucket/latest-by-scala-version.svg)](https://index.scala-lang.org/gitbucket/gitbucket/gitbucket) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/gitbucket/gitbucket/blob/master/LICENSE) GitBucket [![Gitter chat](https://badges.gitter.im/gitbucket/gitbucket.svg)](https://gitter.im/gitbucket/gitbucket) [![build](https://github.com/gitbucket/gitbucket/actions/workflows/build.yml/badge.svg)](https://github.com/gitbucket/gitbucket/actions/workflows/build.yml) [![gitbucket Scala version support](https://index.scala-lang.org/gitbucket/gitbucket/gitbucket/latest-by-scala-version.svg)](https://index.scala-lang.org/gitbucket/gitbucket/gitbucket) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/gitbucket/gitbucket/blob/master/LICENSE)
========= =========
GitBucket is a Git web platform powered by Scala offering: 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 Installation
-------- --------
GitBucket requires **Java8**. 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`. 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**. 2. Go to `http://[hostname]:8080/` and log in with ID: **root** / Pass: **root**.
@@ -56,16 +56,33 @@ Support
-------- --------
- If you have any questions about GitBucket, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues whether there is a same question or request in the past. - If you have any questions about GitBucket, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues whether there is a same question or request in the past.
- If you can't find same question and report, send it to 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. - 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.40.x What's New in 4.43.x
------------- -------------
## 4.40.0 - 22 Oct 2023 ## 4.43.0 - 29 Jun 2025
- Drop Java 8 support - Upgrade H2 database from 1.x to 2.x
- Improve git push performance
- Show activities of all visible repositories as news feed
- Support custom fields of issues and pull requests in search condition
- Configurable default branch name
See the [change log](CHANGELOG.md) for all of the updates. 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}"
...
}
```
See the [change log](CHANGELOG.md) for all the past updates.

104
build.sbt
View File

@@ -1,76 +1,74 @@
import sbtlicensereport.license.{DepModuleInfo, LicenseInfo}
import com.jsuereth.sbtpgp.PgpKeys._ import com.jsuereth.sbtpgp.PgpKeys._
val Organization = "io.github.gitbucket" val Organization = "io.github.gitbucket"
val Name = "gitbucket" val Name = "gitbucket"
val GitBucketVersion = "4.40.0" val GitBucketVersion = "4.43.0"
val ScalatraVersion = "3.0.0" val ScalatraVersion = "3.1.2"
val JettyVersion = "10.0.17" val JettyVersion = "10.0.25"
val JgitVersion = "6.7.0.202309050840-r" val JgitVersion = "6.10.1.202505221210-r"
lazy val root = (project in file(".")) lazy val root = (project in file("."))
.enablePlugins(SbtTwirl, ScalatraPlugin) .enablePlugins(SbtTwirl, ContainerPlugin)
sourcesInBase := false sourcesInBase := false
organization := Organization organization := Organization
name := Name name := Name
version := GitBucketVersion version := GitBucketVersion
scalaVersion := "2.13.12" scalaVersion := "2.13.16"
crossScalaVersions += "3.3.1" crossScalaVersions += "3.7.2"
// scalafmtOnCompile := true // scalafmtOnCompile := true
coverageExcludedPackages := ".*\\.html\\..*" coverageExcludedPackages := ".*\\.html\\..*"
// dependency settings
resolvers ++= Seq(
Classpaths.typesafeReleases,
"sonatype-snapshot" at "https://oss.sonatype.org/content/repositories/snapshots/"
)
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % JgitVersion, "org.eclipse.jgit" % "org.eclipse.jgit.http.server" % JgitVersion,
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % JgitVersion, "org.eclipse.jgit" % "org.eclipse.jgit.archive" % JgitVersion,
"org.scalatra" %% "scalatra-javax" % ScalatraVersion, "org.scalatra" %% "scalatra-javax" % ScalatraVersion,
"org.scalatra" %% "scalatra-json-javax" % ScalatraVersion, "org.scalatra" %% "scalatra-json-javax" % ScalatraVersion,
"org.scalatra" %% "scalatra-forms-javax" % ScalatraVersion, "org.scalatra" %% "scalatra-forms-javax" % ScalatraVersion,
"org.json4s" %% "json4s-jackson" % "4.0.6", "org.json4s" %% "json4s-jackson" % "4.1.0-M8",
"commons-io" % "commons-io" % "2.14.0", "commons-io" % "commons-io" % "2.20.0",
"io.github.gitbucket" % "solidbase" % "1.0.5", "io.github.gitbucket" % "solidbase" % "1.1.0",
"io.github.gitbucket" % "markedj" % "1.0.18", "io.github.gitbucket" % "markedj" % "1.0.20",
"org.apache.commons" % "commons-compress" % "1.24.0", "org.tukaani" % "xz" % "1.10",
"org.apache.commons" % "commons-email" % "1.5", "org.apache.commons" % "commons-compress" % "1.28.0",
"commons-net" % "commons-net" % "3.10.0", "org.apache.commons" % "commons-email" % "1.6.0",
"commons-net" % "commons-net" % "3.12.0",
"org.apache.httpcomponents" % "httpclient" % "4.5.14", "org.apache.httpcomponents" % "httpclient" % "4.5.14",
"org.apache.sshd" % "apache-sshd" % "2.11.0" exclude ("org.slf4j", "slf4j-jdk14") exclude ("org.apache.sshd", "sshd-mina") exclude ("org.apache.sshd", "sshd-netty"), "org.apache.sshd" % "apache-sshd" % "2.15.0" exclude ("org.slf4j", "slf4j-jdk14") exclude (
"org.apache.tika" % "tika-core" % "2.9.1", "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.2",
"com.github.takezoe" %% "blocking-slick" % "0.0.14", "com.github.takezoe" %% "blocking-slick" % "0.0.14",
"com.novell.ldap" % "jldap" % "2009-10-07", "com.novell.ldap" % "jldap" % "2009-10-07",
"com.h2database" % "h2" % "1.4.199", "com.h2database" % "h2" % "2.3.232",
"org.mariadb.jdbc" % "mariadb-java-client" % "2.7.6", "org.mariadb.jdbc" % "mariadb-java-client" % "2.7.12",
"org.postgresql" % "postgresql" % "42.6.0", "org.postgresql" % "postgresql" % "42.7.7",
"ch.qos.logback" % "logback-classic" % "1.4.11", "ch.qos.logback" % "logback-classic" % "1.5.18",
"com.zaxxer" % "HikariCP" % "4.0.3" exclude ("org.slf4j", "slf4j-api"), "com.zaxxer" % "HikariCP" % "7.0.1" exclude ("org.slf4j", "slf4j-api"),
"com.typesafe" % "config" % "1.4.3", "com.typesafe" % "config" % "1.4.4",
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.1.0", "fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.1.0",
"io.github.java-diff-utils" % "java-diff-utils" % "4.12", "io.github.java-diff-utils" % "java-diff-utils" % "4.16",
"org.cache2k" % "cache2k-all" % "1.6.0.Final", "org.cache2k" % "cache2k-all" % "1.6.0.Final",
"net.coobird" % "thumbnailator" % "0.4.20", "net.coobird" % "thumbnailator" % "0.4.20",
"com.github.zafarkhaja" % "java-semver" % "0.9.0", "com.github.zafarkhaja" % "java-semver" % "0.10.2",
"com.nimbusds" % "oauth2-oidc-sdk" % "11.4", "com.nimbusds" % "oauth2-oidc-sdk" % "11.27",
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided", "org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided", "javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
"junit" % "junit" % "4.13.2" % "test", "junit" % "junit" % "4.13.2" % "test",
"org.scalatra" %% "scalatra-scalatest-javax" % ScalatraVersion % "test", "org.scalatra" %% "scalatra-scalatest-javax" % ScalatraVersion % "test",
"org.mockito" % "mockito-core" % "5.6.0" % "test", "org.mockito" % "mockito-core" % "5.18.0" % "test",
"com.dimafeng" %% "testcontainers-scala" % "0.41.0" % "test", "com.dimafeng" %% "testcontainers-scala" % "0.43.0" % "test",
"org.testcontainers" % "mysql" % "1.19.1" % "test", "org.testcontainers" % "mysql" % "1.21.3" % "test",
"org.testcontainers" % "postgresql" % "1.19.1" % "test", "org.testcontainers" % "postgresql" % "1.21.3" % "test",
"net.i2p.crypto" % "eddsa" % "0.3.0", "net.i2p.crypto" % "eddsa" % "0.3.0",
"is.tagomor.woothee" % "woothee-java" % "1.11.0", "is.tagomor.woothee" % "woothee-java" % "1.11.0",
"org.ec4j.core" % "ec4j-core" % "0.3.0", "org.ec4j.core" % "ec4j-core" % "1.1.1",
"org.kohsuke" % "github-api" % "1.317" % "test" "org.kohsuke" % "github-api" % "1.329" % "test"
) )
// Compiler settings // Compiler settings
@@ -85,13 +83,13 @@ scalacOptions := Seq(
scalacOptions ++= { scalacOptions ++= {
scalaBinaryVersion.value match { scalaBinaryVersion.value match {
case "2.13" => case "2.13" =>
Seq("-Xsource:3") Seq("-Xsource:3-cross")
case _ => case _ =>
Nil Nil
} }
} }
compile / javacOptions ++= Seq("-target", "11", "-source", "11") compile / javacOptions ++= Seq("-target", "11", "-source", "11")
Jetty / javaOptions += "-Dlogback.configurationFile=/logback-dev.xml" Container / javaOptions += "-Dlogback.configurationFile=/logback-dev.xml"
// Test settings // Test settings
//testOptions in Test += Tests.Argument("-l", "ExternalDBTest") //testOptions in Test += Tests.Argument("-l", "ExternalDBTest")
@@ -115,8 +113,8 @@ assembly / assemblyMergeStrategy := {
// Exclude a war file from published artifacts // Exclude a war file from published artifacts
signedArtifacts := { signedArtifacts := {
signedArtifacts.value.filterNot { signedArtifacts.value.filterNot { case (_, file) =>
case (_, file) => file.getName.endsWith(".war") || file.getName.endsWith(".war.asc") file.getName.endsWith(".war") || file.getName.endsWith(".war.asc")
} }
} }
@@ -158,8 +156,11 @@ executableKey := {
// include jetty classes // include jetty classes
val jettyJars = Keys.update.value select configurationFilter(name = ExecutableConfig.name) val jettyJars = Keys.update.value select configurationFilter(name = ExecutableConfig.name)
jettyJars foreach { jar => jettyJars foreach { jar =>
IO unzip (jar, temp, (name: String) => IO unzip (
(name startsWith "javax/") || (name startsWith "org/") || (name startsWith "META-INF/services/")) jar,
temp,
(name: String) => (name startsWith "javax/") || (name startsWith "org/") || (name startsWith "META-INF/services/")
)
} }
// include original war file // include original war file
@@ -205,8 +206,7 @@ executableKey := {
"md5" -> "MD5", "md5" -> "MD5",
"sha1" -> "SHA-1", "sha1" -> "SHA-1",
"sha256" -> "SHA-256" "sha256" -> "SHA-256"
).foreach { ).foreach { case (extension, algorithm) =>
case (extension, algorithm) =>
val checksumFile = workDir / (warName + "." + extension) val checksumFile = workDir / (warName + "." + extension)
Checksums generate (outputFile, checksumFile, algorithm) Checksums generate (outputFile, checksumFile, algorithm)
} }
@@ -216,9 +216,9 @@ executableKey := {
outputFile outputFile
} }
publishTo := { publishTo := {
val nexus = "https://oss.sonatype.org/" val centralSnapshots = "https://central.sonatype.com/repository/maven-snapshots/"
if (version.value.trim.endsWith("SNAPSHOT")) Some("snapshots" at nexus + "content/repositories/snapshots") if (isSnapshot.value) Some("central-snapshots" at centralSnapshots)
else Some("releases" at nexus + "service/local/staging/deploy/maven2") else localStaging.value
} }
publishMavenStyle := true publishMavenStyle := true
pomIncludeRepository := { _ => pomIncludeRepository := { _ =>
@@ -284,10 +284,12 @@ Test / testOptions ++= {
} }
} }
Jetty / javaOptions ++= Seq( Container / javaOptions ++= Seq(
"-Dlogback.configurationFile=/logback-dev.xml", "-Dlogback.configurationFile=/logback-dev.xml",
"-Xdebug", "-Xdebug",
"-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000", "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000",
"-Dorg.eclipse.jetty.annotations.AnnotationParser.LEVEL=OFF", "-Dorg.eclipse.jetty.annotations.AnnotationParser.LEVEL=OFF",
//"-Ddev-features=keep-session" // "-Ddev-features=keep-session"
) )
Container / containerLibs := Seq(("org.eclipse.jetty" % "jetty-runner" % JettyVersion).intransitive())
Container / containerMain := "org.eclipse.jetty.runner.Runner"

View File

@@ -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. If you want to test GitBucket, type the following command in the root directory of the source tree.
```shell ```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`. 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. Note that HttpSession is cleared when auto-reloading happened.
This is a bit annoying when developing features that requires sign-in. 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`: 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: Or by launching GitBucket with the following command:
```shell ```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 Note that this feature serializes HttpSession on the local disk and assigns all requests to the same session

View File

@@ -1,6 +1,6 @@
Debug GitBucket on IntelliJ Debug GitBucket on IntelliJ
======== ========
Add following configuration for allowing remote debugging to `buils.sbt`: Add following configuration for allowing remote debugging to `build.sbt`:
```scala ```scala
javaOptions in Jetty ++= Seq( javaOptions in Jetty ++= Seq(
@@ -12,7 +12,7 @@ javaOptions in Jetty ++= Seq(
Run GitBucket: Run GitBucket:
```shell ```shell
$ sbt ~jetty:start $ sbt ~container:start
``` ```
In IntelliJ, create remote debug configuration as follows. Make sure port number is same as above configuration. In IntelliJ, create remote debug configuration as follows. Make sure port number is same as above configuration.

View File

@@ -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. 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, start the sbt shell:
```bash ```bash
$ sbt publishSigned $ 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 ### Make release war file
@@ -55,5 +66,4 @@ Run `sbt executable`. The release war file and fingerprint are generated into `t
```bash ```bash
$ sbt executable $ sbt executable
``` ```
Create new release from the corresponded tag on GitHub, then upload generated jar file and fingerprints to the release. Create new release from the corresponded tag on GitHub, then upload generated jar file and fingerprints to the release.

View File

@@ -1 +1 @@
sbt.version=1.9.6 sbt.version=1.11.4

View File

@@ -1,11 +1,11 @@
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature") scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.5")
addSbtPlugin("com.typesafe.play" % "sbt-twirl" % "1.6.1") addSbtPlugin("org.playframework.twirl" % "sbt-twirl" % "2.0.9")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.1.3") addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.3.1")
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.4") addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.4")
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1") addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.3.1")
addSbtPlugin("com.github.sbt" % "sbt-license-report" % "1.6.1") addSbtPlugin("com.github.sbt" % "sbt-license-report" % "1.7.0")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.9") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.3.1")
addDependencyTreePlugin addDependencyTreePlugin

View File

@@ -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"/> <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"> <createTable tableName="ISSUE_LABEL">
<column name="USER_NAME" type="varchar(100)" nullable="false"/> <column name="USER_NAME" type="varchar(100)" nullable="false"/>

View 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>

View File

@@ -0,0 +1,40 @@
<?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"/>
<!--================================================================================================-->
<!-- PULL_REQUEST -->
<!--================================================================================================-->
<addColumn tableName="PULL_REQUEST">
<column name="MERGED_COMMIT_IDS" type="text" nullable="true"/>
</addColumn>
</changeSet>

View File

@@ -4,7 +4,6 @@ import java.io.FileOutputStream
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.sql.Connection import java.sql.Connection
import java.util.UUID import java.util.UUID
import gitbucket.core.model.Activity import gitbucket.core.model.Activity
import gitbucket.core.util.Directory.ActivityLog import gitbucket.core.util.Directory.ActivityLog
import gitbucket.core.util.JDBCUtil import gitbucket.core.util.JDBCUtil
@@ -15,6 +14,7 @@ import org.json4s.{Formats, NoTypeHints}
import org.json4s.jackson.Serialization import org.json4s.jackson.Serialization
import org.json4s.jackson.Serialization.write import org.json4s.jackson.Serialization.write
import java.util.logging.Level
import scala.util.Using import scala.util.Using
object GitBucketCoreModule object GitBucketCoreModule
@@ -76,8 +76,7 @@ object GitBucketCoreModule
import JDBCUtil._ import JDBCUtil._
val conn = context.get(Solidbase.CONNECTION).asInstanceOf[Connection] val conn = context.get(Solidbase.CONNECTION).asInstanceOf[Connection]
val list = conn.select("SELECT * FROM ACTIVITY ORDER BY ACTIVITY_ID") { val list = conn.select("SELECT * FROM ACTIVITY ORDER BY ACTIVITY_ID") { rs =>
rs =>
Activity( Activity(
activityId = UUID.randomUUID().toString, activityId = UUID.randomUUID().toString,
userName = rs.getString("USER_NAME"), userName = rs.getString("USER_NAME"),
@@ -117,5 +116,12 @@ object GitBucketCoreModule
new Version("4.38.3"), new Version("4.38.3"),
new Version("4.38.4"), new Version("4.38.4"),
new Version("4.39.0", new LiquibaseMigration("update/gitbucket-core_4.39.xml")), new Version("4.39.0", new LiquibaseMigration("update/gitbucket-core_4.39.xml")),
new Version("4.40.0") new Version("4.40.0"),
) new Version("4.41.0"),
new Version("4.42.0", new LiquibaseMigration("update/gitbucket-core_4.42.xml")),
new Version("4.42.1"),
new Version("4.43.0"),
new Version("4.44.0", new LiquibaseMigration("update/gitbucket-core_4.44.xml"))
) {
java.util.logging.Logger.getLogger("liquibase").setLevel(Level.SEVERE)
}

View File

@@ -6,7 +6,7 @@ import gitbucket.core.util.RepositoryName
* https://developer.github.com/v3/repos/#get-branch * https://developer.github.com/v3/repos/#get-branch
* https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection * 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 repositoryName: RepositoryName
) extends FieldSerializable { ) extends FieldSerializable {
val _links = val _links =

View File

@@ -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])
}

View File

@@ -4,55 +4,68 @@ import gitbucket.core.service.ProtectedBranchService
import org.json4s._ import org.json4s._
/** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */ /** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */
case class ApiBranchProtection( case class ApiBranchProtectionResponse(
url: Option[ApiPath], // for output url: Option[ApiPath], // for output
enabled: Boolean, 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 EnforceAdmins(enabled: Boolean)
case class EnablingAndDisabling(protection: ApiBranchProtection)
def apply(info: ProtectedBranchService.ProtectedBranchInfo): ApiBranchProtection = // /** form for enabling-and-disabling-branch-protection */
ApiBranchProtection( // case class EnablingAndDisabling(protection: ApiBranchProtectionResponse)
def apply(info: ProtectedBranchService.ProtectedBranchInfo): ApiBranchProtectionResponse =
ApiBranchProtectionResponse(
url = Some( url = Some(
ApiPath( ApiPath(
s"/api/v3/repos/${info.owner}/${info.repository}/branches/${info.branch}/protection" s"/api/v3/repos/${info.owner}/${info.repository}/branches/${info.branch}/protection"
) )
), ),
enabled = info.enabled, enabled = info.enabled,
required_status_checks = Some( required_status_checks = info.contexts.map { contexts =>
Status( Status(
Some( Some(
ApiPath( ApiPath(
s"/api/v3/repos/${info.owner}/${info.repository}/branches/${info.branch}/protection/required_status_checks" s"/api/v3/repos/${info.owner}/${info.repository}/branches/${info.branch}/protection/required_status_checks"
) )
), ),
EnforcementLevel(info.enabled && info.contexts.nonEmpty, info.includeAdministrators), EnforcementLevel(info.enabled && info.contexts.nonEmpty, info.enforceAdmins),
info.contexts, contexts,
Some( Some(
ApiPath( ApiPath(
s"/api/v3/repos/${info.owner}/${info.repository}/branches/${info.branch}/protection/required_status_checks/contexts" 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( case class Status(
url: Option[ApiPath], // for output url: Option[ApiPath], // for output
enforcement_level: EnforcementLevel, enforcement_level: EnforcementLevel,
contexts: Seq[String], contexts: Seq[String],
contexts_url: Option[ApiPath] // for output contexts_url: Option[ApiPath] // for output
) )
sealed class EnforcementLevel(val name: String) sealed class EnforcementLevel(val name: String)
case object Off extends EnforcementLevel("off") case object Off extends EnforcementLevel("off")
case object NonAdmins extends EnforcementLevel("non_admins") case object NonAdmins extends EnforcementLevel("non_admins")
case object Everyone extends EnforcementLevel("everyone") case object Everyone extends EnforcementLevel("everyone")
object EnforcementLevel { object EnforcementLevel {
def apply(enabled: Boolean, includeAdministrators: Boolean): EnforcementLevel = def apply(enabled: Boolean, includeAdministrators: Boolean): EnforcementLevel =
if (enabled) { if (enabled) {
@@ -66,15 +79,18 @@ object ApiBranchProtection {
} }
} }
implicit val enforcementLevelSerializer: CustomSerializer[EnforcementLevel] = new CustomSerializer[EnforcementLevel]( case class Restrictions(users: Seq[String])
format =>
implicit val enforcementLevelSerializer: CustomSerializer[EnforcementLevel] =
new CustomSerializer[EnforcementLevel](format =>
( (
{ {
case JString("off") => Off case JString("off") => Off
case JString("non_admins") => NonAdmins case JString("non_admins") => NonAdmins
case JString("everyone") => Everyone case JString("everyone") => Everyone
}, { },
case x: EnforcementLevel => JString(x.name) { case x: EnforcementLevel =>
JString(x.name)
} }
) )
) )

View File

@@ -14,7 +14,8 @@ case class ApiComment(id: Int, user: ApiUser, body: String, created_at: Date, up
isPullRequest: Boolean isPullRequest: Boolean
) { ) {
val html_url = ApiPath( val html_url = ApiPath(
s"/${repositoryName.fullName}/${if (isPullRequest) { "pull" } else { "issues" }}/${issueId}#comment-${id}" s"/${repositoryName.fullName}/${if (isPullRequest) { "pull" }
else { "issues" }}/${issueId}#comment-${id}"
) )
} }

View File

@@ -29,7 +29,7 @@ case class ApiCommit(
object ApiCommit { object ApiCommit {
def apply(git: Git, repositoryName: RepositoryName, commit: CommitInfo): ApiCommit = { def apply(git: Git, repositoryName: RepositoryName, commit: CommitInfo): ApiCommit = {
val diffs = JGitUtil.getDiffs(git, None, commit.id, false, false) val diffs = JGitUtil.getDiffs(git = git, from = None, to = commit.id, fetchContent = false, makePatch = false)
ApiCommit( ApiCommit(
id = commit.id, id = commit.id,
message = commit.fullMessage, message = commit.fullMessage,

View File

@@ -69,7 +69,8 @@ object ApiCommits {
} }
File( File(
filename = if (diff.changeType == ChangeType.DELETE) { diff.oldPath } else { diff.newPath }, filename = if (diff.changeType == ChangeType.DELETE) { diff.oldPath }
else { diff.newPath },
additions = additions, additions = additions,
deletions = deletions, deletions = deletions,
changes = additions + deletions, changes = additions + deletions,
@@ -106,7 +107,9 @@ object ApiCommits {
message = commitInfo.shortMessage, message = commitInfo.shortMessage,
comment_count = commentCount, comment_count = commentCount,
tree = Tree( tree = Tree(
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/tree/${commitInfo.id}"), // TODO This endpoint has not been implemented yet. url = ApiPath(
s"/api/v3/repos/${repositoryName.fullName}/tree/${commitInfo.id}"
), // TODO This endpoint has not been implemented yet.
sha = commitInfo.id sha = commitInfo.id
) )
), ),
@@ -114,7 +117,9 @@ object ApiCommits {
committer = ApiUser(committer), committer = ApiUser(committer),
parents = commitInfo.parents.map { parent => parents = commitInfo.parents.map { parent =>
Tree( Tree(
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/tree/${parent}"), // TODO This endpoint has not been implemented yet. url = ApiPath(
s"/api/v3/repos/${repositoryName.fullName}/tree/${parent}"
), // TODO This endpoint has not been implemented yet.
sha = parent sha = parent
) )
}, },

View File

@@ -22,8 +22,7 @@ object ApiContents {
ApiContents("dir", fileInfo.name, fileInfo.path, fileInfo.commitId, None, None)(repositoryName) ApiContents("dir", fileInfo.name, fileInfo.path, fileInfo.commitId, None, None)(repositoryName)
} else { } else {
content content
.map( .map(arr =>
arr =>
ApiContents( ApiContents(
"file", "file",
fileInfo.name, fileInfo.name,

View File

@@ -23,7 +23,8 @@ case class ApiIssue(
val id = 0 // dummy id val id = 0 // dummy id
val assignee = assignees.headOption val assignee = assignees.headOption
val comments_url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/issues/${number}/comments") val comments_url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/issues/${number}/comments")
val html_url = ApiPath(s"/${repositoryName.fullName}/${if (isPullRequest) { "pull" } else { "issues" }}/${number}") val html_url = ApiPath(s"/${repositoryName.fullName}/${if (isPullRequest) { "pull" }
else { "issues" }}/${number}")
val pull_request = if (isPullRequest) { val pull_request = if (isPullRequest) {
Some( Some(
Map( Map(
@@ -54,7 +55,8 @@ object ApiIssue {
assignees = assignees, assignees = assignees,
labels = labels, labels = labels,
milestone = milestone, milestone = milestone,
state = if (issue.closed) { "closed" } else { "open" }, state = if (issue.closed) { "closed" }
else { "open" },
body = issue.content.getOrElse(""), body = issue.content.getOrElse(""),
created_at = issue.registeredDate, created_at = issue.registeredDate,
updated_at = issue.updatedDate updated_at = issue.updatedDate

View File

@@ -27,10 +27,10 @@ case class ApiPullRequest(
val id = 0 // dummy id val id = 0 // dummy id
val assignee = assignees.headOption val assignee = assignees.headOption
val html_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}") val html_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}")
//val diff_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.diff") // val diff_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.diff")
//val patch_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.patch") // val patch_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.patch")
val url = ApiPath(s"${base.repo.url.path}/pulls/${number}") val url = ApiPath(s"${base.repo.url.path}/pulls/${number}")
//val issue_url = ApiPath(s"${base.repo.url.path}/issues/${number}") // val issue_url = ApiPath(s"${base.repo.url.path}/issues/${number}")
val commits_url = ApiPath(s"${base.repo.url.path}/pulls/${number}/commits") val commits_url = ApiPath(s"${base.repo.url.path}/pulls/${number}/commits")
val review_comments_url = ApiPath(s"${base.repo.url.path}/pulls/${number}/comments") val review_comments_url = ApiPath(s"${base.repo.url.path}/pulls/${number}/comments")
val review_comment_url = ApiPath(s"${base.repo.url.path}/pulls/comments/{number}") val review_comment_url = ApiPath(s"${base.repo.url.path}/pulls/comments/{number}")
@@ -69,7 +69,8 @@ object ApiPullRequest {
) )
case class Commit(sha: String, ref: String, repo: ApiRepository)(baseOwner: String) { case class Commit(sha: String, ref: String, repo: ApiRepository)(baseOwner: String) {
val label = if (baseOwner == repo.owner.login) { ref } else { s"${repo.owner.login}:${ref}" } val label = if (baseOwner == repo.owner.login) { ref }
else { s"${repo.owner.login}:${ref}" }
val user = repo.owner val user = repo.owner
} }

View File

@@ -40,12 +40,12 @@ object ApiRef {
ApiRef( ApiRef(
ref = s"refs/tags/${tagInfo.name}", ref = s"refs/tags/${tagInfo.name}",
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/refs/tags/${tagInfo.name}"), url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/refs/tags/${tagInfo.name}"),
//the GH api distinguishes between "releases" and plain git tags // the GH api distinguishes between "releases" and plain git tags
//for "releases", the api returns a reference to the release object (with type `tag`) // for "releases", the api returns a reference to the release object (with type `tag`)
//this would be something like s"/api/v3/repos/${repositoryName.fullName}/git/tags/<hash-of-tag>" // this would be something like s"/api/v3/repos/${repositoryName.fullName}/git/tags/<hash-of-tag>"
//with a hash for the tag, which I do not fully understand // with a hash for the tag, which I do not fully understand
//since this is not yet implemented in GB, we always return a link to the plain `commit` object, // since this is not yet implemented in GB, we always return a link to the plain `commit` object,
//which GH does for tags that are not annotated // which GH does for tags that are not annotated
`object` = ApiRefCommit( `object` = ApiRefCommit(
sha = tagInfo.objectId, sha = tagInfo.objectId,
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${tagInfo.objectId}"), url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${tagInfo.objectId}"),

View File

@@ -21,7 +21,7 @@ case class ApiRepository(
val url = ApiPath(s"/api/v3/repos/${full_name}") val url = ApiPath(s"/api/v3/repos/${full_name}")
val clone_url = ApiPath(s"/git/${full_name}.git") val clone_url = ApiPath(s"/git/${full_name}.git")
val html_url = ApiPath(s"/${full_name}") val html_url = ApiPath(s"/${full_name}")
val ssh_url = Some(SshPath("")) val ssh_url = Some(SshPath(s"/${full_name}.git"))
} }
object ApiRepository { object ApiRepository {

View 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")
)
}

View File

@@ -24,7 +24,8 @@ object ApiUser {
def apply(user: Account): ApiUser = ApiUser( def apply(user: Account): ApiUser = ApiUser(
login = user.userName, login = user.userName,
email = user.mailAddress, email = user.mailAddress,
`type` = if (user.isGroupAccount) { "Organization" } else { "User" }, `type` = if (user.isGroupAccount) { "Organization" }
else { "User" },
site_admin = user.isAdmin, site_admin = user.isAdmin,
created_at = user.registeredDate created_at = user.registeredDate
) )

View File

@@ -15,13 +15,12 @@ object JsonFormat {
val parserISO = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'") val parserISO = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")
val jsonFormats = Serialization.formats(NoTypeHints) + new CustomSerializer[Date]( val jsonFormats = Serialization.formats(NoTypeHints) + new CustomSerializer[Date](format =>
format =>
( (
{ { case JString(s) =>
case JString(s) =>
Try(Date.from(Instant.parse(s))).getOrElse(throw new MappingException("Can't convert " + s + " to Date")) Try(Date.from(Instant.parse(s))).getOrElse(throw new MappingException("Can't convert " + s + " to Date"))
}, { case x: Date => JString(OffsetDateTime.ofInstant(x.toInstant, ZoneId.of("UTC")).format(parserISO)) } },
{ case x: Date => JString(OffsetDateTime.ofInstant(x.toInstant, ZoneId.of("UTC")).format(parserISO)) }
) )
) + FieldSerializer[ApiUser]() + ) + FieldSerializer[ApiUser]() +
FieldSerializer[ApiGroup]() + FieldSerializer[ApiGroup]() +
@@ -45,32 +44,35 @@ object JsonFormat {
FieldSerializer[ApiCommits.File]() + FieldSerializer[ApiCommits.File]() +
FieldSerializer[ApiRelease]() + FieldSerializer[ApiRelease]() +
FieldSerializer[ApiReleaseAsset]() + FieldSerializer[ApiReleaseAsset]() +
ApiBranchProtection.enforcementLevelSerializer ApiBranchProtectionResponse.enforcementLevelSerializer
def apiPathSerializer(c: Context) = def apiPathSerializer(c: Context) =
new CustomSerializer[ApiPath]( new CustomSerializer[ApiPath](_ =>
_ => (
({ {
case JString(s) if s.startsWith(c.baseUrl) => ApiPath(s.substring(c.baseUrl.length)) case JString(s) if s.startsWith(c.baseUrl) => ApiPath(s.substring(c.baseUrl.length))
case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath") case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath")
}, { },
case ApiPath(path) => JString(c.baseUrl + path) { case ApiPath(path) =>
}) JString(c.baseUrl + path)
}
)
) )
def sshPathSerializer(c: Context) = def sshPathSerializer(c: Context) =
new CustomSerializer[SshPath]( new CustomSerializer[SshPath](_ =>
_ => (
({ {
case JString(s) if c.sshUrl.exists(sshUrl => s.startsWith(sshUrl)) => case JString(s) if c.sshUrl.exists(sshUrl => s.startsWith(sshUrl)) =>
SshPath(s.substring(c.sshUrl.get.length)) SshPath(s.substring(c.sshUrl.get.length))
case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath") case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath")
}, { },
case SshPath(path) => { case SshPath(path) =>
c.sshUrl.map { sshUrl => c.sshUrl.map { sshUrl =>
JString(sshUrl + path) JString(sshUrl + path)
} getOrElse JNothing } getOrElse JNothing
}) }
)
) )
/** /**

View File

@@ -38,23 +38,11 @@ class AccountController
with RequestCache with RequestCache
trait AccountControllerBase extends AccountManagementControllerBase { trait AccountControllerBase extends AccountManagementControllerBase {
self: AccountService self: AccountService & RepositoryService & ActivityService & WikiService & LabelsService & SshKeyService &
with RepositoryService GpgKeyService & OneselfAuthenticator & UsersAuthenticator & GroupManagerAuthenticator & ReadableUsersAuthenticator &
with ActivityService AccessTokenService & WebHookService & PrioritiesService & RepositoryCreationService =>
with WikiService
with LabelsService
with SshKeyService
with GpgKeyService
with OneselfAuthenticator
with UsersAuthenticator
with GroupManagerAuthenticator
with ReadableUsersAuthenticator
with AccessTokenService
with WebHookService
with PrioritiesService
with RepositoryCreationService =>
case class AccountNewForm( private case class AccountNewForm(
userName: String, userName: String,
password: String, password: String,
fullName: String, fullName: String,
@@ -65,7 +53,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
fileId: Option[String] fileId: Option[String]
) )
case class AccountEditForm( private case class AccountEditForm(
password: Option[String], password: Option[String],
fullName: String, fullName: String,
mailAddress: String, mailAddress: String,
@@ -76,15 +64,15 @@ trait AccountControllerBase extends AccountManagementControllerBase {
clearImage: Boolean 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))), "userName" -> trim(label("User name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
"password" -> trim(label("Password", text(required, maxlength(40)))), "password" -> trim(label("Password", text(required, maxlength(40)))),
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))), "fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
@@ -97,7 +85,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
"fileId" -> trim(label("File ID", optional(text()))) "fileId" -> trim(label("File ID", optional(text())))
)(AccountNewForm.apply) )(AccountNewForm.apply)
val editForm = mapping( private val editForm = mapping(
"password" -> trim(label("Password", optional(text(maxlength(40))))), "password" -> trim(label("Password", optional(text(maxlength(40))))),
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))), "fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))), "mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))),
@@ -110,41 +98,41 @@ trait AccountControllerBase extends AccountManagementControllerBase {
"clearImage" -> trim(label("Clear image", boolean())) "clearImage" -> trim(label("Clear image", boolean()))
)(AccountEditForm.apply) )(AccountEditForm.apply)
val sshKeyForm = mapping( private val sshKeyForm = mapping(
"title" -> trim(label("Title", text(required, maxlength(100)))), "title" -> trim(label("Title", text(required, maxlength(100)))),
"publicKey" -> trim2(label("Key", text(required, validPublicKey))) "publicKey" -> trim2(label("Key", text(required, validPublicKey)))
)(SshKeyForm.apply) )(SshKeyForm.apply)
val gpgKeyForm = mapping( private val gpgKeyForm = mapping(
"title" -> trim(label("Title", text(required, maxlength(100)))), "title" -> trim(label("Title", text(required, maxlength(100)))),
"publicKey" -> label("Key", text(required, validGpgPublicKey)) "publicKey" -> label("Key", text(required, validGpgPublicKey))
)(GpgKeyForm.apply) )(GpgKeyForm.apply)
val personalTokenForm = mapping( private val personalTokenForm = mapping(
"note" -> trim(label("Token", text(required, maxlength(100)))) "note" -> trim(label("Token", text(required, maxlength(100))))
)(PersonalTokenForm.apply) )(PersonalTokenForm.apply)
val syntaxHighlighterThemeForm = mapping( private val syntaxHighlighterThemeForm = mapping(
"highlighterTheme" -> trim(label("Theme", text(required))) "highlighterTheme" -> trim(label("Theme", text(required)))
)(SyntaxHighlighterThemeForm.apply) )(SyntaxHighlighterThemeForm.apply)
val resetPasswordEmailForm = mapping( private val resetPasswordEmailForm = mapping(
"mailAddress" -> trim(label("Email", text(required))) "mailAddress" -> trim(label("Email", text(required)))
)(ResetPasswordEmailForm.apply) )(ResetPasswordEmailForm.apply)
val resetPasswordForm = mapping( private val resetPasswordForm = mapping(
"token" -> trim(label("Token", text(required))), "token" -> trim(label("Token", text(required))),
"password" -> trim(label("Password", text(required, maxlength(40)))) "password" -> trim(label("Password", text(required, maxlength(40))))
)(ResetPasswordForm.apply) )(ResetPasswordForm.apply)
case class NewGroupForm( private case class NewGroupForm(
groupName: String, groupName: String,
description: Option[String], description: Option[String],
url: Option[String], url: Option[String],
fileId: Option[String], fileId: Option[String],
members: String members: String
) )
case class EditGroupForm( private case class EditGroupForm(
groupName: String, groupName: String,
description: Option[String], description: Option[String],
url: Option[String], url: Option[String],
@@ -152,15 +140,15 @@ trait AccountControllerBase extends AccountManagementControllerBase {
members: String, members: String,
clearImage: Boolean clearImage: Boolean
) )
case class ResetPasswordEmailForm( private case class ResetPasswordEmailForm(
mailAddress: String mailAddress: String
) )
case class ResetPasswordForm( private case class ResetPasswordForm(
token: String, token: String,
password: String password: String
) )
val newGroupForm = mapping( private val newGroupForm = mapping(
"groupName" -> trim(label("Group name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))), "groupName" -> trim(label("Group name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
"description" -> trim(label("Group description", optional(text()))), "description" -> trim(label("Group description", optional(text()))),
"url" -> trim(label("URL", optional(text(maxlength(200))))), "url" -> trim(label("URL", optional(text(maxlength(200))))),
@@ -168,7 +156,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
"members" -> trim(label("Members", text(required, members))) "members" -> trim(label("Members", text(required, members)))
)(NewGroupForm.apply) )(NewGroupForm.apply)
val editGroupForm = mapping( private val editGroupForm = mapping(
"groupName" -> trim(label("Group name", text(required, maxlength(100), identifier))), "groupName" -> trim(label("Group name", text(required, maxlength(100), identifier))),
"description" -> trim(label("Group description", optional(text()))), "description" -> trim(label("Group description", optional(text()))),
"url" -> trim(label("URL", optional(text(maxlength(200))))), "url" -> trim(label("URL", optional(text(maxlength(200))))),
@@ -177,7 +165,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
"clearImage" -> trim(label("Clear image", boolean())) "clearImage" -> trim(label("Clear image", boolean()))
)(EditGroupForm.apply) )(EditGroupForm.apply)
case class RepositoryCreationForm( private case class RepositoryCreationForm(
owner: String, owner: String,
name: String, name: String,
description: Option[String], description: Option[String],
@@ -186,7 +174,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
sourceUrl: Option[String] sourceUrl: Option[String]
) )
val newRepositoryForm = mapping( private val newRepositoryForm = mapping(
"owner" -> trim(label("Owner", text(required, maxlength(100), identifier, existsAccount))), "owner" -> trim(label("Owner", text(required, maxlength(100), identifier, existsAccount))),
"name" -> trim(label("Repository name", text(required, maxlength(100), repository, uniqueRepository))), "name" -> trim(label("Repository name", text(required, maxlength(100), repository, uniqueRepository))),
"description" -> trim(label("Description", optional(text()))), "description" -> trim(label("Description", optional(text()))),
@@ -195,29 +183,27 @@ trait AccountControllerBase extends AccountManagementControllerBase {
"sourceUrl" -> trim(label("Source URL", optionalRequired(_.value("initOption") == "COPY", text()))) "sourceUrl" -> trim(label("Source URL", optionalRequired(_.value("initOption") == "COPY", text())))
)(RepositoryCreationForm.apply) )(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))) "account" -> trim(label("Group/User name", text(required, validAccountName)))
)(AccountForm.apply) )(AccountForm.apply)
// for account web hook url addition. // for account web hook url addition.
case class AccountWebHookForm( private case class AccountWebHookForm(
url: String, url: String,
events: Set[WebHook.Event], events: Set[WebHook.Event],
ctype: WebHookContentType, ctype: WebHookContentType,
token: Option[String] token: Option[String]
) )
def accountWebHookForm(update: Boolean) = private def accountWebHookForm(update: Boolean) =
mapping( mapping(
"url" -> trim(label("url", text(required, accountWebHook(update)))), "url" -> trim(label("url", text(required, accountWebHook(update)))),
"events" -> accountWebhookEvents, "events" -> accountWebhookEvents,
"ctype" -> label("ctype", text()), "ctype" -> label("ctype", text()),
"token" -> optional(trim(label("token", text(maxlength(100))))) "token" -> optional(trim(label("token", text(maxlength(100)))))
)( )((url, events, ctype, token) => AccountWebHookForm(url, events, WebHookContentType.valueOf(ctype), token))
(url, events, ctype, token) => AccountWebHookForm(url, events, WebHookContentType.valueOf(ctype), token)
)
/** /**
* Provides duplication check for web hook url. duplicated from RepositorySettingsController.scala * Provides duplication check for web hook url. duplicated from RepositorySettingsController.scala
@@ -267,7 +253,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
) )
// Members // Members
case "members" if (account.isGroupAccount) => { case "members" if account.isGroupAccount =>
val members = getGroupMembers(account.userName) val members = getGroupMembers(account.userName)
gitbucket.core.account.html.members( gitbucket.core.account.html.members(
account, account,
@@ -275,10 +261,9 @@ trait AccountControllerBase extends AccountManagementControllerBase {
extraMailAddresses, extraMailAddresses,
isGroupManager(context.loginAccount, members) isGroupManager(context.loginAccount, members)
) )
}
// Repositories // Repositories
case _ => { case _ =>
val members = getGroupMembers(account.userName) val members = getGroupMembers(account.userName)
gitbucket.core.account.html.repositories( gitbucket.core.account.html.repositories(
account, account,
@@ -288,14 +273,13 @@ trait AccountControllerBase extends AccountManagementControllerBase {
isGroupManager(context.loginAccount, members) isGroupManager(context.loginAccount, members)
) )
} }
}
} getOrElse NotFound() } getOrElse NotFound()
} }
get("/:userName.atom") { get("/:userName.atom") {
val userName = params("userName") val userName = params("userName")
contentType = "application/atom+xml; type=feed" contentType = "application/atom+xml; type=feed"
helper.xml.feed(getActivitiesByUser(userName, true)) helper.xml.feed(getActivitiesByUser(userName, publicOnly = true))
} }
get("/:userName.keys") { get("/:userName.keys") {
@@ -340,8 +324,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
post("/:userName/_edit", editForm)(oneselfOnly { form => post("/:userName/_edit", editForm)(oneselfOnly { form =>
val userName = params("userName") val userName = params("userName")
getAccountByUserName(userName).map { getAccountByUserName(userName).map { account =>
account =>
updateAccount( updateAccount(
account.copy( account.copy(
password = form.password.map(pbkdf2_sha256).getOrElse(account.password), password = form.password.map(pbkdf2_sha256).getOrElse(account.password),
@@ -355,7 +338,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
updateImage(userName, form.fileId, form.clearImage) updateImage(userName, form.fileId, form.clearImage)
updateAccountExtraMailAddresses(userName, form.extraMailAddresses.filter(_ != "")) updateAccountExtraMailAddresses(userName, form.extraMailAddresses.filter(_ != ""))
flash.update("info", "Account information has been updated.") flash.update("info", "Account information has been updated.")
redirect(s"/${userName}/_edit") redirect(s"/$userName/_edit")
} getOrElse NotFound() } getOrElse NotFound()
}) })
@@ -363,11 +346,10 @@ trait AccountControllerBase extends AccountManagementControllerBase {
get("/:userName/_delete")(oneselfOnly { get("/:userName/_delete")(oneselfOnly {
val userName = params("userName") val userName = params("userName")
getAccountByUserName(userName, true).map { getAccountByUserName(userName, includeRemoved = true).map { account =>
account =>
if (isLastAdministrator(account)) { if (isLastAdministrator(account)) {
flash.update("error", "Account can't be removed because this is last one administrator.") flash.update("error", "Account can't be removed because this is last one administrator.")
redirect(s"/${userName}/_edit") redirect(s"/$userName/_edit")
} else { } else {
// // Remove repositories // // Remove repositories
// getRepositoryNamesOfUser(userName).foreach { repositoryName => // getRepositoryNamesOfUser(userName).foreach { repositoryName =>
@@ -377,7 +359,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName)) // FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
// } // }
suspendAccount(account) suspendAccount(account)
session.invalidate session.invalidate()
redirect("/") redirect("/")
} }
} getOrElse NotFound() } getOrElse NotFound()
@@ -393,20 +375,20 @@ trait AccountControllerBase extends AccountManagementControllerBase {
post("/:userName/_ssh", sshKeyForm)(oneselfOnly { form => post("/:userName/_ssh", sshKeyForm)(oneselfOnly { form =>
val userName = params("userName") val userName = params("userName")
addPublicKey(userName, form.title, form.publicKey) addPublicKey(userName, form.title, form.publicKey)
redirect(s"/${userName}/_ssh") redirect(s"/$userName/_ssh")
}) })
get("/:userName/_ssh/delete/:id")(oneselfOnly { get("/:userName/_ssh/delete/:id")(oneselfOnly {
val userName = params("userName") val userName = params("userName")
val sshKeyId = params("id").toInt val sshKeyId = params("id").toInt
deletePublicKey(userName, sshKeyId) deletePublicKey(userName, sshKeyId)
redirect(s"/${userName}/_ssh") redirect(s"/$userName/_ssh")
}) })
get("/:userName/_gpg")(oneselfOnly { get("/:userName/_gpg")(oneselfOnly {
val userName = params("userName") val userName = params("userName")
getAccountByUserName(userName).map { x => getAccountByUserName(userName).map { x =>
//html.ssh(x, getPublicKeys(x.userName)) // html.ssh(x, getPublicKeys(x.userName))
html.gpg(x, getGpgPublicKeys(x.userName)) html.gpg(x, getGpgPublicKeys(x.userName))
} getOrElse NotFound() } getOrElse NotFound()
}) })
@@ -414,14 +396,14 @@ trait AccountControllerBase extends AccountManagementControllerBase {
post("/:userName/_gpg", gpgKeyForm)(oneselfOnly { form => post("/:userName/_gpg", gpgKeyForm)(oneselfOnly { form =>
val userName = params("userName") val userName = params("userName")
addGpgPublicKey(userName, form.title, form.publicKey) addGpgPublicKey(userName, form.title, form.publicKey)
redirect(s"/${userName}/_gpg") redirect(s"/$userName/_gpg")
}) })
get("/:userName/_gpg/delete/:id")(oneselfOnly { get("/:userName/_gpg/delete/:id")(oneselfOnly {
val userName = params("userName") val userName = params("userName")
val keyId = params("id").toInt val keyId = params("id").toInt
deleteGpgPublicKey(userName, keyId) deleteGpgPublicKey(userName, keyId)
redirect(s"/${userName}/_gpg") redirect(s"/$userName/_gpg")
}) })
get("/:userName/_application")(oneselfOnly { get("/:userName/_application")(oneselfOnly {
@@ -429,13 +411,12 @@ trait AccountControllerBase extends AccountManagementControllerBase {
getAccountByUserName(userName).map { x => getAccountByUserName(userName).map { x =>
var tokens = getAccessTokens(x.userName) var tokens = getAccessTokens(x.userName)
val generatedToken = flash.get("generatedToken") match { val generatedToken = flash.get("generatedToken") match {
case Some((tokenId: Int, token: String)) => { case Some((tokenId: Int, token: String)) =>
val gt = tokens.find(_.accessTokenId == tokenId) val gt = tokens.find(_.accessTokenId == tokenId)
gt.map { t => gt.map { t =>
tokens = tokens.filterNot(_ == t) tokens = tokens.filterNot(_ == t)
(t, token) (t, token)
} }
}
case _ => None case _ => None
} }
html.application(x, tokens, generatedToken) html.application(x, tokens, generatedToken)
@@ -444,18 +425,18 @@ trait AccountControllerBase extends AccountManagementControllerBase {
post("/:userName/_personalToken", personalTokenForm)(oneselfOnly { form => post("/:userName/_personalToken", personalTokenForm)(oneselfOnly { form =>
val userName = params("userName") val userName = params("userName")
getAccountByUserName(userName).foreach { x => getAccountByUserName(userName).foreach { _ =>
val (tokenId, token) = generateAccessToken(userName, form.note) val (tokenId, token) = generateAccessToken(userName, form.note)
flash.update("generatedToken", (tokenId, token)) flash.update("generatedToken", (tokenId, token))
} }
redirect(s"/${userName}/_application") redirect(s"/$userName/_application")
}) })
get("/:userName/_personalToken/delete/:id")(oneselfOnly { get("/:userName/_personalToken/delete/:id")(oneselfOnly {
val userName = params("userName") val userName = params("userName")
val tokenId = params("id").toInt val tokenId = params("id").toInt
deleteAccessToken(userName, tokenId) deleteAccessToken(userName, tokenId)
redirect(s"/${userName}/_application") redirect(s"/$userName/_application")
}) })
/** /**
@@ -478,7 +459,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
post("/:userName/_preferences/highlighter", syntaxHighlighterThemeForm)(oneselfOnly { form => post("/:userName/_preferences/highlighter", syntaxHighlighterThemeForm)(oneselfOnly { form =>
val userName = params("userName") val userName = params("userName")
addOrUpdateAccountPreference(userName, form.theme) addOrUpdateAccountPreference(userName, form.theme)
redirect(s"/${userName}/_preferences") redirect(s"/$userName/_preferences")
}) })
get("/:userName/_hooks")(managersOnly { get("/:userName/_hooks")(managersOnly {
@@ -495,7 +476,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
val userName = params("userName") val userName = params("userName")
getAccountByUserName(userName).map { account => getAccountByUserName(userName).map { account =>
val webhook = AccountWebHook(userName, "", WebHookContentType.FORM, None) 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() } getOrElse NotFound()
}) })
@@ -506,7 +487,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
val userName = params("userName") val userName = params("userName")
addAccountWebHook(userName, form.url, form.events, form.ctype, form.token) addAccountWebHook(userName, form.url, form.events, form.ctype, form.token)
flash.update("info", s"Webhook ${form.url} created") flash.update("info", s"Webhook ${form.url} created")
redirect(s"/${userName}/_hooks") redirect(s"/$userName/_hooks")
}) })
/** /**
@@ -516,7 +497,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
val userName = params("userName") val userName = params("userName")
deleteAccountWebHook(userName, params("url")) deleteAccountWebHook(userName, params("url"))
flash.update("info", s"Webhook ${params("url")} deleted") flash.update("info", s"Webhook ${params("url")} deleted")
redirect(s"/${userName}/_hooks") redirect(s"/$userName/_hooks")
}) })
/** /**
@@ -525,9 +506,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
get("/:userName/_hooks/edit")(managersOnly { get("/:userName/_hooks/edit")(managersOnly {
val userName = params("userName") val userName = params("userName")
getAccountByUserName(userName).flatMap { account => getAccountByUserName(userName).flatMap { account =>
getAccountWebHook(userName, params("url")).map { getAccountWebHook(userName, params("url")).map { case (webhook, events) =>
case (webhook, events) => html.edithook(webhook, events, account, create = false)
html.edithook(webhook, events, account, false)
} }
} getOrElse NotFound() } getOrElse NotFound()
}) })
@@ -539,7 +519,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
val userName = params("userName") val userName = params("userName")
updateAccountWebHook(userName, form.url, form.events, form.ctype, form.token) updateAccountWebHook(userName, form.url, form.events, form.ctype, form.token)
flash.update("info", s"webhook ${form.url} updated") flash.update("info", s"webhook ${form.url} updated")
redirect(s"/${userName}/_hooks") redirect(s"/$userName/_hooks")
}) })
/** /**
@@ -547,8 +527,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
*/ */
ajaxPost("/:userName/_hooks/test")(managersOnly { ajaxPost("/:userName/_hooks/test")(managersOnly {
// TODO Is it possible to merge with [[RepositorySettingsController.ajaxPost]]? // TODO Is it possible to merge with [[RepositorySettingsController.ajaxPost]]?
import scala.concurrent.duration._ import scala.concurrent.duration.*
import scala.concurrent._ import scala.concurrent.*
import scala.util.control.NonFatal import scala.util.control.NonFatal
import org.apache.http.util.EntityUtils import org.apache.http.util.EntityUtils
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
@@ -572,10 +552,10 @@ trait AccountControllerBase extends AccountManagementControllerBase {
callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload, context.settings).head callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload, context.settings).head
val toErrorMap: PartialFunction[Throwable, Map[String, String]] = { val toErrorMap: PartialFunction[Throwable, Map[String, String]] = {
case e: java.net.UnknownHostException => Map("error" -> ("Unknown host " + e.getMessage)) case e: java.net.UnknownHostException => Map("error" -> s"Unknown host ${e.getMessage}")
case e: java.lang.IllegalArgumentException => Map("error" -> ("invalid url")) case _: java.lang.IllegalArgumentException => Map("error" -> "invalid url")
case e: org.apache.http.client.ClientProtocolException => Map("error" -> ("invalid url")) case _: org.apache.http.client.ClientProtocolException => Map("error" -> "invalid url")
case NonFatal(e) => Map("error" -> (s"${e.getClass} ${e.getMessage}")) case NonFatal(e) => Map("error" -> s"${e.getClass} ${e.getMessage}")
} }
contentType = formats("json") contentType = formats("json")
@@ -584,8 +564,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
"url" -> url, "url" -> url,
"request" -> Await.result( "request" -> Await.result(
reqFuture reqFuture
.map( .map(req =>
req =>
Map( Map(
"headers" -> _headers(req.getAllHeaders), "headers" -> _headers(req.getAllHeaders),
"payload" -> json "payload" -> json
@@ -596,12 +575,11 @@ trait AccountControllerBase extends AccountManagementControllerBase {
), ),
"response" -> Await.result( "response" -> Await.result(
resFuture resFuture
.map( .map(res =>
res =>
Map( Map(
"status" -> res.getStatusLine(), "status" -> res.getStatusLine,
"body" -> EntityUtils.toString(res.getEntity()), "body" -> EntityUtils.toString(res.getEntity),
"headers" -> _headers(res.getAllHeaders()) "headers" -> _headers(res.getAllHeaders)
) )
) )
.recover(toErrorMap), .recover(toErrorMap),
@@ -628,11 +606,11 @@ trait AccountControllerBase extends AccountManagementControllerBase {
pbkdf2_sha256(form.password), pbkdf2_sha256(form.password),
form.fullName, form.fullName,
form.mailAddress, form.mailAddress,
false, isAdmin = false,
form.description, form.description,
form.url form.url
) )
updateImage(form.userName, form.fileId, false) updateImage(form.userName, form.fileId, clearImage = false)
updateAccountExtraMailAddresses(form.userName, form.extraMailAddresses.filter(_ != "")) updateAccountExtraMailAddresses(form.userName, form.extraMailAddresses.filter(_ != ""))
redirect("/signin") redirect("/signin")
} else NotFound() } else NotFound()
@@ -657,7 +635,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|You requested to reset the password for your GitBucket account. |You requested to reset the password for your GitBucket account.
|If you are not sure about the request, you can ignore this email. |If you are not sure about the request, you can ignore this email.
|Otherwise, click the following link to set the new password: |Otherwise, click the following link to set the new password:
|${context.baseUrl}/reset/form/${token} |${context.baseUrl}/reset/form/$token
|""".stripMargin |""".stripMargin
) )
} }
@@ -697,7 +675,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
get("/groups/new")(usersOnly { get("/groups/new")(usersOnly {
context.withLoginAccount { loginAccount => context.withLoginAccount { loginAccount =>
html.creategroup(List(GroupMember("", loginAccount.userName, true))) html.creategroup(List(GroupMember("", loginAccount.userName, isManager = true)))
} }
}) })
@@ -714,13 +692,13 @@ trait AccountControllerBase extends AccountManagementControllerBase {
} }
.toList .toList
) )
updateImage(form.groupName, form.fileId, false) updateImage(form.groupName, form.fileId, clearImage = false)
redirect(s"/${form.groupName}") redirect(s"/${form.groupName}")
}) })
get("/:groupName/_editgroup")(managersOnly { get("/:groupName/_editgroup")(managersOnly {
val groupName = params("groupName") val groupName = params("groupName")
getAccountByUserName(groupName, true).map { account => getAccountByUserName(groupName, includeRemoved = true).map { account =>
html.editgroup(account, getGroupMembers(groupName), flash.get("info")) html.editgroup(account, getGroupMembers(groupName), flash.get("info"))
} getOrElse NotFound() } getOrElse NotFound()
}) })
@@ -730,8 +708,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
// Remove from GROUP_MEMBER // Remove from GROUP_MEMBER
updateGroupMembers(groupName, Nil) updateGroupMembers(groupName, Nil)
// Disable group // Disable group
getAccountByUserName(groupName, false).foreach { account => getAccountByUserName(groupName, includeRemoved = false).foreach { account =>
updateGroup(groupName, account.description, account.url, true) updateGroup(groupName, account.description, account.url, removed = true)
} }
// // Remove repositories // // Remove repositories
// getRepositoryNamesOfUser(groupName).foreach { repositoryName => // getRepositoryNamesOfUser(groupName).foreach { repositoryName =>
@@ -754,8 +732,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
} }
.toList .toList
getAccountByUserName(groupName, true).map { account => getAccountByUserName(groupName, includeRemoved = true).map { _ =>
updateGroup(groupName, form.description, form.url, false) updateGroup(groupName, form.description, form.url, removed = false)
// Update GROUP_MEMBER // Update GROUP_MEMBER
updateGroupMembers(form.groupName, members) updateGroupMembers(form.groupName, members)
@@ -770,7 +748,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
updateImage(form.groupName, form.fileId, form.clearImage) updateImage(form.groupName, form.fileId, form.clearImage)
flash.update("info", "Account information has been updated.") flash.update("info", "Account information has been updated.")
redirect(s"/${groupName}/_editgroup") redirect(s"/$groupName/_editgroup")
} getOrElse NotFound() } getOrElse NotFound()
}) })
@@ -788,8 +766,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
* Create new repository. * Create new repository.
*/ */
post("/new", newRepositoryForm)(usersOnly { form => post("/new", newRepositoryForm)(usersOnly { form =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
if (context.settings.basicBehavior.repositoryOperation.create || loginAccount.isAdmin) { if (context.settings.basicBehavior.repositoryOperation.create || loginAccount.isAdmin) {
LockUtil.lock(s"${form.owner}/${form.name}") { LockUtil.lock(s"${form.owner}/${form.name}") {
if (getRepository(form.owner, form.name).isDefined) { if (getRepository(form.owner, form.name).isDefined) {
@@ -819,17 +796,17 @@ trait AccountControllerBase extends AccountManagementControllerBase {
}) })
get("/:owner/:repository/fork")(readableUsersOnly { repository => get("/:owner/:repository/fork")(readableUsersOnly { repository =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount => if (
if (repository.repository.options.allowFork && (context.settings.basicBehavior.repositoryOperation.fork || loginAccount.isAdmin)) { repository.repository.options.allowFork && (context.settings.basicBehavior.repositoryOperation.fork || loginAccount.isAdmin)
) {
val loginUserName = loginAccount.userName val loginUserName = loginAccount.userName
val groups = getGroupsByUserName(loginUserName) val groups = getGroupsByUserName(loginUserName)
groups match { groups match {
case _: List[String] => case _: List[String] =>
val managerPermissions = groups.map { group => val managerPermissions = groups.map { group =>
val members = getGroupMembers(group) val members = getGroupMembers(group)
context.loginAccount.exists( context.loginAccount.exists(x =>
x =>
members.exists { member => members.exists { member =>
member.userName == x.userName && member.isManager member.userName == x.userName && member.isManager
} }
@@ -839,22 +816,23 @@ trait AccountControllerBase extends AccountManagementControllerBase {
repository, repository,
(groups zip managerPermissions).sortBy(_._1) (groups zip managerPermissions).sortBy(_._1)
) )
case _ => redirect(s"/${loginUserName}") case _ => redirect(s"/$loginUserName")
} }
} else BadRequest() } else BadRequest()
} }
}) })
post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) => post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount => if (
if (repository.repository.options.allowFork && (context.settings.basicBehavior.repositoryOperation.fork || loginAccount.isAdmin)) { repository.repository.options.allowFork && (context.settings.basicBehavior.repositoryOperation.fork || loginAccount.isAdmin)
) {
val loginUserName = loginAccount.userName val loginUserName = loginAccount.userName
val accountName = form.accountName val accountName = form.accountName
if (getRepository(accountName, repository.name).isDefined) { if (getRepository(accountName, repository.name).isDefined) {
// redirect to the repository if repository already exists // redirect to the repository if repository already exists
redirect(s"/${accountName}/${repository.name}") redirect(s"/$accountName/${repository.name}")
} else if (!canCreateRepository(accountName, loginAccount)) { } else if (!canCreateRepository(accountName, loginAccount)) {
// Permission error // Permission error
Forbidden() Forbidden()
@@ -862,7 +840,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
// fork repository asynchronously // fork repository asynchronously
forkRepository(accountName, repository, loginUserName) forkRepository(accountName, repository, loginUserName)
// redirect to the repository // redirect to the repository
redirect(s"/${accountName}/${repository.name}") redirect(s"/$accountName/${repository.name}")
} }
} else Forbidden() } else Forbidden()
} }
@@ -891,9 +869,11 @@ trait AccountControllerBase extends AccountManagementControllerBase {
private def members: Constraint = new Constraint() { private def members: Constraint = new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] = { override def validate(name: String, value: String, messages: Messages): Option[String] = {
if (value.split(",").exists { if (
value.split(",").exists {
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean } _.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
}) None }
) None
else Some("Must select one manager at least.") else Some("Must select one manager at least.")
} }
} }

View File

@@ -1,10 +1,10 @@
package gitbucket.core.controller package gitbucket.core.controller
import gitbucket.core.api._ import gitbucket.core.api.*
import gitbucket.core.controller.api._ import gitbucket.core.controller.api.*
import gitbucket.core.service._ import gitbucket.core.service.*
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits.*
import gitbucket.core.util._ import gitbucket.core.util.*
import gitbucket.core.plugin.PluginRegistry import gitbucket.core.plugin.PluginRegistry
class ApiController class ApiController

View File

@@ -4,15 +4,15 @@ import java.io.{File, FileInputStream, FileOutputStream}
import gitbucket.core.api.{ApiError, JsonFormat} import gitbucket.core.api.{ApiError, JsonFormat}
import gitbucket.core.model.Account import gitbucket.core.model.Account
import gitbucket.core.service.{AccountService, RepositoryService, SystemSettingsService} import gitbucket.core.service.{AccountService, RepositoryService, SystemSettingsService}
import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.util.SyntaxSugars.*
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory.*
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits.*
import gitbucket.core.util._ import gitbucket.core.util.*
import org.json4s._ import org.json4s.*
import org.scalatra._ import org.scalatra.{MultiParams, *}
import org.scalatra.i18n._ import org.scalatra.i18n.*
import org.scalatra.json._ import org.scalatra.json.*
import org.scalatra.forms._ import org.scalatra.forms.*
import javax.servlet.http.{HttpServletRequest, HttpServletResponse} import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
import javax.servlet.{FilterChain, ServletRequest, ServletResponse} 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.api.Git
import org.eclipse.jgit.lib.ObjectId import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.revwalk.RevCommit import org.eclipse.jgit.revwalk.RevCommit
import org.eclipse.jgit.treewalk._ import org.eclipse.jgit.treewalk.*
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.json4s.Formats import org.json4s.Formats
@@ -48,11 +48,21 @@ abstract class ControllerBase
implicit val jsonFormats: Formats = gitbucket.core.api.JsonFormat.jsonFormats implicit val jsonFormats: Formats = gitbucket.core.api.JsonFormat.jsonFormats
private case class HttpException(status: Int) extends RuntimeException
before("/api/v3/*") { before("/api/v3/*") {
contentType = formats("json") contentType = formats("json")
request.setAttribute(Keys.Request.APIv3, true) 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 = { override def requestPath(uri: String, idx: Int): String = {
val path = super.requestPath(uri, idx) val path = super.requestPath(uri, idx)
if (path != "/" && path.endsWith("/")) { if (path != "/" && path.endsWith("/")) {
@@ -86,11 +96,10 @@ abstract class ControllerBase
*/ */
implicit def context: Context = { implicit def context: Context = {
contextCache.get match { contextCache.get match {
case null => { case null =>
val context = Context(loadSystemSettings(), LoginAccount, request) val context = Context(loadSystemSettings(), LoginAccount, request)
contextCache.set(context) contextCache.set(context)
context context
}
case context => context case context => context
} }
} }
@@ -130,7 +139,7 @@ abstract class ControllerBase
action(form) action(form)
} }
protected def NotFound() = protected def NotFound(): ActionResult =
if (request.hasAttribute(Keys.Request.Ajax)) { if (request.hasAttribute(Keys.Request.Ajax)) {
org.scalatra.NotFound() org.scalatra.NotFound()
} else if (request.hasAttribute(Keys.Request.APIv3)) { } 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)) { if (request.hasAttribute(Keys.Request.Ajax)) {
org.scalatra.Unauthorized() org.scalatra.Unauthorized()
} else if (request.hasAttribute(Keys.Request.APIv3)) { } else if (request.hasAttribute(Keys.Request.APIv3)) {
@@ -178,7 +187,9 @@ abstract class ControllerBase
} }
error { error {
case e => { case e: HttpException =>
ActionResult(e.status, (), Map.empty)
case e =>
logger.error(s"Catch unhandled error in request: ${request}", e) logger.error(s"Catch unhandled error in request: ${request}", e)
if (request.hasAttribute(Keys.Request.Ajax)) { if (request.hasAttribute(Keys.Request.Ajax)) {
org.scalatra.InternalServerError() org.scalatra.InternalServerError()
@@ -189,7 +200,6 @@ abstract class ControllerBase
org.scalatra.InternalServerError(gitbucket.core.html.error("Internal Server Error", Some(e))) org.scalatra.InternalServerError(gitbucket.core.html.error("Internal Server Error", Some(e)))
} }
} }
}
override def url( override def url(
path: String, path: String,
@@ -200,7 +210,7 @@ abstract class ControllerBase
withSessionId: Boolean = true withSessionId: Boolean = true
)(implicit request: HttpServletRequest, response: HttpServletResponse): String = )(implicit request: HttpServletRequest, response: HttpServletResponse): String =
if (path.startsWith("http")) path 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. * Extends scalatra-form's trim rule to eliminate CR and LF.
@@ -244,7 +254,7 @@ abstract class ControllerBase
protected def getPathObjectId(git: Git, path: String, revCommit: RevCommit): Option[ObjectId] = { protected def getPathObjectId(git: Git, path: String, revCommit: RevCommit): Option[ObjectId] = {
@scala.annotation.tailrec @scala.annotation.tailrec
def _getPathObjectId(path: String, walk: TreeWalk): Option[ObjectId] = walk.next match { def _getPathObjectId(path: String, walk: TreeWalk): Option[ObjectId] = walk.next match {
case true if (walk.getPathString == path) => Some(walk.getObjectId(0)) case true if walk.getPathString == path => Some(walk.getObjectId(0))
case true => _getPathObjectId(path, walk) case true => _getPathObjectId(path, walk)
case false => None case false => None
} }
@@ -338,18 +348,18 @@ case class Context(
loginAccount: Option[Account], loginAccount: Option[Account],
request: HttpServletRequest request: HttpServletRequest
) { ) {
val path = settings.baseUrl.getOrElse(request.getContextPath) val path: String = settings.baseUrl.getOrElse(request.getContextPath)
val currentPath = request.getRequestURI.substring(request.getContextPath.length) val currentPath: String = request.getRequestURI.substring(request.getContextPath.length)
val baseUrl = settings.baseUrl(request) val baseUrl: String = settings.baseUrl(request)
val host = new java.net.URL(baseUrl).getHost val host: String = new java.net.URL(baseUrl).getHost
val platform = request.getHeader("User-Agent") match { val platform: String = request.getHeader("User-Agent") match {
case null => null case null => null
case agent if agent.contains("Mac") => "mac" case agent if agent.contains("Mac") => "mac"
case agent if agent.contains("Linux") => "linux" case agent if agent.contains("Linux") => "linux"
case agent if agent.contains("Win") => "windows" case agent if agent.contains("Win") => "windows"
case _ => null 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 = { def withLoginAccount(f: Account => Any): Any = {
loginAccount match { loginAccount match {
@@ -423,15 +433,16 @@ trait AccountManagementControllerBase extends ControllerBase {
messages: Messages messages: Messages
): Option[String] = { ): Option[String] = {
val extraMailAddresses = params.view.filterKeys(k => k.startsWith("extraMailAddresses")) val extraMailAddresses = params.view.filterKeys(k => k.startsWith("extraMailAddresses"))
if (extraMailAddresses.exists { if (
case (k, v) => extraMailAddresses.exists { case (k, v) =>
v.contains(value) v.contains(value)
}) { }
) {
Some("These mail addresses are duplicated.") Some("These mail addresses are duplicated.")
} else { } else {
getAccountByMailAddress(value, true) getAccountByMailAddress(value, includeRemoved = true)
.collect { .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." "Mail address is already registered."
} }
} }
@@ -446,22 +457,23 @@ trait AccountManagementControllerBase extends ControllerBase {
messages: Messages messages: Messages
): Option[String] = { ): Option[String] = {
val extraMailAddresses = params.view.filterKeys(k => k.startsWith("extraMailAddresses")) val extraMailAddresses = params.view.filterKeys(k => k.startsWith("extraMailAddresses"))
if (Some(value) == params.optionValue("mailAddress") || extraMailAddresses.count { if (
case (k, v) => params.optionValue("mailAddress").contains(value) || extraMailAddresses.count { case (k, v) =>
v.contains(value) v.contains(value)
} > 1) { } > 1
) {
Some("These mail addresses are duplicated.") Some("These mail addresses are duplicated.")
} else { } else {
getAccountByMailAddress(value, true) getAccountByMailAddress(value, includeRemoved = true)
.collect { .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." "Mail address is already registered."
} }
} }
} }
} }
val allReservedNames = Set( private val allReservedNames = Set(
"git", "git",
"admin", "admin",
"upload", "upload",
@@ -480,10 +492,9 @@ trait AccountManagementControllerBase extends ControllerBase {
protected def reservedNames: Constraint = new Constraint() { protected def reservedNames: Constraint = new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] = override def validate(name: String, value: String, messages: Messages): Option[String] =
if (allReservedNames.contains(value.toLowerCase)) { if (allReservedNames.contains(value.toLowerCase)) {
Some(s"${value} is reserved") Some(s"$value is reserved")
} else { } else {
None None
} }
} }
} }

View File

@@ -2,11 +2,11 @@ package gitbucket.core.controller
import gitbucket.core.dashboard.html import gitbucket.core.dashboard.html
import gitbucket.core.model.Account import gitbucket.core.model.Account
import gitbucket.core.service._ import gitbucket.core.service.*
import gitbucket.core.util.{Keys, UsersAuthenticator} import gitbucket.core.util.UsersAuthenticator
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits.*
import gitbucket.core.service.IssuesService._ import gitbucket.core.service.IssuesService.*
import gitbucket.core.service.ActivityService._ import gitbucket.core.service.ActivityService.*
class DashboardController class DashboardController
extends DashboardControllerBase extends DashboardControllerBase
@@ -28,12 +28,8 @@ class DashboardController
with RequestCache with RequestCache
trait DashboardControllerBase extends ControllerBase { trait DashboardControllerBase extends ControllerBase {
self: IssuesService self: IssuesService & PullRequestService & RepositoryService & AccountService & CommitStatusService &
with PullRequestService UsersAuthenticator =>
with RepositoryService
with AccountService
with CommitStatusService
with UsersAuthenticator =>
get("/dashboard/repos")(usersOnly { get("/dashboard/repos")(usersOnly {
context.withLoginAccount { loginAccount => 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) val condition = IssueSearchCondition(request)
filter match { filter match {
@@ -106,19 +102,19 @@ trait DashboardControllerBase extends ControllerBase {
} }
private def searchIssues(loginAccount: Account, filter: String) = { private def searchIssues(loginAccount: Account, filter: String) = {
import IssuesService._ import IssuesService.*
val userName = loginAccount.userName val userName = loginAccount.userName
val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName) val condition = getOrCreateCondition(filter, userName)
val userRepos = getUserRepositories(userName, true).map(repo => repo.owner -> repo.name) val userRepos = getUserRepositories(userName, withoutPhysicalInfo = true).map(repo => repo.owner -> repo.name)
val page = IssueSearchCondition.page(request) val page = IssueSearchCondition.page(request)
val issues = searchIssue(condition, IssueSearchOption.Issues, (page - 1) * IssueLimit, IssueLimit, userRepos: _*) val issues = searchIssue(condition, IssueSearchOption.Issues, (page - 1) * IssueLimit, IssueLimit, userRepos*)
html.issues( html.issues(
issues.map(issue => (issue, None)), issues.map(issue => (issue, None)),
page, page,
countIssue(condition.copy(state = "open"), IssueSearchOption.Issues, userRepos: _*), countIssue(condition.copy(state = "open"), IssueSearchOption.Issues, userRepos*),
countIssue(condition.copy(state = "closed"), IssueSearchOption.Issues, userRepos: _*), countIssue(condition.copy(state = "closed"), IssueSearchOption.Issues, userRepos*),
filter match { filter match {
case "assigned" => condition.copy(assigned = Some(Some(userName))) case "assigned" => condition.copy(assigned = Some(Some(userName)))
case "mentioned" => condition.copy(mentioned = Some(userName)) case "mentioned" => condition.copy(mentioned = Some(userName))
@@ -137,11 +133,11 @@ trait DashboardControllerBase extends ControllerBase {
} }
private def searchPullRequests(loginAccount: Account, filter: String) = { private def searchPullRequests(loginAccount: Account, filter: String) = {
import IssuesService._ import IssuesService.*
import PullRequestService._ import PullRequestService.*
val userName = loginAccount.userName val userName = loginAccount.userName
val condition = getOrCreateCondition(Keys.Session.DashboardPulls, filter, userName) val condition = getOrCreateCondition(filter, userName)
val allRepos = getAllRepositories(userName) val allRepos = getAllRepositories(userName)
val page = IssueSearchCondition.page(request) val page = IssueSearchCondition.page(request)
val issues = searchIssue( val issues = searchIssue(
@@ -149,7 +145,7 @@ trait DashboardControllerBase extends ControllerBase {
IssueSearchOption.PullRequests, IssueSearchOption.PullRequests,
(page - 1) * PullRequestLimit, (page - 1) * PullRequestLimit,
PullRequestLimit, PullRequestLimit,
allRepos: _* allRepos*
) )
val status = issues.map { issue => val status = issues.map { issue =>
issue.commitId.flatMap { commitId => issue.commitId.flatMap { commitId =>
@@ -160,8 +156,8 @@ trait DashboardControllerBase extends ControllerBase {
html.pulls( html.pulls(
issues.zip(status), issues.zip(status),
page, page,
countIssue(condition.copy(state = "open"), IssueSearchOption.PullRequests, allRepos: _*), countIssue(condition.copy(state = "open"), IssueSearchOption.PullRequests, allRepos*),
countIssue(condition.copy(state = "closed"), IssueSearchOption.PullRequests, allRepos: _*), countIssue(condition.copy(state = "closed"), IssueSearchOption.PullRequests, allRepos*),
filter match { filter match {
case "assigned" => condition.copy(assigned = Some(Some(userName))) case "assigned" => condition.copy(assigned = Some(Some(userName)))
case "mentioned" => condition.copy(mentioned = Some(userName)) case "mentioned" => condition.copy(mentioned = Some(userName))

View File

@@ -75,8 +75,7 @@ class FileUploadController
post("/wiki/:owner/:repository") { post("/wiki/:owner/:repository") {
setMultipartConfig() setMultipartConfig()
// Don't accept not logged-in users // Don't accept not logged-in users
session.get(Keys.Session.LoginAccount).collect { session.get(Keys.Session.LoginAccount).collect { case loginAccount: Account =>
case loginAccount: Account =>
val owner = params("owner") val owner = params("owner")
val repository = params("repository") val repository = params("repository")
@@ -85,9 +84,8 @@ class FileUploadController
execute( execute(
{ (file, fileId) => { (file, fileId) =>
val fileName = file.getName val fileName = file.getName
LockUtil.lock(s"${owner}/${repository}/wiki") { LockUtil.lock(s"$owner/$repository/wiki") {
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
git =>
val builder = DirCache.newInCore.builder() val builder = DirCache.newInCore.builder()
val inserter = git.getRepository.newObjectInserter() val inserter = git.getRepository.newObjectInserter()
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
@@ -110,7 +108,7 @@ class FileUploadController
) )
builder.finish() builder.finish()
val newHeadId = JGitUtil.createNewCommit( JGitUtil.createNewCommit(
git, git,
inserter, inserter,
headId, headId,
@@ -118,7 +116,7 @@ class FileUploadController
Constants.HEAD, Constants.HEAD,
loginAccount.fullName, loginAccount.fullName,
loginAccount.mailAddress, loginAccount.mailAddress,
s"Uploaded ${fileName}" s"Uploaded $fileName"
) )
fileName fileName
@@ -135,8 +133,7 @@ class FileUploadController
setMultipartConfigForLargeFile() setMultipartConfigForLargeFile()
session session
.get(Keys.Session.LoginAccount) .get(Keys.Session.LoginAccount)
.collect { .collect { case _: Account =>
case _: Account =>
val owner = params("owner") val owner = params("owner")
val repository = params("repository") val repository = params("repository")
val tag = multiParams("splat").head val tag = multiParams("splat").head
@@ -154,13 +151,16 @@ class FileUploadController
} }
post("/import") { post("/import") {
import JDBCUtil._ import JDBCUtil.*
setMultipartConfig() setMultipartConfig()
session.get(Keys.Session.LoginAccount).collect { session.get(Keys.Session.LoginAccount).collect {
case loginAccount: Account if loginAccount.isAdmin => case loginAccount: Account if loginAccount.isAdmin =>
execute({ (file, fileId) => execute(
{ (file, fileId) =>
request2Session(request).conn.importAsSQL(file.getInputStream) request2Session(request).conn.importAsSQL(file.getInputStream)
}, _ => true) },
_ => true
)
} }
redirect("/admin/data") redirect("/admin/data")
} }
@@ -168,13 +168,13 @@ class FileUploadController
private def setMultipartConfig(): Unit = { private def setMultipartConfig(): Unit = {
val settings = loadSystemSettings() val settings = loadSystemSettings()
val config = MultipartConfig(maxFileSize = Some(settings.upload.maxFileSize)) val config = MultipartConfig(maxFileSize = Some(settings.upload.maxFileSize))
config.apply(request.getServletContext()) config.apply(request.getServletContext)
} }
private def setMultipartConfigForLargeFile(): Unit = { private def setMultipartConfigForLargeFile(): Unit = {
val settings = loadSystemSettings() val settings = loadSystemSettings()
val config = MultipartConfig(maxFileSize = Some(settings.upload.largeMaxFileSize)) 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 = { 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 { fileParams.get("file") match {
case Some(file) if mimeTypeChecker(file.name) => case Some(file) if mimeTypeChecker(file.name) =>
val fileId = FileUtil.generateFileId val fileId = FileUtil.generateFileId

View File

@@ -7,14 +7,14 @@ import com.nimbusds.oauth2.sdk.id.State
import com.nimbusds.openid.connect.sdk.Nonce import com.nimbusds.openid.connect.sdk.Nonce
import gitbucket.core.helper.xml import gitbucket.core.helper.xml
import gitbucket.core.model.Account import gitbucket.core.model.Account
import gitbucket.core.service._ import gitbucket.core.service.*
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits.*
import gitbucket.core.util._ import gitbucket.core.util.*
import gitbucket.core.view.helpers._ import gitbucket.core.view.helpers.*
import org.scalatra.Ok import org.scalatra.Ok
import org.scalatra.forms._ import org.scalatra.forms.*
import gitbucket.core.service.ActivityService._ import gitbucket.core.service.ActivityService.*
class IndexController class IndexController
extends IndexControllerBase extends IndexControllerBase
@@ -34,19 +34,12 @@ class IndexController
with RequestCache with RequestCache
trait IndexControllerBase extends ControllerBase { trait IndexControllerBase extends ControllerBase {
self: RepositoryService self: RepositoryService & ActivityService & AccountService & RepositorySearchService & UsersAuthenticator &
with ActivityService ReferrerAuthenticator & AccessTokenService & AccountFederationService & OpenIDConnectService =>
with AccountService
with RepositorySearchService
with UsersAuthenticator
with ReferrerAuthenticator
with AccessTokenService
with AccountFederationService
with 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))), "userName" -> trim(label("Username", text(required))),
"password" -> trim(label("Password", text(required))), "password" -> trim(label("Password", text(required))),
"hash" -> trim(optional(text())) "hash" -> trim(optional(text()))
@@ -60,13 +53,13 @@ trait IndexControllerBase extends ControllerBase {
// //
// case class SearchForm(query: String, owner: String, repository: String) // case class SearchForm(query: String, owner: String, repository: String)
case class OidcAuthContext(state: State, nonce: Nonce, redirectBackURI: String) private case class OidcAuthContext(state: State, nonce: Nonce, redirectBackURI: String)
case class OidcSessionContext(token: JWT) private case class OidcSessionContext(token: JWT)
get("/") { get("/") {
context.loginAccount context.loginAccount
.map { account => .map { account =>
val visibleOwnerSet: Set[String] = Set(account.userName) ++ getGroupsByUserName(account.userName) // val visibleOwnerSet: Set[String] = Set(account.userName) ++ getGroupsByUserName(account.userName)
if (!isNewsFeedEnabled) { if (!isNewsFeedEnabled) {
redirect("/dashboard/repos") redirect("/dashboard/repos")
} else { } else {
@@ -100,9 +93,13 @@ trait IndexControllerBase extends ControllerBase {
} }
get("/signin") { get("/signin") {
val redirect = params.get("redirect") if (context.loginAccount.nonEmpty) {
if (redirect.isDefined && redirect.get.startsWith("/")) { redirect("/")
flash.update(Keys.Flash.Redirect, redirect.get) }
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")) gitbucket.core.html.signin(flash.get("userName"), flash.get("password"), flash.get("error"))
} }
@@ -151,8 +148,7 @@ trait IndexControllerBase extends ControllerBase {
val redirectURI = new URI(s"$baseUrl/signin/oidc") val redirectURI = new URI(s"$baseUrl/signin/oidc")
session.get(Keys.Session.OidcAuthContext) match { session.get(Keys.Session.OidcAuthContext) match {
case Some(context: OidcAuthContext) => case Some(context: OidcAuthContext) =>
authenticate(params.toMap, redirectURI, context.state, context.nonce, oidc).map { authenticate(params.toMap, redirectURI, context.state, context.nonce, oidc).map { case (jwt, account) =>
case (jwt, account) =>
session.setAttribute(Keys.Session.OidcSessionContext, OidcSessionContext(jwt)) session.setAttribute(Keys.Session.OidcSessionContext, OidcSessionContext(jwt))
signin(account, context.redirectBackURI) signin(account, context.redirectBackURI)
} orElse { } orElse {
@@ -172,8 +168,7 @@ trait IndexControllerBase extends ControllerBase {
get("/signout") { get("/signout") {
context.settings.oidc.foreach { oidc => context.settings.oidc.foreach { oidc =>
session.get(Keys.Session.OidcSessionContext).foreach { session.get(Keys.Session.OidcSessionContext).foreach { case context: OidcSessionContext =>
case context: OidcSessionContext =>
val redirectURI = new URI(baseUrl) val redirectURI = new URI(baseUrl)
val authenticationRequest = createOIDLogoutRequest(oidc.issuer, oidc.clientID, redirectURI, context.token) val authenticationRequest = createOIDLogoutRequest(oidc.issuer, oidc.clientID, redirectURI, context.token)
session.invalidate() session.invalidate()
@@ -201,6 +196,16 @@ trait IndexControllerBase extends ControllerBase {
Ok() 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. * Set account information into HttpSession and redirect.
*/ */
@@ -231,7 +236,7 @@ trait IndexControllerBase extends ControllerBase {
val group = params("group").toBoolean val group = params("group").toBoolean
org.json4s.jackson.Serialization.write( org.json4s.jackson.Serialization.write(
Map( Map(
"options" -> ( "options" ->
getAllUsers(includeRemoved = false) getAllUsers(includeRemoved = false)
.withFilter { t => .withFilter { t =>
(user, group) match { (user, group) match {
@@ -252,16 +257,26 @@ trait IndexControllerBase extends ControllerBase {
} }
) )
) )
)
}) })
/** /**
* JSON API for checking user or group existence. * JSON API for checking user or group existence.
*
* Returns a single string which is any of "group", "user" or "". * 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 { post("/_user/existence")(usersOnly {
getAccountByUserNameIgnoreCase(params("userName")).map { account => getAccountByUserNameIgnoreCase(params("userName")).map { account =>
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" if (account.isGroupAccount) "group" else "user"
}
} getOrElse "" } getOrElse ""
}) })
@@ -269,7 +284,8 @@ trait IndexControllerBase extends ControllerBase {
get("/:owner/:repository/search")(referrersOnly { repository => get("/:owner/:repository/search")(referrersOnly { repository =>
val query = params.getOrElse("q", "").trim val query = params.getOrElse("q", "").trim
val target = params.getOrElse("type", "code") val target = params.getOrElse("type", "code")
val page = try { val page =
try {
val i = params.getOrElse("page", "1").toInt val i = params.getOrElse("page", "1").toInt
if (i <= 0) 1 else i if (i <= 0) 1 else i
} catch { } catch {

View File

@@ -2,13 +2,13 @@ package gitbucket.core.controller
import gitbucket.core.issues.html import gitbucket.core.issues.html
import gitbucket.core.model.{Account, CustomFieldBehavior} import gitbucket.core.model.{Account, CustomFieldBehavior}
import gitbucket.core.service.IssuesService._ import gitbucket.core.service.IssuesService.*
import gitbucket.core.service._ import gitbucket.core.service.*
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits.*
import gitbucket.core.util._ import gitbucket.core.util.*
import gitbucket.core.view import gitbucket.core.view
import gitbucket.core.view.Markdown import gitbucket.core.view.Markdown
import org.scalatra.forms._ import org.scalatra.forms.*
import org.scalatra.{BadRequest, Ok} import org.scalatra.{BadRequest, Ok}
class IssuesController class IssuesController
@@ -34,23 +34,12 @@ class IssuesController
with RequestCache with RequestCache
trait IssuesControllerBase extends ControllerBase { trait IssuesControllerBase extends ControllerBase {
self: IssuesService self: IssuesService & RepositoryService & AccountService & LabelsService & MilestonesService & ActivityService &
with RepositoryService HandleCommentService & IssueCreationService & CustomFieldsService & ReadableUsersAuthenticator &
with AccountService ReferrerAuthenticator & WritableUsersAuthenticator & PullRequestService & WebHookIssueCommentService &
with LabelsService PrioritiesService =>
with MilestonesService
with ActivityService
with HandleCommentService
with IssueCreationService
with CustomFieldsService
with ReadableUsersAuthenticator
with ReferrerAuthenticator
with WritableUsersAuthenticator
with PullRequestService
with WebHookIssueCommentService
with PrioritiesService =>
case class IssueCreateForm( private case class IssueCreateForm(
title: String, title: String,
content: Option[String], content: Option[String],
assigneeUserNames: Option[String], assigneeUserNames: Option[String],
@@ -58,10 +47,10 @@ trait IssuesControllerBase extends ControllerBase {
priorityId: Option[Int], priorityId: Option[Int],
labelNames: Option[String] labelNames: Option[String]
) )
case class CommentForm(issueId: Int, content: String) private case class CommentForm(issueId: Int, content: String)
case class IssueStateForm(issueId: Int, content: Option[String]) private case class IssueStateForm(issueId: Int, content: Option[String])
val issueCreateForm = mapping( private val issueCreateForm = mapping(
"title" -> trim(label("Title", text(required))), "title" -> trim(label("Title", text(required))),
"content" -> trim(optional(text())), "content" -> trim(optional(text())),
"assigneeUserNames" -> trim(optional(text())), "assigneeUserNames" -> trim(optional(text())),
@@ -70,19 +59,19 @@ trait IssuesControllerBase extends ControllerBase {
"labelNames" -> trim(optional(text())) "labelNames" -> trim(optional(text()))
)(IssueCreateForm.apply) )(IssueCreateForm.apply)
val issueTitleEditForm = mapping( private val issueTitleEditForm = mapping(
"title" -> trim(label("Title", text(required))) "title" -> trim(label("Title", text(required)))
)(x => x) )(x => x)
val issueEditForm = mapping( private val issueEditForm = mapping(
"content" -> trim(optional(text())) "content" -> trim(optional(text()))
)(x => x) )(x => x)
val commentForm = mapping( private val commentForm = mapping(
"issueId" -> label("Issue Id", number()), "issueId" -> label("Issue Id", number()),
"content" -> trim(label("Comment", text(required))) "content" -> trim(label("Comment", text(required)))
)(CommentForm.apply) )(CommentForm.apply)
val issueStateForm = mapping( private val issueStateForm = mapping(
"issueId" -> label("Issue Id", number()), "issueId" -> label("Issue Id", number()),
"content" -> trim(optional(text())) "content" -> trim(optional(text()))
)(IssueStateForm.apply) )(IssueStateForm.apply)
@@ -93,7 +82,13 @@ trait IssuesControllerBase extends ControllerBase {
case Some(filter) if filter.contains("is:pr") => case Some(filter) if filter.contains("is:pr") =>
redirect(s"/${repository.owner}/${repository.name}/pulls?q=${StringUtil.urlEncode(q)}") redirect(s"/${repository.owner}/${repository.name}/pulls?q=${StringUtil.urlEncode(q)}")
case Some(filter) => case Some(filter) =>
searchIssues(repository, IssueSearchCondition(filter), IssueSearchCondition.page(request)) val condition = IssueSearchCondition(filter)
if (condition.isEmpty) {
// Redirect to keyword search
redirect(s"/${repository.owner}/${repository.name}/search?q=${StringUtil.urlEncode(q)}&type=issues")
} else {
searchIssues(repository, condition, IssueSearchCondition.page(request))
}
case None => case None =>
searchIssues(repository, IssueSearchCondition(request), IssueSearchCondition.page(request)) searchIssues(repository, IssueSearchCondition(request), IssueSearchCondition.page(request))
} }
@@ -101,10 +96,9 @@ trait IssuesControllerBase extends ControllerBase {
get("/:owner/:repository/issues/:id")(referrersOnly { repository => get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
val issueId = params("id") val issueId = params("id")
getIssue(repository.owner, repository.name, issueId) map { getIssue(repository.owner, repository.name, issueId) map { issue =>
issue =>
if (issue.isPullRequest) { if (issue.isPullRequest) {
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}") redirect(s"/${repository.owner}/${repository.name}/pull/$issueId")
} else { } else {
html.issue( html.issue(
issue, issue,
@@ -142,8 +136,7 @@ trait IssuesControllerBase extends ControllerBase {
}) })
post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) => post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator? if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator?
val issue = createIssue( val issue = createIssue(
repository, repository,
@@ -157,8 +150,7 @@ trait IssuesControllerBase extends ControllerBase {
) )
// Insert custom field values // Insert custom field values
params.toMap.foreach { params.toMap.foreach { case (key, value) =>
case (key, value) =>
if (key.startsWith("custom-field-")) { if (key.startsWith("custom-field-")) {
getCustomField( getCustomField(
repository.owner, repository.owner,
@@ -180,10 +172,8 @@ trait IssuesControllerBase extends ControllerBase {
}) })
ajaxPost("/:owner/:repository/issues/edit_title/:id", issueTitleEditForm)(readableUsersOnly { (title, repository) => ajaxPost("/:owner/:repository/issues/edit_title/:id", issueTitleEditForm)(readableUsersOnly { (title, repository) =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount => getIssue(repository.owner, repository.name, params("id")).map { issue =>
getIssue(repository.owner, repository.name, params("id")).map {
issue =>
if (isEditableContent(repository.owner, repository.name, issue.openedUserName, loginAccount)) { if (isEditableContent(repository.owner, repository.name, issue.openedUserName, loginAccount)) {
if (issue.title != title) { if (issue.title != title) {
// update issue // update issue
@@ -206,8 +196,7 @@ trait IssuesControllerBase extends ControllerBase {
}) })
ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (content, repository) => ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (content, repository) =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
getIssue(repository.owner, repository.name, params("id")).map { issue => getIssue(repository.owner, repository.name, params("id")).map { issue =>
if (isEditableContent(repository.owner, repository.name, issue.openedUserName, loginAccount)) { if (isEditableContent(repository.owner, repository.name, issue.openedUserName, loginAccount)) {
// update issue // update issue
@@ -222,17 +211,15 @@ trait IssuesControllerBase extends ControllerBase {
}) })
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) => post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue => getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
val actionOpt = val actionOpt =
params params
.get("action") .get("action")
.filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName, loginAccount)) .filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName, loginAccount))
handleComment(issue, Some(form.content), repository, actionOpt) map { handleComment(issue, Some(form.content), repository, actionOpt) map { case (issue, id) =>
case (issue, id) =>
redirect( 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() } getOrElse NotFound()
@@ -240,17 +227,15 @@ trait IssuesControllerBase extends ControllerBase {
}) })
post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) => post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue => getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
val actionOpt = val actionOpt =
params params
.get("action") .get("action")
.filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName, loginAccount)) .filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName, loginAccount))
handleComment(issue, form.content, repository, actionOpt) map { handleComment(issue, form.content, repository, actionOpt) map { case (issue, id) =>
case (issue, id) =>
redirect( 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() } getOrElse NotFound()
@@ -258,8 +243,7 @@ trait IssuesControllerBase extends ControllerBase {
}) })
ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) => ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
getComment(repository.owner, repository.name, params("id")).map { comment => getComment(repository.owner, repository.name, params("id")).map { comment =>
if (isEditableContent(repository.owner, repository.name, comment.commentedUserName, loginAccount)) { if (isEditableContent(repository.owner, repository.name, comment.commentedUserName, loginAccount)) {
updateComment(repository.owner, repository.name, comment.issueId, comment.commentId, form.content) updateComment(repository.owner, repository.name, comment.issueId, comment.commentId, form.content)
@@ -280,10 +264,8 @@ trait IssuesControllerBase extends ControllerBase {
}) })
ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository => ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount => getIssue(repository.owner, repository.name, params("id")) map { x =>
getIssue(repository.owner, repository.name, params("id")) map {
x =>
if (isEditableContent(x.userName, x.repositoryName, x.openedUserName, loginAccount)) { if (isEditableContent(x.userName, x.repositoryName, x.openedUserName, loginAccount)) {
params.get("dataType") collect { params.get("dataType") collect {
case t if t == "html" => html.editissue(x.content, x.issueId, repository) case t if t == "html" => html.editissue(x.content, x.issueId, repository)
@@ -312,10 +294,8 @@ trait IssuesControllerBase extends ControllerBase {
}) })
ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository => ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount => getComment(repository.owner, repository.name, params("id")) map { x =>
getComment(repository.owner, repository.name, params("id")) map {
x =>
if (isEditableContent(x.userName, x.repositoryName, x.commentedUserName, loginAccount)) { if (isEditableContent(x.userName, x.repositoryName, x.commentedUserName, loginAccount)) {
params.get("dataType") collect { params.get("dataType") collect {
case t if t == "html" => html.editcomment(x.content, x.commentId, repository) case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
@@ -350,35 +330,40 @@ trait IssuesControllerBase extends ControllerBase {
ajaxPost("/:owner/:repository/issues/:id/label/new")(writableUsersOnly { repository => ajaxPost("/:owner/:repository/issues/:id/label/new")(writableUsersOnly { repository =>
val issueId = params("id").toInt 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)) html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
}) })
ajaxPost("/:owner/:repository/issues/:id/label/delete")(writableUsersOnly { repository => ajaxPost("/:owner/:repository/issues/:id/label/delete")(writableUsersOnly { repository =>
val issueId = params("id").toInt 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)) html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
}) })
ajaxPost("/:owner/:repository/issues/:id/assignee/new")(writableUsersOnly { repository => ajaxPost("/:owner/:repository/issues/:id/assignee/new")(writableUsersOnly { repository =>
val issueId = params("id").toInt 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() Ok()
}) })
ajaxPost("/:owner/:repository/issues/:id/assignee/delete")(writableUsersOnly { repository => ajaxPost("/:owner/:repository/issues/:id/assignee/delete")(writableUsersOnly { repository =>
val issueId = params("id").toInt 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() Ok()
}) })
ajaxPost("/:owner/:repository/issues/:id/milestone")(writableUsersOnly { repository => 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 => milestoneId("milestoneId").map { milestoneId =>
getMilestonesWithIssueCount(repository.owner, repository.name) getMilestonesWithIssueCount(repository.owner, repository.name)
.find(_._1.milestoneId == milestoneId) .find(_._1.milestoneId == milestoneId)
.map { .map { case (_, openCount, closeCount) =>
case (_, openCount, closeCount) =>
gitbucket.core.issues.milestones.html.progress(openCount + closeCount, closeCount) gitbucket.core.issues.milestones.html.progress(openCount + closeCount, closeCount)
} getOrElse NotFound() } getOrElse NotFound()
} getOrElse Ok() } getOrElse Ok()
@@ -386,7 +371,7 @@ trait IssuesControllerBase extends ControllerBase {
ajaxPost("/:owner/:repository/issues/:id/priority")(writableUsersOnly { repository => ajaxPost("/:owner/:repository/issues/:id/priority")(writableUsersOnly { repository =>
val priority = priorityId("priorityId") 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") Ok("updated")
}) })
@@ -448,7 +433,7 @@ trait IssuesControllerBase extends ControllerBase {
params("value").toIntOpt.map { labelId => params("value").toIntOpt.map { labelId =>
executeBatch(repository) { issueId => executeBatch(repository) { issueId =>
getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse { 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) { if (params("uri").nonEmpty) {
redirect(params("uri")) redirect(params("uri"))
} }
@@ -460,12 +445,12 @@ trait IssuesControllerBase extends ControllerBase {
post("/:owner/:repository/issues/batchedit/assign")(writableUsersOnly { repository => post("/:owner/:repository/issues/batchedit/assign")(writableUsersOnly { repository =>
val value = assignedUserName("value") val value = assignedUserName("value")
executeBatch(repository) { executeBatch(repository) {
//updateAssignedUserName(repository.owner, repository.name, _, value, true) // updateAssignedUserName(repository.owner, repository.name, _, value, true)
value match { value match {
case Some(assignedUserName) => case Some(assignedUserName) =>
registerIssueAssignee(repository.owner, repository.name, _, assignedUserName, true) registerIssueAssignee(repository.owner, repository.name, _, assignedUserName, insertComment = true)
case None => case None =>
deleteAllIssueAssignees(repository.owner, repository.name, _, true) deleteAllIssueAssignees(repository.owner, repository.name, _, insertComment = true)
} }
} }
if (params("uri").nonEmpty) { if (params("uri").nonEmpty) {
@@ -476,20 +461,20 @@ trait IssuesControllerBase extends ControllerBase {
post("/:owner/:repository/issues/batchedit/milestone")(writableUsersOnly { repository => post("/:owner/:repository/issues/batchedit/milestone")(writableUsersOnly { repository =>
val value = milestoneId("value") val value = milestoneId("value")
executeBatch(repository) { 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 => post("/:owner/:repository/issues/batchedit/priority")(writableUsersOnly { repository =>
val value = priorityId("value") val value = priorityId("value")
executeBatch(repository) { executeBatch(repository) {
updatePriorityId(repository.owner, repository.name, _, value, true) updatePriorityId(repository.owner, repository.name, _, value, insertComment = true)
} }
}) })
get("/:owner/:repository/_attached/:file")(referrersOnly { repository => get("/:owner/:repository/_attached/:file")(referrersOnly { repository =>
(Directory.getAttachedDir(repository.owner, repository.name) match { (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 => dir.listFiles.find(_.getName.startsWith(params("file") + ".")).map { file =>
response.setHeader("Content-Disposition", f"""inline; filename=${file.getName}""") response.setHeader("Content-Disposition", f"""inline; filename=${file.getName}""")
RawData(FileUtil.getSafeMimeType(file.getName), file) RawData(FileUtil.getSafeMimeType(file.getName), file)
@@ -505,7 +490,7 @@ trait IssuesControllerBase extends ControllerBase {
contentType = formats("json") contentType = formats("json")
org.json4s.jackson.Serialization.write( org.json4s.jackson.Serialization.write(
Map( Map(
"options" -> ( "options" ->
getOpenIssues(repository.owner, repository.name) getOpenIssues(repository.owner, repository.name)
.map { t => .map { t =>
Map( Map(
@@ -518,14 +503,13 @@ trait IssuesControllerBase extends ControllerBase {
} }
) )
) )
)
}) })
val assignedUserName = (key: String) => params.get(key) filter (_.trim != "") private val assignedUserName = (key: String) => params.get(key) filter (_.trim != "")
val milestoneId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt) private 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 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("checked").split(',') map (_.toInt) foreach execute
params("from") match { params("from") match {
case "issues" => redirect(s"/${repository.owner}/${repository.name}/issues") case "issues" => redirect(s"/${repository.owner}/${repository.name}/issues")
@@ -565,8 +549,8 @@ trait IssuesControllerBase extends ControllerBase {
/** /**
* Tests whether an issue or a comment is editable by a logged-in user. * Tests whether an issue or a comment is editable by a logged-in user.
*/ */
private def isEditableContent(owner: String, repository: String, author: String, loginAccount: Account)( private def isEditableContent(owner: String, repository: String, author: String, loginAccount: Account)(implicit
implicit context: Context context: Context
): Boolean = { ): Boolean = {
hasDeveloperRole(owner, repository, context.loginAccount) || author == loginAccount.userName hasDeveloperRole(owner, repository, context.loginAccount) || author == loginAccount.userName
} }
@@ -574,8 +558,8 @@ trait IssuesControllerBase extends ControllerBase {
/** /**
* Tests whether an issue comment is deletable by a logged-in user. * Tests whether an issue comment is deletable by a logged-in user.
*/ */
private def isDeletableComment(owner: String, repository: String, author: String, loginAccount: Account)( private def isDeletableComment(owner: String, repository: String, author: String, loginAccount: Account)(implicit
implicit context: Context context: Context
): Boolean = { ): Boolean = {
hasOwnerRole(owner, repository, context.loginAccount) || author == loginAccount.userName hasOwnerRole(owner, repository, context.loginAccount) || author == loginAccount.userName
} }

View File

@@ -10,9 +10,9 @@ import gitbucket.core.service.{
PrioritiesService PrioritiesService
} }
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator} import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits.*
import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.util.SyntaxSugars.*
import org.scalatra.forms._ import org.scalatra.forms.*
import org.scalatra.i18n.Messages import org.scalatra.i18n.Messages
import org.scalatra.Ok import org.scalatra.Ok
@@ -28,15 +28,11 @@ class LabelsController
with WritableUsersAuthenticator with WritableUsersAuthenticator
trait LabelsControllerBase extends ControllerBase { trait LabelsControllerBase extends ControllerBase {
self: LabelsService self: LabelsService & IssuesService & RepositoryService & ReferrerAuthenticator & WritableUsersAuthenticator =>
with IssuesService
with RepositoryService
with ReferrerAuthenticator
with 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)))), "labelName" -> trim(label("Label name", text(required, labelName, uniqueLabelName, maxlength(100)))),
"labelColor" -> trim(label("Color", text(required, color))) "labelColor" -> trim(label("Color", text(required, color)))
)(LabelForm.apply) )(LabelForm.apply)
@@ -93,9 +89,9 @@ trait LabelsControllerBase extends ControllerBase {
private def labelName: Constraint = new Constraint() { private def labelName: Constraint = new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] = override def validate(name: String, value: String, messages: Messages): Option[String] =
if (value.contains(',')) { if (value.contains(',')) {
Some(s"${name} contains invalid character.") Some(s"$name contains invalid character.")
} else if (value.startsWith("_") || value.startsWith("-")) { } else if (value.startsWith("_") || value.startsWith("-")) {
Some(s"${name} starts with invalid character.") Some(s"$name starts with invalid character.")
} else { } else {
None None
} }

View File

@@ -9,11 +9,11 @@ import gitbucket.core.service.{
MilestonesService, MilestonesService,
RepositoryService RepositoryService
} }
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits.*
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator} 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 gitbucket.core.view.helpers.{getAssignableUserNames, getLabels, getPriorities, searchIssue}
import org.scalatra.forms._ import org.scalatra.forms.*
import org.scalatra.i18n.Messages import org.scalatra.i18n.Messages
class MilestonesController class MilestonesController
@@ -26,15 +26,12 @@ class MilestonesController
with WritableUsersAuthenticator with WritableUsersAuthenticator
trait MilestonesControllerBase extends ControllerBase { trait MilestonesControllerBase extends ControllerBase {
self: MilestonesService self: MilestonesService & RepositoryService & CommitStatusService & ReferrerAuthenticator &
with RepositoryService WritableUsersAuthenticator =>
with CommitStatusService
with ReferrerAuthenticator
with 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))), "title" -> trim(label("Title", text(required, maxlength(100), uniqueMilestone))),
"description" -> trim(label("Description", optional(text()))), "description" -> trim(label("Description", optional(text()))),
"dueDate" -> trim(label("Due Date", optional(date()))) "dueDate" -> trim(label("Due Date", optional(date())))

View File

@@ -30,12 +30,14 @@ trait PreProcessControllerBase extends ControllerBase {
* But if it's not allowed, demands authentication except some paths. * But if it's not allowed, demands authentication except some paths.
*/ */
get(!context.settings.basicBehavior.allowAnonymousAccess, context.loginAccount.isEmpty) { get(!context.settings.basicBehavior.allowAnonymousAccess, context.loginAccount.isEmpty) {
if (!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") && if (
!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
!context.currentPath.startsWith("/register") && !context.currentPath.endsWith("/info/refs") && !context.currentPath.startsWith("/register") && !context.currentPath.endsWith("/info/refs") &&
!context.currentPath.startsWith("/plugin-assets") && !context.currentPath.startsWith("/plugin-assets") && !context.currentPath.equals("/user.css") &&
!PluginRegistry().getAnonymousAccessiblePaths().exists { path => !PluginRegistry().getAnonymousAccessiblePaths().exists { path =>
context.currentPath.startsWith(path) context.currentPath.startsWith(path)
}) { }
) {
Unauthorized() Unauthorized()
} else { } else {
pass() pass()

View File

@@ -28,15 +28,11 @@ class PrioritiesController
with WritableUsersAuthenticator with WritableUsersAuthenticator
trait PrioritiesControllerBase extends ControllerBase { trait PrioritiesControllerBase extends ControllerBase {
self: PrioritiesService self: PrioritiesService & IssuesService & RepositoryService & ReferrerAuthenticator & WritableUsersAuthenticator =>
with IssuesService
with RepositoryService
with ReferrerAuthenticator
with 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)))), "priorityName" -> trim(label("Priority name", text(required, priorityName, uniquePriorityName, maxlength(100)))),
"description" -> trim(label("Description", optional(text(maxlength(255))))), "description" -> trim(label("Description", optional(text(maxlength(255))))),
"priorityColor" -> trim(label("Color", text(required, color))) "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( reorderPriorities(
repository.owner, repository.owner,
repository.name, repository.name,
@@ -104,7 +100,7 @@ trait PrioritiesControllerBase extends ControllerBase {
Ok() Ok()
}) })
ajaxPost("/:owner/:repository/issues/priorities/default")(writableUsersOnly { (repository) => ajaxPost("/:owner/:repository/issues/priorities/default")(writableUsersOnly { repository =>
setDefaultPriority(repository.owner, repository.name, priorityId("priorityId")) setDefaultPriority(repository.owner, repository.name, priorityId("priorityId"))
Ok() Ok()
}) })
@@ -122,9 +118,9 @@ trait PrioritiesControllerBase extends ControllerBase {
private def priorityName: Constraint = new Constraint() { private def priorityName: Constraint = new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] = override def validate(name: String, value: String, messages: Messages): Option[String] =
if (value.contains(',')) { if (value.contains(',')) {
Some(s"${name} contains invalid character.") Some(s"$name contains invalid character.")
} else if (value.startsWith("_") || value.startsWith("-")) { } else if (value.startsWith("_") || value.startsWith("-")) {
Some(s"${name} starts with invalid character.") Some(s"$name starts with invalid character.")
} else { } else {
None None
} }

View File

@@ -4,17 +4,19 @@ import gitbucket.core.model.activity.DeleteBranchInfo
import gitbucket.core.pulls.html import gitbucket.core.pulls.html
import gitbucket.core.service.CommitStatusService import gitbucket.core.service.CommitStatusService
import gitbucket.core.service.MergeService import gitbucket.core.service.MergeService
import gitbucket.core.service.IssuesService._ import gitbucket.core.service.IssuesService.*
import gitbucket.core.service.PullRequestService._ import gitbucket.core.service.PullRequestService.*
import gitbucket.core.service.RepositoryService.RepositoryInfo import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.service._ import gitbucket.core.service.*
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory.*
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits.*
import gitbucket.core.util._ import gitbucket.core.util.*
import org.scalatra.forms._ import org.scalatra.forms.*
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.revwalk.RevWalk
import org.scalatra.BadRequest import org.scalatra.BadRequest
import java.nio.file.Files
import scala.util.Using import scala.util.Using
class PullRequestsController class PullRequestsController
@@ -40,32 +42,19 @@ class PullRequestsController
with RequestCache with RequestCache
trait PullRequestsControllerBase extends ControllerBase { trait PullRequestsControllerBase extends ControllerBase {
self: RepositoryService self: RepositoryService & AccountService & IssuesService & MilestonesService & LabelsService & CustomFieldsService &
with AccountService CommitsService & ActivityService & PullRequestService & WebHookPullRequestService & ReadableUsersAuthenticator &
with IssuesService ReferrerAuthenticator & WritableUsersAuthenticator & CommitStatusService & MergeService & ProtectedBranchService &
with MilestonesService PrioritiesService =>
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 =>
val pullRequestForm = mapping( private val pullRequestForm = mapping(
"title" -> trim(label("Title", text(required))), "title" -> trim(label("Title", text(required))),
"content" -> trim(label("Content", optional(text()))), "content" -> trim(label("Content", optional(text()))),
"targetUserName" -> trim(text(required, maxlength(100))), "targetUserName" -> trim(text(required, maxlength(100))),
"targetBranch" -> trim(text(required, maxlength(100))), "targetBranch" -> trim(text(required, maxlength(255))),
"requestUserName" -> trim(text(required, maxlength(100))), "requestUserName" -> trim(text(required, maxlength(100))),
"requestRepositoryName" -> 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))), "commitIdFrom" -> trim(text(required, maxlength(40))),
"commitIdTo" -> trim(text(required, maxlength(40))), "commitIdTo" -> trim(text(required, maxlength(40))),
"isDraft" -> trim(boolean(required)), "isDraft" -> trim(boolean(required)),
@@ -75,13 +64,13 @@ trait PullRequestsControllerBase extends ControllerBase {
"labelNames" -> trim(optional(text())) "labelNames" -> trim(optional(text()))
)(PullRequestForm.apply) )(PullRequestForm.apply)
val mergeForm = mapping( private val mergeForm = mapping(
"message" -> trim(label("Message", text(required))), "message" -> trim(label("Message", text(required))),
"strategy" -> trim(label("Strategy", text(required))), "strategy" -> trim(label("Strategy", text(required))),
"isDraft" -> trim(boolean(required)) "isDraft" -> trim(boolean(required))
)(MergeForm.apply) )(MergeForm.apply)
case class PullRequestForm( private case class PullRequestForm(
title: String, title: String,
content: Option[String], content: Option[String],
targetUserName: String, targetUserName: String,
@@ -98,7 +87,7 @@ trait PullRequestsControllerBase extends ControllerBase {
labelNames: Option[String] 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 => get("/:owner/:repository/pulls")(referrersOnly { repository =>
val q = request.getParameter("q") val q = request.getParameter("q")
@@ -106,17 +95,21 @@ trait PullRequestsControllerBase extends ControllerBase {
case Some(filter) if filter.contains("is:issue") => case Some(filter) if filter.contains("is:issue") =>
redirect(s"/${repository.owner}/${repository.name}/issues?q=${StringUtil.urlEncode(q)}") redirect(s"/${repository.owner}/${repository.name}/issues?q=${StringUtil.urlEncode(q)}")
case Some(filter) => case Some(filter) =>
val condition = IssueSearchCondition(filter)
if (condition.isEmpty) {
// Redirect to keyword search
redirect(s"/${repository.owner}/${repository.name}/search?q=${StringUtil.urlEncode(q)}&type=pulls")
} else {
searchPullRequests(repository, IssueSearchCondition(filter), IssueSearchCondition.page(request)) searchPullRequests(repository, IssueSearchCondition(filter), IssueSearchCondition.page(request))
}
case None => case None =>
searchPullRequests(repository, IssueSearchCondition(request), IssueSearchCondition.page(request)) searchPullRequests(repository, IssueSearchCondition(request), IssueSearchCondition.page(request))
} }
}) })
get("/:owner/:repository/pull/:id")(referrersOnly { repository => get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
params("id").toIntOpt.flatMap { params("id").toIntOpt.flatMap { issueId =>
issueId => getPullRequest(repository.owner, repository.name, issueId) map { case (issue, pullreq) =>
getPullRequest(repository.owner, repository.name, issueId) map {
case (issue, pullreq) =>
val (commits, diffs) = val (commits, diffs) =
getRequestCompareInfo( getRequestCompareInfo(
repository.owner, repository.owner,
@@ -124,7 +117,8 @@ trait PullRequestsControllerBase extends ControllerBase {
pullreq.commitIdFrom, pullreq.commitIdFrom,
repository.owner, repository.owner,
repository.name, repository.name,
pullreq.commitIdTo pullreq.commitIdTo,
context.settings
) )
html.conversation( html.conversation(
@@ -152,10 +146,8 @@ trait PullRequestsControllerBase extends ControllerBase {
}) })
get("/:owner/:repository/pull/:id/commits")(referrersOnly { repository => get("/:owner/:repository/pull/:id/commits")(referrersOnly { repository =>
params("id").toIntOpt.flatMap { params("id").toIntOpt.flatMap { issueId =>
issueId => getPullRequest(repository.owner, repository.name, issueId) map { case (issue, pullreq) =>
getPullRequest(repository.owner, repository.name, issueId) map {
case (issue, pullreq) =>
val (commits, diffs) = val (commits, diffs) =
getRequestCompareInfo( getRequestCompareInfo(
repository.owner, repository.owner,
@@ -163,7 +155,8 @@ trait PullRequestsControllerBase extends ControllerBase {
pullreq.commitIdFrom, pullreq.commitIdFrom,
repository.owner, repository.owner,
repository.name, repository.name,
pullreq.commitIdTo pullreq.commitIdTo,
context.settings
) )
val commitsWithStatus = commits.map { day => val commitsWithStatus = commits.map { day =>
@@ -186,10 +179,8 @@ trait PullRequestsControllerBase extends ControllerBase {
}) })
get("/:owner/:repository/pull/:id/files")(referrersOnly { repository => get("/:owner/:repository/pull/:id/files")(referrersOnly { repository =>
params("id").toIntOpt.flatMap { params("id").toIntOpt.flatMap { issueId =>
issueId => getPullRequest(repository.owner, repository.name, issueId) map { case (issue, pullreq) =>
getPullRequest(repository.owner, repository.name, issueId) map {
case (issue, pullreq) =>
val (commits, diffs) = val (commits, diffs) =
getRequestCompareInfo( getRequestCompareInfo(
repository.owner, repository.owner,
@@ -197,7 +188,8 @@ trait PullRequestsControllerBase extends ControllerBase {
pullreq.commitIdFrom, pullreq.commitIdFrom,
repository.owner, repository.owner,
repository.name, repository.name,
pullreq.commitIdTo pullreq.commitIdTo,
context.settings
) )
html.files( html.files(
@@ -214,10 +206,8 @@ trait PullRequestsControllerBase extends ControllerBase {
}) })
ajaxGet("/:owner/:repository/pull/:id/mergeguide")(referrersOnly { repository => ajaxGet("/:owner/:repository/pull/:id/mergeguide")(referrersOnly { repository =>
params("id").toIntOpt.flatMap { params("id").toIntOpt.flatMap { issueId =>
issueId => getPullRequest(repository.owner, repository.name, issueId) map { case (issue, pullreq) =>
getPullRequest(repository.owner, repository.name, issueId) map {
case (issue, pullreq) =>
val conflictMessage = LockUtil.lock(s"${repository.owner}/${repository.name}") { val conflictMessage = LockUtil.lock(s"${repository.owner}/${repository.name}") {
checkConflict(repository.owner, repository.name, pullreq.branch, issueId) checkConflict(repository.owner, repository.name, pullreq.branch, issueId)
} }
@@ -227,9 +217,8 @@ trait PullRequestsControllerBase extends ControllerBase {
conflictMessage = conflictMessage, conflictMessage = conflictMessage,
commitStatuses = getCommitStatuses(repository.owner, repository.name, pullreq.commitIdTo), commitStatuses = getCommitStatuses(repository.owner, repository.name, pullreq.commitIdTo),
branchProtection = branchProtection, branchProtection = branchProtection,
branchIsOutOfDate = JGitUtil.getShaByRef(repository.owner, repository.name, pullreq.branch) != Some( branchIsOutOfDate =
pullreq.commitIdFrom !JGitUtil.getShaByRef(repository.owner, repository.name, pullreq.branch).contains(pullreq.commitIdFrom),
),
needStatusCheck = context.loginAccount.forall { u => needStatusCheck = context.loginAccount.forall { u =>
branchProtection.needStatusCheck(u.userName) branchProtection.needStatusCheck(u.userName)
}, },
@@ -260,10 +249,10 @@ trait PullRequestsControllerBase extends ControllerBase {
}) })
get("/:owner/:repository/pull/:id/delete_branch")(readableUsersOnly { baseRepository => get("/:owner/:repository/pull/:id/delete_branch")(readableUsersOnly { baseRepository =>
context.withLoginAccount { _ =>
(for { (for {
issueId <- params("id").toIntOpt issueId <- params("id").toIntOpt
loginAccount <- context.loginAccount case (issue, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId)
(issue, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId)
owner = pullreq.requestUserName owner = pullreq.requestUserName
name = pullreq.requestRepositoryName name = pullreq.requestRepositoryName
if hasDeveloperRole(owner, name, context.loginAccount) if hasDeveloperRole(owner, name, context.loginAccount)
@@ -277,7 +266,8 @@ trait PullRequestsControllerBase extends ControllerBase {
val userName = context.loginAccount.get.userName val userName = context.loginAccount.get.userName
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
git.branchDelete().setForce(true).setBranchNames(pullreq.requestBranch).call() git.branchDelete().setForce(true).setBranchNames(pullreq.requestBranch).call()
val deleteBranchInfo = DeleteBranchInfo(repository.owner, repository.name, userName, pullreq.requestBranch) val deleteBranchInfo =
DeleteBranchInfo(repository.owner, repository.name, userName, pullreq.requestBranch)
recordActivity(deleteBranchInfo) recordActivity(deleteBranchInfo)
} }
createComment( createComment(
@@ -295,13 +285,14 @@ trait PullRequestsControllerBase extends ControllerBase {
redirect(s"/${baseRepository.owner}/${baseRepository.name}/pull/${issueId}") redirect(s"/${baseRepository.owner}/${baseRepository.name}/pull/${issueId}")
}) getOrElse NotFound() }) getOrElse NotFound()
}
}) })
post("/:owner/:repository/pull/:id/update_branch")(readableUsersOnly { baseRepository => post("/:owner/:repository/pull/:id/update_branch")(readableUsersOnly { baseRepository =>
(for { (for {
issueId <- params("id").toIntOpt issueId <- params("id").toIntOpt
loginAccount <- context.loginAccount 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) repository <- getRepository(pullreq.requestUserName, pullreq.requestRepositoryName)
remoteRepository <- getRepository(pullreq.userName, pullreq.repositoryName) remoteRepository <- getRepository(pullreq.userName, pullreq.repositoryName)
owner = pullreq.requestUserName owner = pullreq.requestUserName
@@ -312,34 +303,36 @@ trait PullRequestsControllerBase extends ControllerBase {
if (branchProtection.needStatusCheck(loginAccount.userName)) { if (branchProtection.needStatusCheck(loginAccount.userName)) {
flash.update("error", s"branch ${pullreq.requestBranch} is protected need status check.") flash.update("error", s"branch ${pullreq.requestBranch} is protected need status check.")
} else { } else {
LockUtil.lock(s"${owner}/${name}") { LockUtil.lock(s"$owner/$name") {
val alias = val alias =
if (pullreq.repositoryName == pullreq.requestRepositoryName && pullreq.userName == pullreq.requestUserName) { if (
pullreq.repositoryName == pullreq.requestRepositoryName && pullreq.userName == pullreq.requestUserName
) {
pullreq.branch pullreq.branch
} else { } else {
s"${pullreq.userName}:${pullreq.branch}" s"${pullreq.userName}:${pullreq.branch}"
} }
val existIds = Using // val existIds = Using
.resource(Git.open(Directory.getRepositoryDir(owner, name))) { git => // .resource(Git.open(Directory.getRepositoryDir(owner, name))) { git =>
JGitUtil.getAllCommitIds(git) // JGitUtil.getAllCommitIds(git)
} // }
.toSet // .toSet
pullRemote( pullRemote(
repository, repository,
pullreq.requestBranch, pullreq.requestBranch,
remoteRepository, remoteRepository,
pullreq.branch, pullreq.branch,
loginAccount, loginAccount,
s"Merge branch '${alias}' into ${pullreq.requestBranch}", s"Merge branch '$alias' into ${pullreq.requestBranch}",
Some(pullreq), Some(pullreq),
context.settings context.settings
) match { ) match {
case None => // conflict 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) => case Some(oldId) =>
// update pull request // update pull request
updatePullRequests(owner, name, pullreq.requestBranch, loginAccount, "synchronize", context.settings) 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}")
} }
} }
} }
@@ -351,7 +344,7 @@ trait PullRequestsControllerBase extends ControllerBase {
post("/:owner/:repository/pull/:id/update_draft")(readableUsersOnly { baseRepository => post("/:owner/:repository/pull/:id/update_draft")(readableUsersOnly { baseRepository =>
(for { (for {
issueId <- params("id").toIntOpt issueId <- params("id").toIntOpt
(_, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId) case (_, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId)
owner = pullreq.requestUserName owner = pullreq.requestUserName
name = pullreq.requestRepositoryName name = pullreq.requestRepositoryName
if hasDeveloperRole(owner, name, context.loginAccount) if hasDeveloperRole(owner, name, context.loginAccount)
@@ -372,8 +365,11 @@ trait PullRequestsControllerBase extends ControllerBase {
form.isDraft, form.isDraft,
context.settings context.settings
) match { ) match {
case Right(objectId) => redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}") case Right(result) =>
case Left(message) => Some(BadRequest(message)) updateMergedCommitIds(repository.owner, repository.name, issueId, result.mergedCommitId)
redirect(s"/${repository.owner}/${repository.name}/pull/$issueId")
case Left(message) =>
Some(BadRequest(message))
} }
} getOrElse NotFound() } getOrElse NotFound()
} }
@@ -383,8 +379,7 @@ trait PullRequestsControllerBase extends ControllerBase {
val headBranch = params.get("head") val headBranch = params.get("head")
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match { (forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
case (Some(originUserName), Some(originRepositoryName)) => case (Some(originUserName), Some(originRepositoryName)) =>
getRepository(originUserName, originRepositoryName).map { getRepository(originUserName, originRepositoryName).map { originRepository =>
originRepository =>
Using.resources( Using.resources(
Git.open(getRepositoryDir(originUserName, originRepositoryName)), Git.open(getRepositoryDir(originUserName, originRepositoryName)),
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name)) Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
@@ -395,16 +390,15 @@ trait PullRequestsControllerBase extends ControllerBase {
.getOrElse(JGitUtil.getDefaultBranch(oldGit, originRepository).get._2) .getOrElse(JGitUtil.getDefaultBranch(oldGit, originRepository).get._2)
redirect( redirect(
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${originUserName}:${oldBranch}...${newBranch}" s"/${forkedRepository.owner}/${forkedRepository.name}/compare/$originUserName:$oldBranch...$newBranch"
) )
} }
} getOrElse NotFound() } getOrElse NotFound()
case _ => case _ =>
Using.resource(Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))) { git => Using.resource(Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))) { git =>
JGitUtil.getDefaultBranch(git, forkedRepository).map { JGitUtil.getDefaultBranch(git, forkedRepository).map { case (_, defaultBranch) =>
case (_, defaultBranch) =>
redirect( redirect(
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${defaultBranch}...${headBranch.getOrElse(defaultBranch)}" s"/${forkedRepository.owner}/${forkedRepository.name}/compare/$defaultBranch...${headBranch.getOrElse(defaultBranch)}"
) )
} getOrElse { } getOrElse {
redirect(s"/${forkedRepository.owner}/${forkedRepository.name}") redirect(s"/${forkedRepository.owner}/${forkedRepository.name}")
@@ -426,7 +420,7 @@ trait PullRequestsControllerBase extends ControllerBase {
getForkedRepositories(forkedRepository.owner, forkedRepository.name) getForkedRepositories(forkedRepository.owner, forkedRepository.name)
.find(_.userName == originOwner) .find(_.userName == originOwner)
.map(_.repositoryName) .map(_.repositoryName)
} else if (Some(originOwner) == forkedRepository.repository.originUserName) { } else if (forkedRepository.repository.originUserName.contains(originOwner)) {
// Original repository // Original repository
forkedRepository.repository.originRepositoryName forkedRepository.repository.originRepositoryName
} else { } else {
@@ -445,8 +439,10 @@ trait PullRequestsControllerBase extends ControllerBase {
val (originOwner, originId) = parseCompareIdentifier(origin, forkedRepository.owner) val (originOwner, originId) = parseCompareIdentifier(origin, forkedRepository.owner)
val (forkedOwner, forkedId) = parseCompareIdentifier(forked, forkedRepository.owner) val (forkedOwner, forkedId) = parseCompareIdentifier(forked, forkedRepository.owner)
(for (originRepositoryName <- getOriginRepositoryName(originOwner, forkedOwner, forkedRepository); (for (
originRepository <- getRepository(originOwner, originRepositoryName)) yield { originRepositoryName <- getOriginRepositoryName(originOwner, forkedOwner, forkedRepository);
originRepository <- getRepository(originOwner, originRepositoryName)
) yield {
val (oldId, newId) = val (oldId, newId) =
getPullRequestCommitFromTo(originRepository, forkedRepository, originId, forkedId) getPullRequestCommitFromTo(originRepository, forkedRepository, originId, forkedId)
@@ -458,7 +454,8 @@ trait PullRequestsControllerBase extends ControllerBase {
oldId.getName, oldId.getName,
forkedRepository.owner, forkedRepository.owner,
forkedRepository.name, forkedRepository.name,
newId.getName newId.getName,
context.settings
) )
val title = if (commits.flatten.length == 1) { val title = if (commits.flatten.length == 1) {
@@ -484,7 +481,9 @@ trait PullRequestsControllerBase extends ControllerBase {
(repository.userName, repository.repositoryName, repository.defaultBranch) (repository.userName, repository.repositoryName, repository.defaultBranch)
}, },
commits.flatten commits.flatten
.flatMap(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false)) .flatMap(commit =>
getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, includePullRequest = false)
)
.toList, .toList,
originId, originId,
forkedId, forkedId,
@@ -505,8 +504,8 @@ trait PullRequestsControllerBase extends ControllerBase {
case (oldId, newId) => case (oldId, newId) =>
redirect( redirect(
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/" + s"/${forkedRepository.owner}/${forkedRepository.name}/compare/" +
s"${originOwner}:${oldId.map(_ => originId).getOrElse(originRepository.repository.defaultBranch)}..." + s"$originOwner:${oldId.map(_ => originId).getOrElse(originRepository.repository.defaultBranch)}..." +
s"${forkedOwner}:${newId.map(_ => forkedId).getOrElse(forkedRepository.repository.defaultBranch)}" s"$forkedOwner:${newId.map(_ => forkedId).getOrElse(forkedRepository.repository.defaultBranch)}"
) )
} }
@@ -566,7 +565,9 @@ trait PullRequestsControllerBase extends ControllerBase {
val (originOwner, tmpOriginBranch) = parseCompareIdentifier(origin, forkedRepository.owner) val (originOwner, tmpOriginBranch) = parseCompareIdentifier(origin, forkedRepository.owner)
val (forkedOwner, tmpForkedBranch) = parseCompareIdentifier(forked, forkedRepository.owner) val (forkedOwner, tmpForkedBranch) = parseCompareIdentifier(forked, forkedRepository.owner)
(for (originRepositoryName <- if (originOwner == forkedOwner) { (for {
originRepositoryName <-
if (originOwner == forkedOwner) {
Some(forkedRepository.name) Some(forkedRepository.name)
} else { } else {
forkedRepository.repository.originRepositoryName.orElse { forkedRepository.repository.originRepositoryName.orElse {
@@ -574,13 +575,13 @@ trait PullRequestsControllerBase extends ControllerBase {
.find(_.userName == originOwner) .find(_.userName == originOwner)
.map(_.repositoryName) .map(_.repositoryName)
} }
}; }
originRepository <- getRepository(originOwner, originRepositoryName)) yield { originRepository <- getRepository(originOwner, originRepositoryName)
} yield {
Using.resources( Using.resources(
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)), Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name)) Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
) { ) { case (oldGit, newGit) =>
case (oldGit, newGit) =>
val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2 val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2
val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2 val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2
val conflict = LockUtil.lock(s"${originRepository.owner}/${originRepository.name}") { val conflict = LockUtil.lock(s"${originRepository.owner}/${originRepository.name}") {
@@ -599,8 +600,7 @@ trait PullRequestsControllerBase extends ControllerBase {
}) })
post("/:owner/:repository/pulls/new", pullRequestForm)(readableUsersOnly { (form, repository) => post("/:owner/:repository/pulls/new", pullRequestForm)(readableUsersOnly { (form, repository) =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
val manageable = isManageable(repository) val manageable = isManageable(repository)
val issueId = insertIssue( val issueId = insertIssue(
@@ -646,7 +646,7 @@ trait PullRequestsControllerBase extends ControllerBase {
} }
} }
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}") redirect(s"/${repository.owner}/${repository.name}/pull/$issueId")
} }
}) })
@@ -656,8 +656,7 @@ trait PullRequestsControllerBase extends ControllerBase {
context.loginAccount.map(x => Seq(x.mailAddress) ++ getAccountExtraMailAddresses(x.userName)).getOrElse(Nil) context.loginAccount.map(x => Seq(x.mailAddress) ++ getAccountExtraMailAddresses(x.userName)).getOrElse(Nil)
val branches = val branches =
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
git =>
JGitUtil JGitUtil
.getBranches( .getBranches(
git = git, git = git,
@@ -730,6 +729,107 @@ 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)
// TODO Call webhook ???
val tempDir = Files.createTempDirectory("jgit-revert-")
val revertCommitName =
try {
// Clone bare repository
Using.resource(
Git.cloneRepository
.setURI(getRepositoryDir(repository.owner, repository.name).getAbsolutePath)
.setDirectory(tempDir.toFile)
.setBranch(revertBranch)
.setBare(false)
.setNoCheckout(false)
.call()
) { git =>
// Get commit Ids to be reverted
val commitsToRevert = Using.resource(new RevWalk(git.getRepository)) { revWalk =>
pullreq.mergedCommitIds
.map(
_.split(",")
.map { mergedCommitId =>
revWalk.parseCommit(git.getRepository.resolve(mergedCommitId))
}
.toSeq
.reverse
)
.getOrElse(Nil)
}
// revert
var revert = git.revert
commitsToRevert.foreach { id =>
revert = revert.include(id)
}
val newCommit = revert.call()
if (newCommit != null) {
System.out.println("Reverted commit created: " + newCommit.getName)
git.push.call()
Some(newCommit.getName)
} else {
System.out.println("Revert resulted in conflicts.")
None
}
}
} finally {
FileUtil.deleteRecursively(tempDir.toFile)
}
revertCommitName match {
case Some(revertCommitName) =>
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 = baseBranch,
commitIdTo = revertCommitName,
isDraft = false,
loginAccount = loginAccount,
settings = context.settings
)
redirect(s"/${repository.owner}/${repository.name}/pull/$newIssueId")
case None =>
BadRequest("Failed to create revert commit.")
}
} catch {
case ex: Exception =>
ex.printStackTrace()
BadRequest(s"Revert failed: ${ex.getMessage}")
}
}
}) getOrElse NotFound()
}
})
/** /**
* Tests whether an logged-in user can manage pull requests. * Tests whether an logged-in user can manage pull requests.
*/ */
@@ -748,5 +848,4 @@ trait PullRequestsControllerBase extends ControllerBase {
case "DISABLE" => false case "DISABLE" => false
} }
} }
} }

View File

@@ -11,10 +11,10 @@ import gitbucket.core.service.{
RepositoryService, RepositoryService,
RequestCache RequestCache
} }
import gitbucket.core.util._ import gitbucket.core.util.*
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory.*
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits.*
import org.scalatra.forms._ import org.scalatra.forms.*
import gitbucket.core.releases.html import gitbucket.core.releases.html
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
@@ -33,20 +33,15 @@ class ReleaseController
with RequestCache with RequestCache
trait ReleaseControllerBase extends ControllerBase { trait ReleaseControllerBase extends ControllerBase {
self: RepositoryService self: RepositoryService & AccountService & ReleaseService & ReadableUsersAuthenticator & ReferrerAuthenticator &
with AccountService WritableUsersAuthenticator & ActivityService =>
with ReleaseService
with ReadableUsersAuthenticator
with ReferrerAuthenticator
with WritableUsersAuthenticator
with ActivityService =>
case class ReleaseForm( private case class ReleaseForm(
name: String, name: String,
content: Option[String] content: Option[String]
) )
val releaseForm = mapping( private val releaseForm = mapping(
"name" -> trim(text(required)), "name" -> trim(text(required)),
"content" -> trim(optional(text())) "content" -> trim(optional(text()))
)(ReleaseForm.apply) )(ReleaseForm.apply)
@@ -106,8 +101,7 @@ trait ReleaseControllerBase extends ControllerBase {
}) })
post("/:owner/:repository/releases/*/create", releaseForm)(writableUsersOnly { (form, repository) => post("/:owner/:repository/releases/*/create", releaseForm)(writableUsersOnly { (form, repository) =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
val tagName = multiParams("splat").head val tagName = multiParams("splat").head
// Insert into RELEASE // Insert into RELEASE
@@ -119,8 +113,7 @@ trait ReleaseControllerBase extends ControllerBase {
val Array(_, fileId) = name.split(":") val Array(_, fileId) = name.split(":")
(fileId, value) (fileId, value)
} }
files.foreach { files.foreach { case (fileId, fileName) =>
case (fileId, fileName) =>
val size = val size =
new File( new File(
getReleaseFilesDir(repository.owner, repository.name), getReleaseFilesDir(repository.owner, repository.name),
@@ -132,16 +125,15 @@ trait ReleaseControllerBase extends ControllerBase {
val releaseInfo = ReleaseInfo(repository.owner, repository.name, loginAccount.userName, form.name, tagName) val releaseInfo = ReleaseInfo(repository.owner, repository.name, loginAccount.userName, form.name, tagName)
recordActivity(releaseInfo) recordActivity(releaseInfo)
redirect(s"/${repository.owner}/${repository.name}/releases/${tagName}") redirect(s"/${repository.owner}/${repository.name}/releases/$tagName")
} }
}) })
get("/:owner/:repository/changelog/*...*")(writableUsersOnly { repository => get("/:owner/:repository/changelog/*...*")(writableUsersOnly { repository =>
val Seq(previousTag, currentTag) = multiParams("splat")
val previousTagId = repository.tags.collectFirst { case x if x.name == previousTag => x.commitId }.getOrElse("")
val commitLog = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => val commitLog = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
val commits = JGitUtil.getCommitLog(git, previousTagId, currentTag).reverse val Seq(previousTag, currentTag) = multiParams("splat")
val commits = JGitUtil.getCommitLog(git, previousTag, currentTag).reverse
commits commits
.map { commit => .map { commit =>
s"- ${commit.shortMessage} ${commit.id}" s"- ${commit.shortMessage} ${commit.id}"
@@ -171,13 +163,11 @@ trait ReleaseControllerBase extends ControllerBase {
}) })
post("/:owner/:repository/releases/*/edit", releaseForm)(writableUsersOnly { (form, repository) => post("/:owner/:repository/releases/*/edit", releaseForm)(writableUsersOnly { (form, repository) =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
val tagName = multiParams("splat").head val tagName = multiParams("splat").head
getRelease(repository.owner, repository.name, tagName) getRelease(repository.owner, repository.name, tagName)
.map { .map { release =>
release =>
// Update RELEASE // Update RELEASE
updateRelease(repository.owner, repository.name, tagName, form.name, form.content) updateRelease(repository.owner, repository.name, tagName, form.name, form.content)
@@ -190,8 +180,7 @@ trait ReleaseControllerBase extends ControllerBase {
val Array(_, fileId) = name.split(":") val Array(_, fileId) = name.split(":")
(fileId, value) (fileId, value)
} }
files.foreach { files.foreach { case (fileId, fileName) =>
case (fileId, fileName) =>
val size = val size =
new File( new File(
getReleaseFilesDir(repository.owner, repository.name), getReleaseFilesDir(repository.owner, repository.name),
@@ -210,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()) .getOrElse(NotFound())
} }
@@ -228,7 +217,7 @@ trait ReleaseControllerBase extends ControllerBase {
}) })
private def fetchReleases(repository: RepositoryService.RepositoryInfo, page: Int) = { private def fetchReleases(repository: RepositoryService.RepositoryInfo, page: Int) = {
import gitbucket.core.service.ReleaseService._ import gitbucket.core.service.ReleaseService.*
val (offset, limit) = ((page - 1) * ReleaseLimit, ReleaseLimit) val (offset, limit) = ((page - 1) * ReleaseLimit, ReleaseLimit)
val tagsToDisplay = repository.tags.reverse.slice(offset, offset + limit) val tagsToDisplay = repository.tags.reverse.slice(offset, offset + limit)
@@ -237,9 +226,12 @@ trait ReleaseControllerBase extends ControllerBase {
val assets = getReleaseAssetsMap(repository.owner, repository.name, releases) val assets = getReleaseAssetsMap(repository.owner, repository.name, releases)
val tagsWithReleases = tagsToDisplay.map { tag => val tagsWithReleases = tagsToDisplay.map { tag =>
(tag, releases.find(_.tag == tag.name).map { release => (
tag,
releases.find(_.tag == tag.name).map { release =>
(release, assets(release)) (release, assets(release))
}) }
)
} }
tagsWithReleases tagsWithReleases
} }

View File

@@ -4,16 +4,16 @@ import java.time.{LocalDateTime, ZoneOffset}
import java.util.Date import java.util.Date
import gitbucket.core.settings.html import gitbucket.core.settings.html
import gitbucket.core.model.{RepositoryWebHook, WebHook} import gitbucket.core.model.{RepositoryWebHook, WebHook}
import gitbucket.core.service._ import gitbucket.core.service.*
import gitbucket.core.service.WebHookService._ import gitbucket.core.service.WebHookService.*
import gitbucket.core.util._ import gitbucket.core.util.*
import gitbucket.core.util.JGitUtil._ import gitbucket.core.util.JGitUtil.*
import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.util.SyntaxSugars.*
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits.*
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory.*
import gitbucket.core.model.WebHookContentType import gitbucket.core.model.WebHookContentType
import gitbucket.core.model.activity.RenameRepositoryInfo import gitbucket.core.model.activity.RenameRepositoryInfo
import org.scalatra.forms._ import org.scalatra.forms.*
import org.scalatra.i18n.Messages import org.scalatra.i18n.Messages
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.Constants import org.eclipse.jgit.lib.Constants
@@ -37,19 +37,11 @@ class RepositorySettingsController
with RequestCache with RequestCache
trait RepositorySettingsControllerBase extends ControllerBase { trait RepositorySettingsControllerBase extends ControllerBase {
self: RepositoryService self: RepositoryService & AccountService & WebHookService & ProtectedBranchService & CommitStatusService &
with AccountService DeployKeyService & CustomFieldsService & ActivityService & OwnerAuthenticator & UsersAuthenticator =>
with WebHookService
with ProtectedBranchService
with CommitStatusService
with DeployKeyService
with CustomFieldsService
with ActivityService
with OwnerAuthenticator
with UsersAuthenticator =>
// for repository options // for repository options
case class OptionsForm( private case class OptionsForm(
description: Option[String], description: Option[String],
isPrivate: Boolean, isPrivate: Boolean,
issuesOption: String, issuesOption: String,
@@ -62,7 +54,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
safeMode: Boolean safeMode: Boolean
) )
val optionsForm = mapping( private val optionsForm = mapping(
"description" -> trim(label("Description", optional(text()))), "description" -> trim(label("Description", optional(text()))),
"isPrivate" -> trim(label("Repository Type", boolean())), "isPrivate" -> trim(label("Repository Type", boolean())),
"issuesOption" -> trim(label("Issues Option", text(required, featureOption))), "issuesOption" -> trim(label("Issues Option", text(required, featureOption))),
@@ -80,25 +72,30 @@ trait RepositorySettingsControllerBase extends ControllerBase {
} }
// for default branch // 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)))) "defaultBranch" -> trim(label("Default Branch", text(required, maxlength(100))))
)(DefaultBranchForm.apply) )(DefaultBranchForm.apply)
// for deploy key // 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)))), "title" -> trim(label("Title", text(required, maxlength(100)))),
"publicKey" -> trim2(label("Key", text(required))), // TODO duplication check in the repository? "publicKey" -> trim2(label("Key", text(required))), // TODO duplication check in the repository?
"allowWrite" -> trim(label("Key", boolean())) "allowWrite" -> trim(label("Key", boolean()))
)(DeployKeyForm.apply) )(DeployKeyForm.apply)
// for web hook url addition // 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( mapping(
"url" -> trim(label("url", text(required, webHook(update)))), "url" -> trim(label("url", text(required, webHook(update)))),
"events" -> webhookEvents, "events" -> webhookEvents,
@@ -107,23 +104,23 @@ trait RepositorySettingsControllerBase extends ControllerBase {
)((url, events, ctype, token) => WebHookForm(url, events, WebHookContentType.valueOf(ctype), token)) )((url, events, ctype, token) => WebHookForm(url, events, WebHookContentType.valueOf(ctype), token))
// for rename repository // for rename repository
case class RenameRepositoryForm(repositoryName: String) private case class RenameRepositoryForm(repositoryName: String)
val renameForm = mapping( private val renameForm = mapping(
"repositoryName" -> trim( "repositoryName" -> trim(
label("New repository name", text(required, maxlength(100), repository, renameRepositoryName)) label("New repository name", text(required, maxlength(100), repository, renameRepositoryName))
) )
)(RenameRepositoryForm.apply) )(RenameRepositoryForm.apply)
// for transfer ownership // 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))) "newOwner" -> trim(label("New owner", text(required, transferUser)))
)(TransferOwnerShipForm.apply) )(TransferOwnerShipForm.apply)
// for custom field // for custom field
case class CustomFieldForm( private case class CustomFieldForm(
fieldName: String, fieldName: String,
fieldType: String, fieldType: String,
constraints: Option[String], constraints: Option[String],
@@ -131,7 +128,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
enableForPullRequests: Boolean enableForPullRequests: Boolean
) )
val customFieldForm = mapping( private val customFieldForm = mapping(
"fieldName" -> trim(label("Field name", text(required, maxlength(100)))), "fieldName" -> trim(label("Field name", text(required, maxlength(100)))),
"fieldType" -> trim(label("Field type", text(required))), "fieldType" -> trim(label("Field type", text(required))),
"constraints" -> trim(label("Constraints", optional(text()))), "constraints" -> trim(label("Constraints", optional(text()))),
@@ -186,7 +183,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
/** Update default branch */ /** Update default branch */
post("/:owner/:repository/settings/update_default_branch", defaultBranchForm)(ownerOnly { (form, repository) => post("/:owner/:repository/settings/update_default_branch", defaultBranchForm)(ownerOnly { (form, repository) =>
if (!repository.branchList.contains(form.defaultBranch)) { if (!repository.branchList.contains(form.defaultBranch)) {
redirect(s"/${repository.owner}/${repository.name}/settings/options") redirect(s"/${repository.owner}/${repository.name}/settings/branches")
} else { } else {
saveRepositoryDefaultBranch(repository.owner, repository.name, form.defaultBranch) saveRepositoryDefaultBranch(repository.owner, repository.name, form.defaultBranch)
// Change repository HEAD // Change repository HEAD
@@ -200,13 +197,13 @@ trait RepositorySettingsControllerBase extends ControllerBase {
/** Branch protection for branch */ /** Branch protection for branch */
get("/:owner/:repository/settings/branches/*")(ownerOnly { repository => get("/:owner/:repository/settings/branches/*")(ownerOnly { repository =>
import gitbucket.core.api._ import gitbucket.core.api.*
val branch = params("splat") val branch = params("splat")
if (!repository.branchList.contains(branch)) { if (!repository.branchList.contains(branch)) {
redirect(s"/${repository.owner}/${repository.name}/settings/branches") redirect(s"/${repository.owner}/${repository.name}/settings/branches")
} else { } else {
val protection = ApiBranchProtection(getProtectedBranchInfo(repository.owner, repository.name, branch)) val protection = ApiBranchProtectionResponse(getProtectedBranchInfo(repository.owner, repository.name, branch))
val lastWeeks = getRecentStatusContexts( val lastWeeks = getRecentStatusContexts(
repository.owner, repository.owner,
repository.name, repository.name,
@@ -256,7 +253,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
ctype = WebHookContentType.FORM, ctype = WebHookContentType.FORM,
token = None token = None
) )
html.edithook(webhook, Set(WebHook.Push), repository, true) html.edithook(webhook, Set(WebHook.Push), repository, create = true)
}) })
/** /**
@@ -286,11 +283,10 @@ trait RepositorySettingsControllerBase extends ControllerBase {
Array(h.getName, h.getValue) Array(h.getName, h.getValue)
} }
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
git => import scala.concurrent.duration.*
import scala.concurrent.duration._ import scala.concurrent.*
import scala.concurrent._ import scala.jdk.CollectionConverters.*
import scala.jdk.CollectionConverters._
import scala.util.control.NonFatal import scala.util.control.NonFatal
import org.apache.http.util.EntityUtils import org.apache.http.util.EntityUtils
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
@@ -336,10 +332,10 @@ trait RepositorySettingsControllerBase extends ControllerBase {
callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload, context.settings).head callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload, context.settings).head
val toErrorMap: PartialFunction[Throwable, Map[String, String]] = { val toErrorMap: PartialFunction[Throwable, Map[String, String]] = {
case e: java.net.UnknownHostException => Map("error" -> ("Unknown host " + e.getMessage)) case e: java.net.UnknownHostException => Map("error" -> s"Unknown host ${e.getMessage}")
case e: java.lang.IllegalArgumentException => Map("error" -> ("invalid url")) case _: java.lang.IllegalArgumentException => Map("error" -> "invalid url")
case e: org.apache.http.client.ClientProtocolException => Map("error" -> ("invalid url")) case _: org.apache.http.client.ClientProtocolException => Map("error" -> "invalid url")
case NonFatal(e) => Map("error" -> (s"${e.getClass} ${e.getMessage}")) case NonFatal(e) => Map("error" -> s"${e.getClass} ${e.getMessage}")
} }
contentType = formats("json") contentType = formats("json")
@@ -348,8 +344,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
"url" -> url, "url" -> url,
"request" -> Await.result( "request" -> Await.result(
reqFuture reqFuture
.map( .map(req =>
req =>
Map( Map(
"headers" -> _headers(req.getAllHeaders), "headers" -> _headers(req.getAllHeaders),
"payload" -> json "payload" -> json
@@ -360,12 +355,11 @@ trait RepositorySettingsControllerBase extends ControllerBase {
), ),
"response" -> Await.result( "response" -> Await.result(
resFuture resFuture
.map( .map(res =>
res =>
Map( Map(
"status" -> res.getStatusLine.getStatusCode, "status" -> res.getStatusLine.getStatusCode,
"body" -> EntityUtils.toString(res.getEntity()), "body" -> EntityUtils.toString(res.getEntity),
"headers" -> _headers(res.getAllHeaders()) "headers" -> _headers(res.getAllHeaders)
) )
) )
.recover(toErrorMap), .recover(toErrorMap),
@@ -380,9 +374,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Display the web hook edit page. * Display the web hook edit page.
*/ */
get("/:owner/:repository/settings/hooks/edit")(ownerOnly { repository => get("/:owner/:repository/settings/hooks/edit")(ownerOnly { repository =>
getWebHook(repository.owner, repository.name, params("url")).map { getWebHook(repository.owner, repository.name, params("url")).map { case (webhook, events) =>
case (webhook, events) => html.edithook(webhook, events, repository, create = false)
html.edithook(webhook, events, repository, false)
} getOrElse NotFound() } getOrElse NotFound()
}) })
@@ -406,8 +399,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Rename repository. * Rename repository.
*/ */
post("/:owner/:repository/settings/rename", renameForm)(ownerOnly { (form, repository) => post("/:owner/:repository/settings/rename", renameForm)(ownerOnly { (form, repository) =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
if (context.settings.basicBehavior.repositoryOperation.rename || loginAccount.isAdmin) { if (context.settings.basicBehavior.repositoryOperation.rename || loginAccount.isAdmin) {
if (repository.name != form.repositoryName) { if (repository.name != form.repositoryName) {
// Update database and move git repository // Update database and move git repository
@@ -430,8 +422,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Transfer repository ownership. * Transfer repository ownership.
*/ */
post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) => post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
if (context.settings.basicBehavior.repositoryOperation.transfer || loginAccount.isAdmin) { if (context.settings.basicBehavior.repositoryOperation.transfer || loginAccount.isAdmin) {
// Change repository owner // Change repository owner
if (repository.owner != form.newOwner) { if (repository.owner != form.newOwner) {

View File

@@ -6,12 +6,12 @@ import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
import gitbucket.core.repo.html import gitbucket.core.repo.html
import gitbucket.core.helper import gitbucket.core.helper
import gitbucket.core.model.activity.DeleteBranchInfo import gitbucket.core.model.activity.DeleteBranchInfo
import gitbucket.core.service._ import gitbucket.core.service.*
import gitbucket.core.service.RepositoryCommitFileService.CommitFile import gitbucket.core.service.RepositoryCommitFileService.CommitFile
import gitbucket.core.util._ import gitbucket.core.util.*
import gitbucket.core.util.StringUtil._ import gitbucket.core.util.StringUtil.*
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits.*
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory.*
import gitbucket.core.model.{Account, WebHook} import gitbucket.core.model.{Account, WebHook}
import gitbucket.core.service.RepositoryService.RepositoryInfo import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.service.WebHookService.{WebHookCreatePayload, WebHookPushPayload} import gitbucket.core.service.WebHookService.{WebHookCreatePayload, WebHookPushPayload}
@@ -25,11 +25,11 @@ import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream
import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream
import org.apache.commons.compress.utils.IOUtils import org.apache.commons.compress.utils.IOUtils
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.scalatra.forms._ import org.scalatra.forms.*
import org.eclipse.jgit.api.{ArchiveCommand, Git} import org.eclipse.jgit.api.{ArchiveCommand, Git}
import org.eclipse.jgit.archive.{TgzFormat, ZipFormat} import org.eclipse.jgit.archive.{TgzFormat, ZipFormat}
import org.eclipse.jgit.errors.MissingObjectException 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, WorkingTreeOptions}
import org.eclipse.jgit.treewalk.TreeWalk.OperationType import org.eclipse.jgit.treewalk.TreeWalk.OperationType
import org.eclipse.jgit.treewalk.filter.PathFilter import org.eclipse.jgit.treewalk.filter.PathFilter
@@ -65,26 +65,15 @@ class RepositoryViewerController
* The repository viewer. * The repository viewer.
*/ */
trait RepositoryViewerControllerBase extends ControllerBase { trait RepositoryViewerControllerBase extends ControllerBase {
self: RepositoryService self: RepositoryService & RepositoryCommitFileService & AccountService & ActivityService & IssuesService &
with RepositoryCommitFileService WebHookService & CommitsService & ReadableUsersAuthenticator & ReferrerAuthenticator & WritableUsersAuthenticator &
with AccountService PullRequestService & CommitStatusService & WebHookPullRequestService & WebHookPullRequestReviewCommentService &
with ActivityService ProtectedBranchService =>
with IssuesService
with WebHookService
with CommitsService
with ReadableUsersAuthenticator
with ReferrerAuthenticator
with WritableUsersAuthenticator
with PullRequestService
with CommitStatusService
with WebHookPullRequestService
with WebHookPullRequestReviewCommentService
with ProtectedBranchService =>
ArchiveCommand.registerFormat("zip", new ZipFormat) ArchiveCommand.registerFormat("zip", new ZipFormat)
ArchiveCommand.registerFormat("tar.gz", new TgzFormat) ArchiveCommand.registerFormat("tar.gz", new TgzFormat)
case class UploadForm( private case class UploadForm(
branch: String, branch: String,
path: String, path: String,
uploadFiles: String, uploadFiles: String,
@@ -93,7 +82,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
newBranch: Boolean newBranch: Boolean
) )
case class EditorForm( private case class EditorForm(
branch: String, branch: String,
path: String, path: String,
content: String, content: String,
@@ -106,7 +95,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
newBranch: Boolean newBranch: Boolean
) )
case class DeleteForm( private case class DeleteForm(
branch: String, branch: String,
path: String, path: String,
message: Option[String], message: Option[String],
@@ -115,7 +104,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
newBranch: Boolean newBranch: Boolean
) )
case class CommentForm( private case class CommentForm(
fileName: Option[String], fileName: Option[String],
oldLineNumber: Option[Int], oldLineNumber: Option[Int],
newLineNumber: Option[Int], newLineNumber: Option[Int],
@@ -124,13 +113,13 @@ trait RepositoryViewerControllerBase extends ControllerBase {
diff: Option[String] diff: Option[String]
) )
case class TagForm( private case class TagForm(
commitId: String, commitId: String,
tagName: String, tagName: String,
message: Option[String] message: Option[String]
) )
val uploadForm = mapping( private val uploadForm = mapping(
"branch" -> trim(label("Branch", text(required))), "branch" -> trim(label("Branch", text(required))),
"path" -> trim(label("Path", text())), "path" -> trim(label("Path", text())),
"uploadFiles" -> trim(label("Upload files", text(required))), "uploadFiles" -> trim(label("Upload files", text(required))),
@@ -139,7 +128,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
"newBranch" -> trim(label("New Branch", boolean())) "newBranch" -> trim(label("New Branch", boolean()))
)(UploadForm.apply) )(UploadForm.apply)
val editorForm = mapping( private val editorForm = mapping(
"branch" -> trim(label("Branch", text(required))), "branch" -> trim(label("Branch", text(required))),
"path" -> trim(label("Path", text())), "path" -> trim(label("Path", text())),
"content" -> trim(label("Content", text(required))), "content" -> trim(label("Content", text(required))),
@@ -152,7 +141,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
"newBranch" -> trim(label("New Branch", boolean())) "newBranch" -> trim(label("New Branch", boolean()))
)(EditorForm.apply) )(EditorForm.apply)
val deleteForm = mapping( private val deleteForm = mapping(
"branch" -> trim(label("Branch", text(required))), "branch" -> trim(label("Branch", text(required))),
"path" -> trim(label("Path", text())), "path" -> trim(label("Path", text())),
"message" -> trim(label("Message", optional(text()))), "message" -> trim(label("Message", optional(text()))),
@@ -161,7 +150,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
"newBranch" -> trim(label("New Branch", boolean())) "newBranch" -> trim(label("New Branch", boolean()))
)(DeleteForm.apply) )(DeleteForm.apply)
val commentForm = mapping( private val commentForm = mapping(
"fileName" -> trim(label("Filename", optional(text()))), "fileName" -> trim(label("Filename", optional(text()))),
"oldLineNumber" -> trim(label("Old line number", optional(number()))), "oldLineNumber" -> trim(label("Old line number", optional(number()))),
"newLineNumber" -> trim(label("New line number", optional(number()))), "newLineNumber" -> trim(label("New line number", optional(number()))),
@@ -170,7 +159,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
"diff" -> optional(text()) "diff" -> optional(text())
)(CommentForm.apply) )(CommentForm.apply)
val tagForm = mapping( private val tagForm = mapping(
"commitId" -> trim(label("Commit id", text(required))), "commitId" -> trim(label("Commit id", text(required))),
"tagName" -> trim(label("Tag name", text(required))), "tagName" -> trim(label("Tag name", text(required))),
"message" -> trim(label("Message", optional(text()))) "message" -> trim(label("Message", optional(text())))
@@ -257,8 +246,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
val (branchName, path) = repository.splitPath(multiParams("splat").head) val (branchName, path) = repository.splitPath(multiParams("splat").head)
val page = params.get("page").flatMap(_.toIntOpt).getOrElse(1) val page = params.get("page").flatMap(_.toIntOpt).getOrElse(1)
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
git =>
JGitUtil.getCommitLog(git, branchName, page, 30, path) match { JGitUtil.getCommitLog(git, branchName, page, 30, path) match {
case Right((logs, hasNext)) => case Right((logs, hasNext)) =>
html.commits( html.commits(
@@ -273,8 +261,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
getCommitStatusWithSummary(repository.owner, repository.name, commit.id) getCommitStatusWithSummary(repository.owner, repository.name, commit.id)
) )
} }
.splitWith { .splitWith { case ((commit1, _, _), (commit2, _, _)) =>
case ((commit1, _, _), (commit2, _, _)) =>
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime) view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
}, },
page, page,
@@ -287,8 +274,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
}) })
get("/:owner/:repository/new/*")(writableUsersOnly { repository => get("/:owner/:repository/new/*")(writableUsersOnly { repository =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
val (branch, path) = repository.splitPath(multiParams("splat").head) val (branch, path) = repository.splitPath(multiParams("splat").head)
val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch) val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch)
.needStatusCheck(loginAccount.userName) .needStatusCheck(loginAccount.userName)
@@ -299,7 +285,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
html.editor( html.editor(
branch = branch, branch = branch,
repository = repository, repository = repository,
pathList = if (path.length == 0) Nil else path.split("/").toList, pathList = if (path.isEmpty) Nil else path.split("/").toList,
fileName = None, fileName = None,
content = JGitUtil.ContentInfo("text", None, None, Some("UTF-8")), content = JGitUtil.ContentInfo("text", None, None, Some("UTF-8")),
protectedBranch = protectedBranch, protectedBranch = protectedBranch,
@@ -310,8 +296,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
}) })
get("/:owner/:repository/upload/*")(writableUsersOnly { repository => get("/:owner/:repository/upload/*")(writableUsersOnly { repository =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
val (branch, path) = repository.splitPath(multiParams("splat").head) val (branch, path) = repository.splitPath(multiParams("splat").head)
val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch) val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch)
.needStatusCheck(loginAccount.userName) .needStatusCheck(loginAccount.userName)
@@ -320,7 +305,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
html.upload( html.upload(
branch, branch,
repository, repository,
if (path.length == 0) Nil else path.split("/").toList, if (path.isEmpty) Nil else path.split("/").toList,
protectedBranch, protectedBranch,
revCommit.name revCommit.name
) )
@@ -340,8 +325,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
message = form.message.getOrElse("Add files via upload"), message = form.message.getOrElse("Add files via upload"),
loginAccount = loginAccount, loginAccount = loginAccount,
settings = context.settings settings = context.settings
) { ) { case (git, headTip, builder, inserter) =>
case (git, headTip, builder, inserter) =>
JGitUtil.processTree(git, headTip) { (path, tree) => JGitUtil.processTree(git, headTip) { (path, tree) =>
if (!newFiles.exists(_.name.contains(path))) { if (!newFiles.exists(_.name.contains(path))) {
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
@@ -359,8 +343,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
} }
} }
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
val files = form.uploadFiles val files = form.uploadFiles
.split("\n") .split("\n")
.map { line => .map { line =>
@@ -370,7 +353,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
.toSeq .toSeq
val newFiles = files.map { file => 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) { if (form.newBranch) {
@@ -387,13 +370,13 @@ trait RepositoryViewerControllerBase extends ControllerBase {
form.message, form.message,
loginAccount 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)) case Left(error) => Forbidden(gitbucket.core.html.error(error))
} }
} else { } else {
_commit(form.branch, newFiles, loginAccount) match { _commit(form.branch, newFiles, loginAccount) match {
case Right(_) => case Right(_) =>
if (form.path.length == 0) { if (form.path.isEmpty) {
redirect(s"/${repository.owner}/${repository.name}/tree/${encodeRefName(form.branch)}") redirect(s"/${repository.owner}/${repository.name}/tree/${encodeRefName(form.branch)}")
} else { } else {
redirect( redirect(
@@ -407,19 +390,16 @@ trait RepositoryViewerControllerBase extends ControllerBase {
}) })
get("/:owner/:repository/edit/*")(writableUsersOnly { repository => get("/:owner/:repository/edit/*")(writableUsersOnly { repository =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
val (branch, path) = repository.splitPath(multiParams("splat").head) val (branch, path) = repository.splitPath(multiParams("splat").head)
val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch) val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch)
.needStatusCheck(loginAccount.userName) .needStatusCheck(loginAccount.userName)
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch)) val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
getPathObjectId(git, path, revCommit) getPathObjectId(git, path, revCommit)
.map { .map { objectId =>
objectId =>
val paths = path.split("/") val paths = path.split("/")
val info = EditorConfigUtil.getEditorConfigInfo(git, branch, path) val info = EditorConfigUtil.getEditorConfigInfo(git, branch, path)
@@ -442,8 +422,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
get("/:owner/:repository/remove/*")(writableUsersOnly { repository => get("/:owner/:repository/remove/*")(writableUsersOnly { repository =>
val (branch, path) = repository.splitPath(multiParams("splat").head) val (branch, path) = repository.splitPath(multiParams("splat").head)
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch)) val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
getPathObjectId(git, path, revCommit).map { objectId => getPathObjectId(git, path, revCommit).map { objectId =>
@@ -477,8 +456,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
).map(_._1) ).map(_._1)
} }
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
if (form.newBranch) { if (form.newBranch) {
val newBranchName = createNewBranchForPullRequest(repository, form.branch, loginAccount) val newBranchName = createNewBranchForPullRequest(repository, form.branch, loginAccount)
_commit(newBranchName, loginAccount) match { _commit(newBranchName, loginAccount) match {
@@ -493,13 +471,13 @@ trait RepositoryViewerControllerBase extends ControllerBase {
form.message, form.message,
loginAccount 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)) case Left(error) => Forbidden(gitbucket.core.html.error(error))
} }
} else { } else {
_commit(form.branch, loginAccount) match { _commit(form.branch, loginAccount) match {
case Right(_) => case Right(_) =>
if (form.path.length == 0) { if (form.path.isEmpty) {
redirect( redirect(
s"/${repository.owner}/${repository.name}/blob/${encodeRefName(form.branch)}/${urlEncode(form.newFileName)}" s"/${repository.owner}/${repository.name}/blob/${encodeRefName(form.branch)}/${urlEncode(form.newFileName)}"
) )
@@ -535,8 +513,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
).map(_._1) ).map(_._1)
} }
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
if (form.newBranch) { if (form.newBranch) {
val newBranchName = createNewBranchForPullRequest(repository, form.branch, loginAccount) val newBranchName = createNewBranchForPullRequest(repository, form.branch, loginAccount)
_commit(newBranchName, loginAccount) match { _commit(newBranchName, loginAccount) match {
@@ -551,13 +528,13 @@ trait RepositoryViewerControllerBase extends ControllerBase {
form.message, form.message,
loginAccount 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)) case Left(error) => Forbidden(gitbucket.core.html.error(error))
} }
} else { } else {
_commit(form.branch, loginAccount) match { _commit(form.branch, loginAccount) match {
case Right(_) => case Right(_) =>
if (form.path.length == 0) { if (form.path.isEmpty) {
redirect( redirect(
s"/${repository.owner}/${repository.name}/blob/${encodeRefName(form.branch)}/${urlEncode(form.newFileName)}" s"/${repository.owner}/${repository.name}/blob/${encodeRefName(form.branch)}/${urlEncode(form.newFileName)}"
) )
@@ -589,8 +566,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
).map(_._1) ).map(_._1)
} }
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
if (form.newBranch) { if (form.newBranch) {
val newBranchName = createNewBranchForPullRequest(repository, form.branch, loginAccount) val newBranchName = createNewBranchForPullRequest(repository, form.branch, loginAccount)
_commit(newBranchName, loginAccount) match { _commit(newBranchName, loginAccount) match {
@@ -605,7 +581,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
form.message, form.message,
loginAccount 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)) case Left(error) => Forbidden(gitbucket.core.html.error(error))
} }
} else { } else {
@@ -653,7 +629,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
sender, sender,
repository, repository,
owner, owner,
ref = newBranchName, ref = s"refs/heads/$newBranchName",
refType = "branch" refType = "branch"
) )
} }
@@ -710,15 +686,13 @@ trait RepositoryViewerControllerBase extends ControllerBase {
/** /**
* Displays the file content of the specified branch or commit. * 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 (id, path) = repository.splitPath(multiParams("splat").head)
val raw = params.get("raw").getOrElse("false").toBoolean val raw = params.get("raw").getOrElse("false").toBoolean
val highlighterTheme = getSyntaxHighlighterTheme() val highlighterTheme = getSyntaxHighlighterTheme()
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id)) val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
getPathObjectId(git, path, revCommit).map { getPathObjectId(git, path, revCommit).map { objectId =>
objectId =>
if (raw) { if (raw) {
// Download (This route is left for backward compatibility) // Download (This route is left for backward compatibility)
responseRawFile(git, objectId, path, repository) responseRawFile(git, objectId, path, repository)
@@ -766,8 +740,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
ajaxGet("/:owner/:repository/get-blame/*")(referrersOnly { repository => ajaxGet("/:owner/:repository/get-blame/*")(referrersOnly { repository =>
val (id, path) = repository.splitPath(multiParams("splat").head) val (id, path) = repository.splitPath(multiParams("splat").head)
contentType = formats("json") contentType = formats("json")
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
git =>
val last = git.log.add(git.getRepository.resolve(id)).addPath(path).setMaxCount(1).call.iterator.next.name val last = git.log.add(git.getRepository.resolve(id)).addPath(path).setMaxCount(1).call.iterator.next.name
Serialization.write( Serialization.write(
Map( Map(
@@ -775,8 +748,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
"id" -> id, "id" -> id,
"path" -> path, "path" -> path,
"last" -> last, "last" -> last,
"blame" -> JGitUtil.getBlame(git, id, path).map { "blame" -> JGitUtil.getBlame(git, id, path).map { blame =>
blame =>
Map( Map(
"id" -> blame.id, "id" -> blame.id,
"author" -> view.helpers.user(blame.authorName, blame.authorEmailAddress).toString, "author" -> view.helpers.user(blame.authorName, blame.authorEmailAddress).toString,
@@ -801,10 +773,17 @@ trait RepositoryViewerControllerBase extends ControllerBase {
val id = params("id") val id = params("id")
try { try {
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id)) val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
val diffs = JGitUtil.getDiffs(git, None, id, true, false) val diffs = JGitUtil.getDiffs(
git = git,
from = None,
to = id,
fetchContent = true,
makePatch = false,
maxFiles = context.settings.repositoryViewer.maxDiffFiles,
maxLines = context.settings.repositoryViewer.maxDiffLines
)
val oldCommitId = JGitUtil.getParentCommitId(git, id) val oldCommitId = JGitUtil.getParentCommitId(git, id)
html.commit( html.commit(
@@ -813,7 +792,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
JGitUtil.getBranchesOfCommit(git, revCommit.getName), JGitUtil.getBranchesOfCommit(git, revCommit.getName),
JGitUtil.getTagsOfCommit(git, revCommit.getName), JGitUtil.getTagsOfCommit(git, revCommit.getName),
getCommitStatusWithSummary(repository.owner, repository.name, 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, repository,
diffs, diffs,
oldCommitId, oldCommitId,
@@ -823,7 +802,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
) )
} }
} catch { } catch {
case e: MissingObjectException => NotFound() case _: MissingObjectException => NotFound()
} }
}) })
@@ -835,7 +814,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
diff diff
} }
} catch { } catch {
case e: MissingObjectException => NotFound() case _: MissingObjectException => NotFound()
} }
}) })
@@ -848,7 +827,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
diff diff
} }
} catch { } catch {
case e: MissingObjectException => NotFound() case _: MissingObjectException => NotFound()
} }
}) })
@@ -867,7 +846,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
form.issueId form.issueId
) )
redirect(s"/${repository.owner}/${repository.name}/commit/${id}") redirect(s"/${repository.owner}/${repository.name}/commit/$id")
} }
}) })
@@ -890,8 +869,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
}) })
ajaxPost("/:owner/:repository/commit/:id/comment/_data/new", commentForm)(readableUsersOnly { (form, repository) => ajaxPost("/:owner/:repository/commit/:id/comment/_data/new", commentForm)(readableUsersOnly { (form, repository) =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
val id = params("id") val id = params("id")
val commentId = createCommitComment( val commentId = createCommitComment(
repository, repository,
@@ -912,10 +890,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
}) })
ajaxGet("/:owner/:repository/commit_comments/_data/:id")(readableUsersOnly { repository => ajaxGet("/:owner/:repository/commit_comments/_data/:id")(readableUsersOnly { repository =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount => getCommitComment(repository.owner, repository.name, params("id")) map { x =>
getCommitComment(repository.owner, repository.name, params("id")) map {
x =>
if (isEditable(x.userName, x.repositoryName, x.commentedUserName, loginAccount)) { if (isEditable(x.userName, x.repositoryName, x.commentedUserName, loginAccount)) {
params.get("dataType") collect { params.get("dataType") collect {
case t if t == "html" => html.editcomment(x.content, x.commentId, repository) case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
@@ -943,8 +919,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
}) })
ajaxPost("/:owner/:repository/commit_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) => ajaxPost("/:owner/:repository/commit_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
getCommitComment(repository.owner, repository.name, params("id")).map { comment => getCommitComment(repository.owner, repository.name, params("id")).map { comment =>
if (isEditable(repository.owner, repository.name, comment.commentedUserName, loginAccount)) { if (isEditable(repository.owner, repository.name, comment.commentedUserName, loginAccount)) {
updateCommitComment(comment.commentId, form.content) updateCommitComment(comment.commentId, form.content)
@@ -969,8 +944,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
*/ */
get("/:owner/:repository/branches")(referrersOnly { repository => get("/:owner/:repository/branches")(referrersOnly { repository =>
val protectedBranches = getProtectedBranchList(repository.owner, repository.name).toSet val protectedBranches = getProtectedBranchList(repository.owner, repository.name).toSet
val branches = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { val branches = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
git =>
JGitUtil JGitUtil
.getBranches( .getBranches(
git = git, git = git,
@@ -978,8 +952,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
origin = repository.repository.originUserName.isEmpty origin = repository.repository.originUserName.isEmpty
) )
.sortBy(branch => (branch.mergeInfo.isEmpty, branch.commitTime)) .sortBy(branch => (branch.mergeInfo.isEmpty, branch.commitTime))
.map( .map(branch =>
branch =>
( (
branch, branch,
getPullRequestByRequestCommit( getPullRequestByRequestCommit(
@@ -1028,13 +1001,12 @@ trait RepositoryViewerControllerBase extends ControllerBase {
post("/:owner/:repository/branches")(writableUsersOnly { repository => post("/:owner/:repository/branches")(writableUsersOnly { repository =>
val newBranchName = params.getOrElse("new", halt(400)) val newBranchName = params.getOrElse("new", halt(400))
val fromBranchName = params.getOrElse("from", halt(400)) val fromBranchName = params.getOrElse("from", halt(400))
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
git =>
JGitUtil.createBranch(git, fromBranchName, newBranchName) match { JGitUtil.createBranch(git, fromBranchName, newBranchName) match {
case Right(message) => case Right(message) =>
flash.update("info", message) flash.update("info", message)
val settings = loadSystemSettings() 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) val oldCommitId = ObjectId.fromString("0" * 40)
// call push webhook // call push webhook
callWebHookOf(repository.owner, repository.name, WebHook.Push, settings) { callWebHookOf(repository.owner, repository.name, WebHook.Push, settings) {
@@ -1045,7 +1017,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
WebHookPushPayload( WebHookPushPayload(
git, git,
pusherAccount, pusherAccount,
newBranchName, s"refs/heads/$newBranchName",
repository, repository,
List(), List(),
ownerAccount, ownerAccount,
@@ -1064,7 +1036,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
sender, sender,
repository, repository,
owner, owner,
ref = newBranchName, ref = s"refs/heads/$newBranchName",
refType = "branch" refType = "branch"
) )
} }
@@ -1074,7 +1046,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
) )
case Left(message) => case Left(message) =>
flash.update("error", message) flash.update("error", message)
redirect(s"/${repository.owner}/${repository.name}/tree/${fromBranchName}") redirect(s"/${repository.owner}/${repository.name}/tree/$fromBranchName")
} }
} }
}) })
@@ -1083,8 +1055,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
* Deletes branch. * Deletes branch.
*/ */
get("/:owner/:repository/delete/*")(writableUsersOnly { repository => get("/:owner/:repository/delete/*")(writableUsersOnly { repository =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
val branchName = multiParams("splat").head val branchName = multiParams("splat").head
if (repository.repository.defaultBranch != branchName) { if (repository.repository.defaultBranch != branchName) {
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
@@ -1161,9 +1132,9 @@ trait RepositoryViewerControllerBase extends ControllerBase {
} }
}) })
case class UploadFiles(branch: String, path: String, fileIds: Map[String, String], message: String) { // case class UploadFiles(branch: String, path: String, fileIds: Map[String, String], message: String) {
lazy val isValid: Boolean = fileIds.nonEmpty // lazy val isValid: Boolean = fileIds.nonEmpty
} // }
/** /**
* Provides HTML of the file list. * Provides HTML of the file list.
@@ -1179,8 +1150,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
html.guide(repository, hasDeveloperRole(repository.owner, repository.name, context.loginAccount)) html.guide(repository, hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
} else { } else {
// get specified commit // get specified commit
JGitUtil.getDefaultBranch(git, repository, revstr).map { JGitUtil.getDefaultBranch(git, repository, revstr).map { case (objectId, revision) =>
case (objectId, revision) =>
val revCommit = JGitUtil.getRevCommitFromId(git, objectId) val revCommit = JGitUtil.getRevCommitFromId(git, objectId)
val lastModifiedCommit = val lastModifiedCommit =
if (path == ".") revCommit else JGitUtil.getLastModifiedCommit(git, revCommit, path) if (path == ".") revCommit else JGitUtil.getLastModifiedCommit(git, revCommit, path)
@@ -1204,7 +1174,11 @@ trait RepositoryViewerControllerBase extends ControllerBase {
val path = (file.name :: parentPath.reverse).reverse val path = (file.name :: parentPath.reverse).reverse
path -> StringUtil.convertFromByteArray( path -> StringUtil.convertFromByteArray(
JGitUtil 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 .get
) )
} }
@@ -1238,14 +1212,14 @@ trait RepositoryViewerControllerBase extends ControllerBase {
repository: RepositoryService.RepositoryInfo, repository: RepositoryService.RepositoryInfo,
path: String path: String
) = { ) = {
def archive(revision: String, archiveFormat: String, archive: ArchiveOutputStream)( def archive[A <: ArchiveEntry](revision: String, archiveFormat: String, archive: ArchiveOutputStream[A])(
entryCreator: (String, Long, java.util.Date, Int) => ArchiveEntry entryCreator: (String, Long, java.util.Date, Int) => A
): Unit = { ): Unit = {
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
val oid = git.getRepository.resolve(revision) val oid = git.getRepository.resolve(revision)
val commit = JGitUtil.getRevCommitFromId(git, oid) val commit = JGitUtil.getRevCommitFromId(git, oid)
val date = commit.getCommitterIdent.getWhen val date = commit.getCommitterIdent.getWhen
val sha1 = oid.getName() val sha1 = oid.getName
val repositorySuffix = (if (sha1.startsWith(revision)) sha1 else revision).replace('/', '-') val repositorySuffix = (if (sha1.startsWith(revision)) sha1 else revision).replace('/', '-')
val pathSuffix = if (path.isEmpty) "" else s"-${path.replace('/', '-')}" val pathSuffix = if (path.isEmpty) "" else s"-${path.replace('/', '-')}"
val baseName = repository.name + "-" + repositorySuffix + pathSuffix val baseName = repository.name + "-" + repositorySuffix + pathSuffix
@@ -1253,7 +1227,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
Using.resource(new TreeWalk(git.getRepository)) { treeWalk => Using.resource(new TreeWalk(git.getRepository)) { treeWalk =>
treeWalk.addTree(commit.getTree) treeWalk.addTree(commit.getTree)
treeWalk.setRecursive(true) treeWalk.setRecursive(true)
if (!path.isEmpty) { if (path.nonEmpty) {
treeWalk.setFilter(PathFilter.create(path)) treeWalk.setFilter(PathFilter.create(path))
} }
if (treeWalk != null) { if (treeWalk != null) {
@@ -1281,7 +1255,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
) )
} }
val entry: ArchiveEntry = entryCreator(entryPath, size, date, mode) val entry: A = entryCreator(entryPath, size, date, mode)
archive.putArchiveEntry(entry) archive.putArchiveEntry(entry)
Using.resource(new FileInputStream(tempFile)) { in => Using.resource(new FileInputStream(tempFile)) { in =>
IOUtils.copy(in, archive) IOUtils.copy(in, archive)
@@ -1297,7 +1271,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
} }
val suffix = 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 zipRe = """(.+)\.zip$""".r
val tarRe = """(.+)\.tar\.(gz|bz2|xz)$""".r val tarRe = """(.+)\.tar\.(gz|bz2|xz)$""".r
@@ -1305,7 +1279,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
case zipRe(revision) => case zipRe(revision) =>
response.setHeader( response.setHeader(
"Content-Disposition", "Content-Disposition",
s"attachment; filename=${repository.name}-${revision}${suffix}.zip" s"attachment; filename=${repository.name}-$revision$suffix.zip"
) )
contentType = "application/octet-stream" contentType = "application/octet-stream"
response.setBufferSize(1024 * 1024) response.setBufferSize(1024 * 1024)
@@ -1322,7 +1296,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
case tarRe(revision, compressor) => case tarRe(revision, compressor) =>
response.setHeader( response.setHeader(
"Content-Disposition", "Content-Disposition",
s"attachment; filename=${repository.name}-${revision}${suffix}.tar.${compressor}" s"attachment; filename=${repository.name}-$revision$suffix.tar.$compressor"
) )
contentType = "application/octet-stream" contentType = "application/octet-stream"
response.setBufferSize(1024 * 1024) response.setBufferSize(1024 * 1024)
@@ -1360,9 +1334,9 @@ trait RepositoryViewerControllerBase extends ControllerBase {
val repository = params("repository") val repository = params("repository")
val branch = params("branch") val branch = params("branch")
LockUtil.lock(s"${owner}/${repository}") { LockUtil.lock(s"$owner/$repository") {
Using.resource(Git.open(getRepositoryDir(owner, repository))) { git => 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) val headTip = git.getRepository.resolve(headName)
if (headTip.getName != value) { if (headTip.getName != value) {
Some("Someone pushed new commits before you. Please reload this page and re-apply your changes.") Some("Someone pushed new commits before you. Please reload this page and re-apply your changes.")

View File

@@ -3,17 +3,17 @@ package gitbucket.core.controller
import java.io.FileInputStream import java.io.FileInputStream
import gitbucket.core.admin.html import gitbucket.core.admin.html
import gitbucket.core.plugin.PluginRegistry import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service.SystemSettingsService._ import gitbucket.core.service.SystemSettingsService.*
import gitbucket.core.service.{AccountService, RepositoryService} import gitbucket.core.service.{AccountService, RepositoryService}
import gitbucket.core.ssh.SshServer import gitbucket.core.ssh.SshServer
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits.*
import gitbucket.core.util.StringUtil._ import gitbucket.core.util.StringUtil.*
import gitbucket.core.util.{AdminAuthenticator, Mailer} import gitbucket.core.util.{AdminAuthenticator, Mailer}
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
import org.apache.commons.mail.EmailException import org.apache.commons.mail.EmailException
import org.json4s.jackson.Serialization import org.json4s.jackson.Serialization
import org.scalatra._ import org.scalatra.*
import org.scalatra.forms._ import org.scalatra.forms.*
import org.scalatra.i18n.Messages import org.scalatra.i18n.Messages
import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer
@@ -29,7 +29,7 @@ case class Table(name: String, columns: Seq[Column])
case class Column(name: String, primaryKey: Boolean) case class Column(name: String, primaryKey: Boolean)
trait SystemSettingsControllerBase extends AccountManagementControllerBase { trait SystemSettingsControllerBase extends AccountManagementControllerBase {
self: AccountService with RepositoryService with AdminAuthenticator => self: AccountService & RepositoryService & AdminAuthenticator =>
private val form = mapping( private val form = mapping(
"baseUrl" -> trim(label("Base URL", optional(text()))), "baseUrl" -> trim(label("Base URL", optional(text()))),
@@ -55,15 +55,13 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
"bindAddress" -> mapping( "bindAddress" -> mapping(
"host" -> trim(label("Bind SSH host", optional(text()))), "host" -> trim(label("Bind SSH host", optional(text()))),
"port" -> trim(label("Bind SSH port", optional(number()))), "port" -> trim(label("Bind SSH port", optional(number()))),
)( )((hostOption, portOption) =>
(hostOption, portOption) =>
hostOption.map(h => SshAddress(h, portOption.getOrElse(DefaultSshPort), GenericSshUser)) hostOption.map(h => SshAddress(h, portOption.getOrElse(DefaultSshPort), GenericSshUser))
), ),
"publicAddress" -> mapping( "publicAddress" -> mapping(
"host" -> trim(label("Public SSH host", optional(text()))), "host" -> trim(label("Public SSH host", optional(text()))),
"port" -> trim(label("Public SSH port", optional(number()))), "port" -> trim(label("Public SSH port", optional(number()))),
)( )((hostOption, portOption) =>
(hostOption, portOption) =>
hostOption.map(h => SshAddress(h, portOption.getOrElse(PublicSshPort), GenericSshUser)) hostOption.map(h => SshAddress(h, portOption.getOrElse(PublicSshPort), GenericSshUser))
), ),
)(Ssh.apply), )(Ssh.apply),
@@ -123,7 +121,9 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
"largeTimeout" -> trim(label("Timeout for large file", long(required))) "largeTimeout" -> trim(label("Timeout for large file", long(required)))
)(Upload.apply), )(Upload.apply),
"repositoryViewer" -> mapping( "repositoryViewer" -> mapping(
"maxFiles" -> trim(label("Max files", number(required))) "maxFiles" -> trim(label("Max files", number(required))),
"maxDiffFiles" -> trim(label("Max diff files", number(required))),
"maxDiffLines" -> trim(label("Max diff lines", number(required)))
)(RepositoryViewerSettings.apply), )(RepositoryViewerSettings.apply),
"defaultBranch" -> trim(label("Default branch", text(required))) "defaultBranch" -> trim(label("Default branch", text(required)))
)(SystemSettings.apply).verifying { settings => )(SystemSettings.apply).verifying { settings =>
@@ -151,11 +151,11 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
"testAddress" -> trim(label("", text(required))) "testAddress" -> trim(label("", text(required)))
)(SendMailForm.apply) )(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, userName: String,
password: String, password: String,
fullName: String, fullName: String,
@@ -167,7 +167,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
fileId: Option[String] fileId: Option[String]
) )
case class EditUserForm( private case class EditUserForm(
userName: String, userName: String,
password: Option[String], password: Option[String],
fullName: String, fullName: String,
@@ -181,7 +181,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
isRemoved: Boolean isRemoved: Boolean
) )
case class NewGroupForm( private case class NewGroupForm(
groupName: String, groupName: String,
description: Option[String], description: Option[String],
url: Option[String], url: Option[String],
@@ -189,7 +189,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
members: String members: String
) )
case class EditGroupForm( private case class EditGroupForm(
groupName: String, groupName: String,
description: Option[String], description: Option[String],
url: Option[String], url: Option[String],
@@ -199,7 +199,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
isRemoved: Boolean isRemoved: Boolean
) )
val newUserForm = mapping( private val newUserForm = mapping(
"userName" -> trim(label("Username", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))), "userName" -> trim(label("Username", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
"password" -> trim(label("Password", text(required, maxlength(40)))), "password" -> trim(label("Password", text(required, maxlength(40)))),
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))), "fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
@@ -213,7 +213,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
"fileId" -> trim(label("File ID", optional(text()))) "fileId" -> trim(label("File ID", optional(text())))
)(NewUserForm.apply) )(NewUserForm.apply)
val editUserForm = mapping( private val editUserForm = mapping(
"userName" -> trim(label("Username", text(required, maxlength(100), identifier))), "userName" -> trim(label("Username", text(required, maxlength(100), identifier))),
"password" -> trim(label("Password", optional(text(maxlength(40))))), "password" -> trim(label("Password", optional(text(maxlength(40))))),
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))), "fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
@@ -229,7 +229,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
"removed" -> trim(label("Disable", boolean(disableByNotYourself("userName")))) "removed" -> trim(label("Disable", boolean(disableByNotYourself("userName"))))
)(EditUserForm.apply) )(EditUserForm.apply)
val newGroupForm = mapping( private val newGroupForm = mapping(
"groupName" -> trim(label("Group name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))), "groupName" -> trim(label("Group name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
"description" -> trim(label("Group description", optional(text()))), "description" -> trim(label("Group description", optional(text()))),
"url" -> trim(label("URL", optional(text(maxlength(200))))), "url" -> trim(label("URL", optional(text(maxlength(200))))),
@@ -237,7 +237,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
"members" -> trim(label("Members", text(required, members))) "members" -> trim(label("Members", text(required, members)))
)(NewGroupForm.apply) )(NewGroupForm.apply)
val editGroupForm = mapping( private val editGroupForm = mapping(
"groupName" -> trim(label("Group name", text(required, maxlength(100), identifier))), "groupName" -> trim(label("Group name", text(required, maxlength(100), identifier))),
"description" -> trim(label("Group description", optional(text()))), "description" -> trim(label("Group description", optional(text()))),
"url" -> trim(label("URL", optional(text(maxlength(200))))), "url" -> trim(label("URL", optional(text(maxlength(200))))),
@@ -251,8 +251,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
val conn = request2Session(request).conn val conn = request2Session(request).conn
val meta = conn.getMetaData val meta = conn.getMetaData
val tables = ListBuffer[Table]() val tables = ListBuffer[Table]()
Using.resource(meta.getTables(null, "%", "%", Array("TABLE", "VIEW"))) { Using.resource(meta.getTables(null, "%", "%", Array("TABLE", "VIEW"))) { rs =>
rs =>
while (rs.next()) { while (rs.next()) {
val tableName = rs.getString("TABLE_NAME") val tableName = rs.getString("TABLE_NAME")
@@ -285,11 +284,9 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
if (trimmedQuery.nonEmpty) { if (trimmedQuery.nonEmpty) {
try { try {
val conn = request2Session(request).conn val conn = request2Session(request).conn
Using.resource(conn.prepareStatement(query)) { Using.resource(conn.prepareStatement(query)) { stmt =>
stmt =>
if (trimmedQuery.toUpperCase.startsWith("SELECT")) { if (trimmedQuery.toUpperCase.startsWith("SELECT")) {
Using.resource(stmt.executeQuery()) { Using.resource(stmt.executeQuery()) { rs =>
rs =>
val meta = rs.getMetaData val meta = rs.getMetaData
val columns = for (i <- 1 to meta.getColumnCount) yield { val columns = for (i <- 1 to meta.getColumnCount) yield {
meta.getColumnName(i) meta.getColumnName(i)
@@ -323,7 +320,9 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
post("/admin/system", form)(adminOnly { form => post("/admin/system", form)(adminOnly { form =>
saveSystemSettings(form) saveSystemSettings(form)
if (form.ssh.bindAddress != context.settings.sshBindAddress || form.ssh.publicAddress != context.settings.sshPublicAddress) { if (
form.ssh.bindAddress != context.settings.sshBindAddress || form.ssh.publicAddress != context.settings.sshPublicAddress
) {
SshServer.stop() SshServer.stop()
for { for {
bindAddress <- form.ssh.bindAddress bindAddress <- form.ssh.bindAddress
@@ -364,7 +363,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
}) })
post("/admin/plugins/_reload")(adminOnly { 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.") flash.update("info", "All plugins were reloaded.")
redirect("/admin/plugins") redirect("/admin/plugins")
}) })
@@ -386,7 +385,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
val includeGroups = params.get("includeGroups").exists(_.toBoolean) val includeGroups = params.get("includeGroups").exists(_.toBoolean)
val users = getAllUsers(includeRemoved, includeGroups) val users = getAllUsers(includeRemoved, includeGroups)
val members = users.collect { val members = users.collect {
case account if (account.isGroupAccount) => case account if account.isGroupAccount =>
account.userName -> getGroupMembers(account.userName).map(_.userName) account.userName -> getGroupMembers(account.userName).map(_.userName)
}.toMap }.toMap
@@ -407,7 +406,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
form.description, form.description,
form.url form.url
) )
updateImage(form.userName, form.fileId, false) updateImage(form.userName, form.fileId, clearImage = false)
updateAccountExtraMailAddresses(form.userName, form.extraMailAddresses.filter(_ != "")) updateAccountExtraMailAddresses(form.userName, form.extraMailAddresses.filter(_ != ""))
redirect("/admin/users") redirect("/admin/users")
}) })
@@ -415,13 +414,12 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
get("/admin/users/:userName/_edituser")(adminOnly { get("/admin/users/:userName/_edituser")(adminOnly {
val userName = params("userName") val userName = params("userName")
val extraMails = getAccountExtraMailAddresses(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 => post("/admin/users/:name/_edituser", editUserForm)(adminOnly { form =>
val userName = params("userName") val userName = params("userName")
getAccountByUserName(userName, true).map { getAccountByUserName(userName, includeRemoved = true).map { account =>
account =>
if (account.isAdmin && (form.isRemoved || !form.isAdmin) && isLastAdministrator(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.") flash.update("error", "Account can't be turned off because this is last one administrator.")
redirect(s"/admin/users/${userName}/_edituser") redirect(s"/admin/users/${userName}/_edituser")
@@ -478,13 +476,13 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
} }
.toList .toList
) )
updateImage(form.groupName, form.fileId, false) updateImage(form.groupName, form.fileId, clearImage = false)
redirect("/admin/users") redirect("/admin/users")
}) })
get("/admin/users/:groupName/_editgroup")(adminOnly { get("/admin/users/:groupName/_editgroup")(adminOnly {
val groupName = params("groupName") 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 => post("/admin/users/:groupName/_editgroup", editGroupForm)(adminOnly { form =>
@@ -498,8 +496,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
} }
.toList .toList
getAccountByUserName(groupName, true).map { getAccountByUserName(groupName, includeRemoved = true).map { account =>
account =>
updateGroup(groupName, form.description, form.url, form.isRemoved) updateGroup(groupName, form.description, form.url, form.isRemoved)
if (form.isRemoved) { if (form.isRemoved) {
@@ -531,13 +528,13 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
}) })
get("/admin/data")(adminOnly { get("/admin/data")(adminOnly {
import gitbucket.core.util.JDBCUtil._ import gitbucket.core.util.JDBCUtil.*
val session = request2Session(request) val session = request2Session(request)
html.data(session.conn.allTableNames()) html.data(session.conn.allTableNames())
}) })
post("/admin/export")(adminOnly { post("/admin/export")(adminOnly {
import gitbucket.core.util.JDBCUtil._ import gitbucket.core.util.JDBCUtil.*
val file = request2Session(request).conn.exportAsSQL(request.getParameterValues("tableNames").toSeq) val file = request2Session(request).conn.exportAsSQL(request.getParameterValues("tableNames").toSeq)
contentType = "application/octet-stream" contentType = "application/octet-stream"
@@ -552,7 +549,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
}) })
private def multiLineText(constraints: Constraint*): SingleValueType[Seq[String]] = private def multiLineText(constraints: Constraint*): SingleValueType[Seq[String]] =
new SingleValueType[Seq[String]](constraints: _*) { new SingleValueType[Seq[String]](constraints*) {
def convert(value: String, messages: Messages): Seq[String] = { def convert(value: String, messages: Messages): Seq[String] = {
if (value == null) { if (value == null) {
Nil Nil
@@ -565,9 +562,11 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
private def members: Constraint = private def members: Constraint =
new Constraint() { new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] = { override def validate(name: String, value: String, messages: Messages): Option[String] = {
if (value.split(",").exists { if (
value.split(",").exists {
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean } _.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
}) None }
) None
else Some("Must select one manager at least.") else Some("Must select one manager at least.")
} }
} }
@@ -578,7 +577,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
for { for {
userName <- params.get(paramName) userName <- params.get(paramName)
loginAccount <- context.loginAccount 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" } yield "You can't disable your account yourself"
} }
} }

View File

@@ -1,16 +1,16 @@
package gitbucket.core.controller package gitbucket.core.controller
import org.json4s.{JField, JObject, JString} import org.json4s.{JField, JObject, JString}
import org.scalatra._ import org.scalatra.*
import org.scalatra.json._ import org.scalatra.json.*
import org.scalatra.forms._ import org.scalatra.forms.*
import org.scalatra.i18n.I18nSupport import org.scalatra.i18n.I18nSupport
import org.scalatra.servlet.ServletBase import org.scalatra.servlet.ServletBase
/** /**
* Extends scalatra-forms to support the client-side validation and Ajax requests as well. * 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 = { def get[T](path: String, form: ValueType[T])(action: T => Any): Route = {
registerValidate(path, form) registerValidate(path, form)
@@ -84,8 +84,7 @@ trait ValidationSupport extends FormSupport { self: ServletBase with JacksonJson
* Converts errors to JSON. * Converts errors to JSON.
*/ */
private def toJson(errors: Seq[(String, String)]): JObject = private def toJson(errors: Seq[(String, String)]): JObject =
JObject(errors.map { JObject(errors.map { case (key, value) =>
case (key, value) =>
JField(key, JString(value)) JField(key, JString(value))
}.toList) }.toList)

View File

@@ -5,13 +5,13 @@ import gitbucket.core.model.activity.{CreateWikiPageInfo, DeleteWikiInfo, EditWi
import gitbucket.core.service.RepositoryService.RepositoryInfo import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.service.WebHookService.WebHookGollumPayload import gitbucket.core.service.WebHookService.WebHookGollumPayload
import gitbucket.core.wiki.html import gitbucket.core.wiki.html
import gitbucket.core.service._ import gitbucket.core.service.*
import gitbucket.core.util._ import gitbucket.core.util.*
import gitbucket.core.util.StringUtil._ import gitbucket.core.util.StringUtil.*
import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.util.SyntaxSugars.*
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits.*
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory.*
import org.scalatra.forms._ import org.scalatra.forms.*
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.scalatra.i18n.Messages import org.scalatra.i18n.Messages
@@ -29,15 +29,10 @@ class WikiController
with RequestCache with RequestCache
trait WikiControllerBase extends ControllerBase { trait WikiControllerBase extends ControllerBase {
self: WikiService self: WikiService & RepositoryService & AccountService & ActivityService & WebHookService &
with RepositoryService ReadableUsersAuthenticator & ReferrerAuthenticator =>
with AccountService
with ActivityService
with WebHookService
with ReadableUsersAuthenticator
with ReferrerAuthenticator =>
case class WikiPageEditForm( private case class WikiPageEditForm(
pageName: String, pageName: String,
content: String, content: String,
message: Option[String], message: Option[String],
@@ -45,7 +40,7 @@ trait WikiControllerBase extends ControllerBase {
id: String id: String
) )
val newForm = mapping( private val newForm = mapping(
"pageName" -> trim(label("Page name", text(required, maxlength(40), pageName, unique))), "pageName" -> trim(label("Page name", text(required, maxlength(40), pageName, unique))),
"content" -> trim(label("Content", text(required, conflictForNew))), "content" -> trim(label("Content", text(required, conflictForNew))),
"message" -> trim(label("Message", optional(text()))), "message" -> trim(label("Message", optional(text()))),
@@ -53,7 +48,7 @@ trait WikiControllerBase extends ControllerBase {
"id" -> trim(label("Latest commit id", text())) "id" -> trim(label("Latest commit id", text()))
)(WikiPageEditForm.apply) )(WikiPageEditForm.apply)
val editForm = mapping( private val editForm = mapping(
"pageName" -> trim(label("Page name", text(required, maxlength(40), pageName))), "pageName" -> trim(label("Page name", text(required, maxlength(40), pageName))),
"content" -> trim(label("Content", text(required, conflictForEdit))), "content" -> trim(label("Content", text(required, conflictForEdit))),
"message" -> trim(label("Message", optional(text()))), "message" -> trim(label("Message", optional(text()))),
@@ -121,7 +116,17 @@ trait WikiControllerBase extends ControllerBase {
Some(pageName), Some(pageName),
from, from,
to, to,
JGitUtil.getDiffs(git, Some(from), to, true, false).filter(_.newPath == pageName + ".md"), JGitUtil
.getDiffs(
git = git,
from = Some(from),
to = to,
fetchContent = true,
makePatch = false,
maxFiles = context.settings.repositoryViewer.maxDiffFiles,
maxLines = context.settings.repositoryViewer.maxDiffLines
)
.filter(_.newPath == pageName + ".md"),
repository, repository,
isEditable(repository), isEditable(repository),
flash.get("info") flash.get("info")
@@ -137,7 +142,15 @@ trait WikiControllerBase extends ControllerBase {
None, None,
from, from,
to, to,
JGitUtil.getDiffs(git, Some(from), to, true, false), JGitUtil.getDiffs(
git = git,
from = Some(from),
to = to,
fetchContent = true,
makePatch = false,
maxFiles = context.settings.repositoryViewer.maxDiffFiles,
maxLines = context.settings.repositoryViewer.maxDiffLines
),
repository, repository,
isEditable(repository), isEditable(repository),
flash.get("info") flash.get("info")
@@ -146,8 +159,7 @@ trait WikiControllerBase extends ControllerBase {
}) })
get("/:owner/:repository/wiki/:page/_revert/:commitId")(readableUsersOnly { repository => get("/:owner/:repository/wiki/:page/_revert/:commitId")(readableUsersOnly { repository =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
if (isEditable(repository)) { if (isEditable(repository)) {
val pageName = StringUtil.urlDecode(params("page")) val pageName = StringUtil.urlDecode(params("page"))
val Array(from, to) = params("commitId").split("\\.\\.\\.") val Array(from, to) = params("commitId").split("\\.\\.\\.")
@@ -158,7 +170,7 @@ trait WikiControllerBase extends ControllerBase {
} else { } else {
flash.update("info", "This patch was not able to be reversed.") flash.update("info", "This patch was not able to be reversed.")
redirect( redirect(
s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}" s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/$from...$to"
) )
} }
} else Unauthorized() } else Unauthorized()
@@ -166,8 +178,7 @@ trait WikiControllerBase extends ControllerBase {
}) })
get("/:owner/:repository/wiki/_revert/:commitId")(readableUsersOnly { repository => get("/:owner/:repository/wiki/_revert/:commitId")(readableUsersOnly { repository =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
if (isEditable(repository)) { if (isEditable(repository)) {
val Array(from, to) = params("commitId").split("\\.\\.\\.") val Array(from, to) = params("commitId").split("\\.\\.\\.")
val branch = getWikiBranch(repository.owner, repository.name) val branch = getWikiBranch(repository.owner, repository.name)
@@ -176,7 +187,7 @@ trait WikiControllerBase extends ControllerBase {
redirect(s"/${repository.owner}/${repository.name}/wiki") redirect(s"/${repository.owner}/${repository.name}/wiki")
} else { } else {
flash.update("info", "This patch was not able to be reversed.") flash.update("info", "This patch was not able to be reversed.")
redirect(s"/${repository.owner}/${repository.name}/wiki/_compare/${from}...${to}") redirect(s"/${repository.owner}/${repository.name}/wiki/_compare/$from...$to")
} }
} else Unauthorized() } else Unauthorized()
} }
@@ -192,8 +203,7 @@ trait WikiControllerBase extends ControllerBase {
}) })
post("/:owner/:repository/wiki/_edit", editForm)(readableUsersOnly { (form, repository) => post("/:owner/:repository/wiki/_edit", editForm)(readableUsersOnly { (form, repository) =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
if (isEditable(repository)) { if (isEditable(repository)) {
saveWikiPage( saveWikiPage(
repository.owner, repository.owner,
@@ -204,8 +214,7 @@ trait WikiControllerBase extends ControllerBase {
loginAccount, loginAccount,
form.message.getOrElse(""), form.message.getOrElse(""),
Some(form.id) Some(form.id)
).foreach { ).foreach { commitId =>
commitId =>
updateLastActivityDate(repository.owner, repository.name) updateLastActivityDate(repository.owner, repository.name)
val wikiEditInfo = val wikiEditInfo =
EditWikiPageInfo(repository.owner, repository.name, loginAccount.userName, form.pageName, commitId) EditWikiPageInfo(repository.owner, repository.name, loginAccount.userName, form.pageName, commitId)
@@ -232,8 +241,7 @@ trait WikiControllerBase extends ControllerBase {
}) })
post("/:owner/:repository/wiki/_new", newForm)(readableUsersOnly { (form, repository) => post("/:owner/:repository/wiki/_new", newForm)(readableUsersOnly { (form, repository) =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
if (isEditable(repository)) { if (isEditable(repository)) {
saveWikiPage( saveWikiPage(
repository.owner, repository.owner,
@@ -244,8 +252,7 @@ trait WikiControllerBase extends ControllerBase {
loginAccount, loginAccount,
form.message.getOrElse(""), form.message.getOrElse(""),
None None
).foreach { ).foreach { commitId =>
commitId =>
updateLastActivityDate(repository.owner, repository.name) updateLastActivityDate(repository.owner, repository.name)
val createWikiPageInfo = val createWikiPageInfo =
CreateWikiPageInfo(repository.owner, repository.name, loginAccount.userName, form.pageName) CreateWikiPageInfo(repository.owner, repository.name, loginAccount.userName, form.pageName)
@@ -267,8 +274,7 @@ trait WikiControllerBase extends ControllerBase {
}) })
get("/:owner/:repository/wiki/:page/_delete")(readableUsersOnly { repository => get("/:owner/:repository/wiki/:page/_delete")(readableUsersOnly { repository =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
if (isEditable(repository)) { if (isEditable(repository)) {
val pageName = StringUtil.urlDecode(params("page")) val pageName = StringUtil.urlDecode(params("page"))
deleteWikiPage( deleteWikiPage(
@@ -277,7 +283,7 @@ trait WikiControllerBase extends ControllerBase {
pageName, pageName,
loginAccount.fullName, loginAccount.fullName,
loginAccount.mailAddress, loginAccount.mailAddress,
s"Destroyed ${pageName}" s"Destroyed $pageName"
) )
val deleteWikiInfo = DeleteWikiInfo( val deleteWikiInfo = DeleteWikiInfo(
repository.owner, repository.owner,
@@ -338,9 +344,9 @@ trait WikiControllerBase extends ControllerBase {
private def pageName: Constraint = new Constraint() { private def pageName: Constraint = new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] = override def validate(name: String, value: String, messages: Messages): Option[String] =
if (value.exists("\\/:*?\"<>|".contains(_))) { if (value.exists("\\/:*?\"<>|".contains(_))) {
Some(s"${name} contains invalid character.") Some(s"$name contains invalid character.")
} else if (notReservedPageName(value) && (value.startsWith("_") || value.startsWith("-"))) { } else if (notReservedPageName(value) && (value.startsWith("_") || value.startsWith("-"))) {
Some(s"${name} starts with invalid character.") Some(s"$name starts with invalid character.")
} else { } else {
None None
} }

View File

@@ -15,7 +15,7 @@ import scala.jdk.CollectionConverters._
import scala.util.Using import scala.util.Using
trait ApiGitReferenceControllerBase extends ControllerBase { trait ApiGitReferenceControllerBase extends ControllerBase {
self: ReferrerAuthenticator with WritableUsersAuthenticator => self: ReferrerAuthenticator & WritableUsersAuthenticator =>
private val logger = LoggerFactory.getLogger(classOf[ApiGitReferenceControllerBase]) private val logger = LoggerFactory.getLogger(classOf[ApiGitReferenceControllerBase])
@@ -58,10 +58,8 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#create-a-reference * https://docs.github.com/en/free-pro-team@latest/rest/reference/git#create-a-reference
*/ */
post("/api/v3/repos/:owner/:repository/git/refs")(writableUsersOnly { repository => post("/api/v3/repos/:owner/:repository/git/refs")(writableUsersOnly { repository =>
extractFromJsonBody[CreateARef].map { extractFromJsonBody[CreateARef].map { data =>
data => Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
git =>
val ref = git.getRepository.findRef(data.ref) val ref = git.getRepository.findRef(data.ref)
if (ref == null) { if (ref == null) {
val update = git.getRepository.updateRef(data.ref) val update = git.getRepository.updateRef(data.ref)
@@ -88,8 +86,7 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
*/ */
patch("/api/v3/repos/:owner/:repository/git/refs/*")(writableUsersOnly { repository => patch("/api/v3/repos/:owner/:repository/git/refs/*")(writableUsersOnly { repository =>
val refName = multiParams("splat").mkString("/") val refName = multiParams("splat").mkString("/")
extractFromJsonBody[UpdateARef].map { extractFromJsonBody[UpdateARef].map { data =>
data =>
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
val ref = git.getRepository.findRef(refName) val ref = git.getRepository.findRef(refName)
if (ref == null) { if (ref == null) {

View File

@@ -7,13 +7,8 @@ import gitbucket.core.util.{ReadableUsersAuthenticator, ReferrerAuthenticator, R
import org.scalatra.ActionResult import org.scalatra.ActionResult
trait ApiIssueCommentControllerBase extends ControllerBase { trait ApiIssueCommentControllerBase extends ControllerBase {
self: AccountService self: AccountService & IssuesService & RepositoryService & HandleCommentService & MilestonesService &
with IssuesService ReadableUsersAuthenticator & ReferrerAuthenticator =>
with RepositoryService
with HandleCommentService
with MilestonesService
with ReadableUsersAuthenticator
with ReferrerAuthenticator =>
/* /*
* i. List issue comments for a repository * i. List issue comments for a repository
* https://docs.github.com/en/rest/reference/issues#list-issue-comments-for-a-repository * https://docs.github.com/en/rest/reference/issues#list-issue-comments-for-a-repository
@@ -23,8 +18,7 @@ trait ApiIssueCommentControllerBase extends ControllerBase {
issueId <- params("id").toIntOpt issueId <- params("id").toIntOpt
comments = getCommentsForApi(repository.owner, repository.name, issueId) comments = getCommentsForApi(repository.owner, repository.name, issueId)
} yield { } yield {
JsonFormat(comments.map { JsonFormat(comments.map { case (issueComment, user, issue) =>
case (issueComment, user, issue) =>
ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest) ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest)
}) })
}) getOrElse NotFound() }) getOrElse NotFound()

View File

@@ -9,12 +9,8 @@ import gitbucket.core.util.{ReadableUsersAuthenticator, ReferrerAuthenticator, R
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
trait ApiIssueControllerBase extends ControllerBase { trait ApiIssueControllerBase extends ControllerBase {
self: AccountService self: AccountService & IssuesService & IssueCreationService & MilestonesService & ReadableUsersAuthenticator &
with IssuesService ReferrerAuthenticator =>
with IssueCreationService
with MilestonesService
with ReadableUsersAuthenticator
with ReferrerAuthenticator =>
/* /*
* i. List issues * i. List issues
* https://developer.github.com/v3/issues/#list-issues * https://developer.github.com/v3/issues/#list-issues
@@ -29,7 +25,7 @@ trait ApiIssueControllerBase extends ControllerBase {
val page = IssueSearchCondition.page(request) val page = IssueSearchCondition.page(request)
// TODO: more api spec condition // TODO: more api spec condition
val condition = IssueSearchCondition(request) val condition = IssueSearchCondition(request)
//val baseOwner = getAccountByUserName(repository.owner).get // val baseOwner = getAccountByUserName(repository.owner).get
val issues: List[(Issue, Account, List[Account])] = val issues: List[(Issue, Account, List[Account])] =
searchIssueByApi( searchIssueByApi(
@@ -39,8 +35,7 @@ trait ApiIssueControllerBase extends ControllerBase {
repos = repository.owner -> repository.name repos = repository.owner -> repository.name
) )
JsonFormat(issues.map { JsonFormat(issues.map { case (issue, issueUser, assigneeUsers) =>
case (issue, issueUser, assigneeUsers) =>
ApiIssue( ApiIssue(
issue = issue, issue = issue,
repositoryName = RepositoryName(repository), repositoryName = RepositoryName(repository),

View File

@@ -7,11 +7,7 @@ import gitbucket.core.util._
import org.scalatra.{Created, NoContent, UnprocessableEntity} import org.scalatra.{Created, NoContent, UnprocessableEntity}
trait ApiIssueLabelControllerBase extends ControllerBase { trait ApiIssueLabelControllerBase extends ControllerBase {
self: AccountService self: AccountService & IssuesService & LabelsService & ReferrerAuthenticator & WritableUsersAuthenticator =>
with IssuesService
with LabelsService
with ReferrerAuthenticator
with WritableUsersAuthenticator =>
/* /*
* i. List all labels for this repository * i. List all labels for this repository
@@ -69,8 +65,7 @@ trait ApiIssueLabelControllerBase extends ControllerBase {
data <- extractFromJsonBody[CreateALabel] if data.isValid data <- extractFromJsonBody[CreateALabel] if data.isValid
} yield { } yield {
LockUtil.lock(RepositoryName(repository).fullName) { LockUtil.lock(RepositoryName(repository).fullName) {
getLabel(repository.owner, repository.name, params("labelName")).map { getLabel(repository.owner, repository.name, params("labelName")).map { label =>
label =>
if (getLabel(repository.owner, repository.name, data.name).isEmpty) { if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
updateLabel(repository.owner, repository.name, label.labelId, data.name, data.color) updateLabel(repository.owner, repository.name, label.labelId, data.name, data.color)
JsonFormat( JsonFormat(

View File

@@ -7,7 +7,7 @@ import gitbucket.core.util.Implicits._
import org.scalatra.NoContent import org.scalatra.NoContent
trait ApiIssueMilestoneControllerBase extends ControllerBase { trait ApiIssueMilestoneControllerBase extends ControllerBase {
self: MilestonesService with WritableUsersAuthenticator with ReferrerAuthenticator => self: MilestonesService & WritableUsersAuthenticator & ReferrerAuthenticator =>
/* /*
* i. List milestones * i. List milestones
@@ -16,8 +16,10 @@ trait ApiIssueMilestoneControllerBase extends ControllerBase {
get("/api/v3/repos/:owner/:repository/milestones")(referrersOnly { repository => get("/api/v3/repos/:owner/:repository/milestones")(referrersOnly { repository =>
val state = params.getOrElse("state", "all") val state = params.getOrElse("state", "all")
// TODO "sort", "direction" params should be implemented. // TODO "sort", "direction" params should be implemented.
val apiMilestones = (for (milestoneWithIssue <- getMilestonesWithIssueCount(repository.owner, repository.name) val apiMilestones = (for (
.sortBy(p => p._1.milestoneId)) milestoneWithIssue <- getMilestonesWithIssueCount(repository.owner, repository.name)
.sortBy(p => p._1.milestoneId)
)
yield { yield {
ApiMilestone( ApiMilestone(
repository.repository, repository.repository,

View File

@@ -6,7 +6,7 @@ import gitbucket.core.util.Implicits._
import gitbucket.core.util.{AdminAuthenticator, UsersAuthenticator} import gitbucket.core.util.{AdminAuthenticator, UsersAuthenticator}
trait ApiOrganizationControllerBase extends ControllerBase { trait ApiOrganizationControllerBase extends ControllerBase {
self: RepositoryService with AccountService with AdminAuthenticator with UsersAuthenticator => self: RepositoryService & AccountService & AdminAuthenticator & UsersAuthenticator =>
/* /*
* i. List your organizations * i. List your organizations

View File

@@ -16,14 +16,8 @@ import scala.util.Using
import scala.jdk.CollectionConverters._ import scala.jdk.CollectionConverters._
trait ApiPullRequestControllerBase extends ControllerBase { trait ApiPullRequestControllerBase extends ControllerBase {
self: AccountService self: AccountService & IssuesService & PullRequestService & RepositoryService & MergeService & ReferrerAuthenticator &
with IssuesService ReadableUsersAuthenticator & WritableUsersAuthenticator =>
with PullRequestService
with RepositoryService
with MergeService
with ReferrerAuthenticator
with ReadableUsersAuthenticator
with WritableUsersAuthenticator =>
/* /*
* i. Link Relations * i. Link Relations
@@ -48,8 +42,7 @@ trait ApiPullRequestControllerBase extends ControllerBase {
repos = repository.owner -> repository.name repos = repository.owner -> repository.name
) )
JsonFormat(issues.map { JsonFormat(issues.map { case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner, assignees) =>
case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner, assignees) =>
ApiPullRequest( ApiPullRequest(
issue = issue, issue = issue,
pullRequest = pullRequest, pullRequest = pullRequest,
@@ -89,8 +82,7 @@ trait ApiPullRequestControllerBase extends ControllerBase {
case Left(createPullReq) => case Left(createPullReq) =>
val (reqOwner, reqBranch) = parseCompareIdentifier(createPullReq.head, repository.owner) val (reqOwner, reqBranch) = parseCompareIdentifier(createPullReq.head, repository.owner)
getRepository(reqOwner, repository.name) getRepository(reqOwner, repository.name)
.flatMap { .flatMap { forkedRepository =>
forkedRepository =>
getPullRequestCommitFromTo(repository, forkedRepository, createPullReq.base, reqBranch) match { getPullRequestCommitFromTo(repository, forkedRepository, createPullReq.base, reqBranch) match {
case (Some(commitIdFrom), Some(commitIdTo)) => case (Some(commitIdFrom), Some(commitIdTo)) =>
val issueId = insertIssue( val issueId = insertIssue(
@@ -128,8 +120,7 @@ trait ApiPullRequestControllerBase extends ControllerBase {
case Right(createPullReqAlt) => case Right(createPullReqAlt) =>
val (reqOwner, reqBranch) = parseCompareIdentifier(createPullReqAlt.head, repository.owner) val (reqOwner, reqBranch) = parseCompareIdentifier(createPullReqAlt.head, repository.owner)
getRepository(reqOwner, repository.name) getRepository(reqOwner, repository.name)
.flatMap { .flatMap { forkedRepository =>
forkedRepository =>
getPullRequestCommitFromTo(repository, forkedRepository, createPullReqAlt.base, reqBranch) match { getPullRequestCommitFromTo(repository, forkedRepository, createPullReqAlt.base, reqBranch) match {
case (Some(commitIdFrom), Some(commitIdTo)) => case (Some(commitIdFrom), Some(commitIdTo)) =>
changeIssueToPullRequest(repository.owner, repository.name, createPullReqAlt.issue) changeIssueToPullRequest(repository.owner, repository.name, createPullReqAlt.issue)
@@ -190,10 +181,8 @@ trait ApiPullRequestControllerBase extends ControllerBase {
get("/api/v3/repos/:owner/:repository/pulls/:id/commits")(referrersOnly { repository => get("/api/v3/repos/:owner/:repository/pulls/:id/commits")(referrersOnly { repository =>
val owner = repository.owner val owner = repository.owner
val name = repository.name val name = repository.name
params("id").toIntOpt.flatMap { params("id").toIntOpt.flatMap { issueId =>
issueId => getPullRequest(owner, name, issueId) map { case (issue, pullreq) =>
getPullRequest(owner, name, issueId) map {
case (issue, pullreq) =>
Using.resource(Git.open(getRepositoryDir(owner, name))) { git => Using.resource(Git.open(getRepositoryDir(owner, name))) { git =>
val oldId = git.getRepository.resolve(pullreq.commitIdFrom) val oldId = git.getRepository.resolve(pullreq.commitIdFrom)
val newId = git.getRepository.resolve(pullreq.commitIdTo) val newId = git.getRepository.resolve(pullreq.commitIdTo)
@@ -240,8 +229,8 @@ trait ApiPullRequestControllerBase extends ControllerBase {
*/ */
put("/api/v3/repos/:owner/:repository/pulls/:id/merge")(referrersOnly { repository => put("/api/v3/repos/:owner/:repository/pulls/:id/merge")(referrersOnly { repository =>
(for { (for {
//TODO: crash when body is empty // TODO: crash when body is empty
//TODO: Implement sha parameter // TODO: Implement sha parameter
data <- extractFromJsonBody[MergeAPullRequest] data <- extractFromJsonBody[MergeAPullRequest]
issueId <- params("id").toIntOpt issueId <- params("id").toIntOpt
(issue, pullReq) <- getPullRequest(repository.owner, repository.name, issueId) (issue, pullReq) <- getPullRequest(repository.owner, repository.name, issueId)
@@ -273,7 +262,7 @@ trait ApiPullRequestControllerBase extends ControllerBase {
repository, repository,
issueId, issueId,
context.loginAccount.get, context.loginAccount.get,
data.commit_message.getOrElse(""), //TODO: Implement commit_title data.commit_message.getOrElse(""), // TODO: Implement commit_title
strategy, strategy,
pullReq.isDraft, pullReq.isDraft,
context.settings context.settings

View File

@@ -11,7 +11,7 @@ import org.apache.commons.io.FileUtils
import org.scalatra.NoContent import org.scalatra.NoContent
trait ApiReleaseControllerBase extends ControllerBase { trait ApiReleaseControllerBase extends ControllerBase {
self: AccountService with ReleaseService with ReferrerAuthenticator with WritableUsersAuthenticator => self: AccountService & ReleaseService & ReferrerAuthenticator & WritableUsersAuthenticator =>
/** /**
* i. List releases for a repository * i. List releases for a repository
@@ -119,8 +119,7 @@ trait ApiReleaseControllerBase extends ControllerBase {
* ix. Upload a release asset * ix. Upload a release asset
* https://developer.github.com/v3/repos/releases/#upload-a-release-asset * https://developer.github.com/v3/repos/releases/#upload-a-release-asset
*/ */
post("/api/v3/repos/:owner/:repository/releases/:tag/assets")(writableUsersOnly { post("/api/v3/repos/:owner/:repository/releases/:tag/assets")(writableUsersOnly { repository =>
repository =>
val name = params("name") val name = params("name")
val tag = params("tag") val tag = params("tag")
getRelease(repository.owner, repository.name, tag) getRelease(repository.owner, repository.name, tag)

View File

@@ -1,26 +1,19 @@
package gitbucket.core.controller.api package gitbucket.core.controller.api
import gitbucket.core.api._ import gitbucket.core.api.*
import gitbucket.core.controller.ControllerBase import gitbucket.core.controller.ControllerBase
import gitbucket.core.service.{AccountService, ProtectedBranchService, RepositoryService} import gitbucket.core.service.{AccountService, ProtectedBranchService, RepositoryService}
import gitbucket.core.util._ import gitbucket.core.util.*
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory.*
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits.*
import gitbucket.core.util.JGitUtil.getBranches import gitbucket.core.util.JGitUtil.{getBranchesNoMergeInfo, processTree}
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.scalatra.NoContent import org.scalatra.NoContent
import scala.util.Using import scala.util.Using
trait ApiRepositoryBranchControllerBase extends ControllerBase { trait ApiRepositoryBranchControllerBase extends ControllerBase {
self: RepositoryService self: RepositoryService & AccountService & OwnerAuthenticator & UsersAuthenticator & GroupManagerAuthenticator &
with AccountService ProtectedBranchService & ReferrerAuthenticator & ReadableUsersAuthenticator & WritableUsersAuthenticator =>
with OwnerAuthenticator
with UsersAuthenticator
with GroupManagerAuthenticator
with ProtectedBranchService
with ReferrerAuthenticator
with ReadableUsersAuthenticator
with WritableUsersAuthenticator =>
/** /**
* i. List branches * i. List branches
@@ -30,11 +23,7 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
JsonFormat( JsonFormat(
JGitUtil JGitUtil
.getBranches( .getBranchesNoMergeInfo(git)
git = git,
defaultBranch = repository.repository.defaultBranch,
origin = repository.repository.originUserName.isEmpty
)
.map { br => .map { br =>
ApiBranchForList(br.name, ApiBranchCommit(br.commitId)) ApiBranchForList(br.name, ApiBranchCommit(br.commitId))
} }
@@ -47,19 +36,16 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
* https://docs.github.com/en/rest/reference/repos#get-a-branch * https://docs.github.com/en/rest/reference/repos#get-a-branch
*/ */
get("/api/v3/repos/:owner/:repository/branches/*")(referrersOnly { repository => get("/api/v3/repos/:owner/:repository/branches/*")(referrersOnly { repository =>
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
git =>
(for { (for {
branch <- params.get("splat") if repository.branchList.contains(branch) branch <- params.get("splat") if repository.branchList.contains(branch)
br <- getBranches( br <- getBranchesNoMergeInfo(git).find(_.name == branch)
git,
repository.repository.defaultBranch,
repository.repository.originUserName.isEmpty
).find(_.name == branch)
} yield { } yield {
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch) val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
JsonFormat( JsonFormat(
ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtection(protection))(RepositoryName(repository)) ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtectionResponse(protection))(
RepositoryName(repository)
)
) )
}) getOrElse NotFound() }) getOrElse NotFound()
} }
@@ -74,7 +60,7 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
if (repository.branchList.contains(branch)) { if (repository.branchList.contains(branch)) {
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch) val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
JsonFormat( JsonFormat(
ApiBranchProtection(protection) ApiBranchProtectionResponse(protection)
) )
} else { NotFound() } } else { NotFound() }
}) })
@@ -154,7 +140,7 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
if (repository.branchList.contains(branch)) { if (repository.branchList.contains(branch)) {
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch) val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
JsonFormat( JsonFormat(
ApiBranchProtection(protection).required_status_checks ApiBranchProtectionResponse(protection).required_status_checks
) )
} else { NotFound() } } else { NotFound() }
}) })
@@ -274,30 +260,29 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
* https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection * https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection
*/ */
patch("/api/v3/repos/:owner/:repository/branches/*")(ownerOnly { repository => patch("/api/v3/repos/:owner/:repository/branches/*")(ownerOnly { repository =>
import gitbucket.core.api._ import gitbucket.core.api.*
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
git =>
(for { (for {
branch <- params.get("splat") if repository.branchList.contains(branch) branch <- params.get("splat") if repository.branchList.contains(branch)
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection) protection <- extractFromJsonBody[ApiBranchProtectionRequest.EnablingAndDisabling].map(_.protection)
br <- getBranches( br <- getBranchesNoMergeInfo(git).find(_.name == branch)
git,
repository.repository.defaultBranch,
repository.repository.originUserName.isEmpty
).find(_.name == branch)
} yield { } yield {
if (protection.enabled) { if (protection.enabled) {
enableBranchProtection( enableBranchProtection(
repository.owner, repository.owner,
repository.name, repository.name,
branch, branch,
protection.status.enforcement_level == ApiBranchProtection.Everyone, protection.enforce_admins.getOrElse(false),
protection.status.contexts protection.required_status_checks.isDefined,
protection.required_status_checks.map(_.contexts).getOrElse(Nil),
protection.restrictions.isDefined,
protection.restrictions.map(_.users).getOrElse(Nil)
) )
} else { } else {
disableBranchProtection(repository.owner, repository.name, branch) 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() }) getOrElse NotFound()
} }
}) })

View File

@@ -7,7 +7,7 @@ import gitbucket.core.util.{OwnerAuthenticator, ReferrerAuthenticator}
import org.scalatra.NoContent import org.scalatra.NoContent
trait ApiRepositoryCollaboratorControllerBase extends ControllerBase { trait ApiRepositoryCollaboratorControllerBase extends ControllerBase {
self: RepositoryService with AccountService with ReferrerAuthenticator with OwnerAuthenticator => self: RepositoryService & AccountService & ReferrerAuthenticator & OwnerAuthenticator =>
/* /*
* i. List repository collaborators * i. List repository collaborators

View File

@@ -4,31 +4,24 @@ import gitbucket.core.controller.ControllerBase
import gitbucket.core.model.Account import gitbucket.core.model.Account
import gitbucket.core.service.{AccountService, CommitsService, ProtectedBranchService} import gitbucket.core.service.{AccountService, CommitsService, ProtectedBranchService}
import gitbucket.core.util.Directory.getRepositoryDir import gitbucket.core.util.Directory.getRepositoryDir
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits.*
import gitbucket.core.util.JGitUtil.{CommitInfo, getBranches, getBranchesOfCommit} import gitbucket.core.util.JGitUtil.{CommitInfo, getBranchesNoMergeInfo, getBranchesOfCommit}
import gitbucket.core.util.{JGitUtil, ReferrerAuthenticator, RepositoryName} import gitbucket.core.util.{JGitUtil, ReferrerAuthenticator, RepositoryName}
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.revwalk.RevWalk import org.eclipse.jgit.revwalk.RevWalk
import org.eclipse.jgit.revwalk.filter.{ import org.eclipse.jgit.revwalk.filter.*
AndRevFilter,
AuthorRevFilter,
CommitTimeRevFilter,
MaxCountRevFilter,
RevFilter,
SkipRevFilter
}
import org.eclipse.jgit.treewalk.filter.{AndTreeFilter, PathFilterGroup, TreeFilter} import org.eclipse.jgit.treewalk.filter.{AndTreeFilter, PathFilterGroup, TreeFilter}
import scala.collection.mutable.ListBuffer
import scala.jdk.CollectionConverters._ import java.time.format.DateTimeFormatter.*
import scala.util.Using import java.time.{LocalDateTime, ZoneOffset}
import math.min
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter._
import java.util.Date import java.util.Date
import java.time.ZoneOffset import scala.collection.mutable.ListBuffer
import scala.jdk.CollectionConverters.*
import scala.math.min
import scala.util.Using
trait ApiRepositoryCommitControllerBase extends ControllerBase { trait ApiRepositoryCommitControllerBase extends ControllerBase {
self: AccountService with CommitsService with ProtectedBranchService with ReferrerAuthenticator => self: AccountService & CommitsService & ProtectedBranchService & ReferrerAuthenticator =>
/* /*
* i. List commits on a repository * i. List commits on a repository
* https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository * https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository
@@ -43,11 +36,9 @@ trait ApiRepositoryCommitControllerBase extends ControllerBase {
val path = params.get("path").filter(_.nonEmpty) val path = params.get("path").filter(_.nonEmpty)
val since = params.get("since").filter(_.nonEmpty) val since = params.get("since").filter(_.nonEmpty)
val until = params.get("until").filter(_.nonEmpty) val until = params.get("until").filter(_.nonEmpty)
Using.resource(Git.open(getRepositoryDir(owner, name))) { Using.resource(Git.open(getRepositoryDir(owner, name))) { git =>
git =>
val repo = git.getRepository val repo = git.getRepository
Using.resource(new RevWalk(repo)) { Using.resource(new RevWalk(repo)) { revWalk =>
revWalk =>
val objectId = repo.resolve(sha) val objectId = repo.resolve(sha)
revWalk.markStart(revWalk.parseCommit(objectId)) revWalk.markStart(revWalk.parseCommit(objectId))
if (path.nonEmpty) { if (path.nonEmpty) {
@@ -80,8 +71,7 @@ trait ApiRepositoryCommitControllerBase extends ControllerBase {
revfilters(0) revfilters(0)
} }
) )
JsonFormat(revWalk.asScala.map { JsonFormat(revWalk.asScala.map { commit =>
commit =>
val commitInfo = new CommitInfo(commit) val commitInfo = new CommitInfo(commit)
ApiCommits( ApiCommits(
repositoryName = RepositoryName(repository), repositoryName = RepositoryName(repository),
@@ -105,8 +95,7 @@ trait ApiRepositoryCommitControllerBase extends ControllerBase {
val name = repository.name val name = repository.name
val sha = params("sha") val sha = params("sha")
Using.resource(Git.open(getRepositoryDir(owner, name))) { Using.resource(Git.open(getRepositoryDir(owner, name))) { git =>
git =>
val repo = git.getRepository val repo = git.getRepository
val objectId = repo.resolve(sha) val objectId = repo.resolve(sha)
val commitInfo = Using.resource(new RevWalk(repo)) { revWalk => val commitInfo = Using.resource(new RevWalk(repo)) { revWalk =>
@@ -170,7 +159,7 @@ trait ApiRepositoryCommitControllerBase extends ControllerBase {
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
val apiBranchForCommits = for { val apiBranchForCommits = for {
branch <- getBranchesOfCommit(git, sha) branch <- getBranchesOfCommit(git, sha)
br <- getBranches(git, branch, repository.repository.originUserName.isEmpty).find(_.name == branch) br <- getBranchesNoMergeInfo(git).find(_.name == branch)
} yield { } yield {
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch) val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
ApiBranchForHeadCommit(branch, ApiBranchCommit(br.commitId), protection.enabled) ApiBranchForHeadCommit(branch, ApiBranchCommit(br.commitId), protection.enabled)

View File

@@ -12,15 +12,14 @@ import org.eclipse.jgit.api.Git
import scala.util.Using import scala.util.Using
trait ApiRepositoryContentsControllerBase extends ControllerBase { trait ApiRepositoryContentsControllerBase extends ControllerBase {
self: ReferrerAuthenticator with WritableUsersAuthenticator with RepositoryCommitFileService => self: ReferrerAuthenticator & WritableUsersAuthenticator & RepositoryCommitFileService =>
/** /**
* i. Get a repository README * i. Get a repository README
* https://docs.github.com/en/rest/reference/repos#get-a-repository-readme * https://docs.github.com/en/rest/reference/repos#get-a-repository-readme
*/ */
get("/api/v3/repos/:owner/:repository/readme")(referrersOnly { repository => get("/api/v3/repos/:owner/:repository/readme")(referrersOnly { repository =>
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
git =>
val refStr = params.getOrElse("ref", repository.repository.defaultBranch) val refStr = params.getOrElse("ref", repository.repository.defaultBranch)
val files = getFileList(git, refStr, ".", maxFiles = context.settings.repositoryViewer.maxFiles) val files = getFileList(git, refStr, ".", maxFiles = context.settings.repositoryViewer.maxFiles)
files // files should be sorted alphabetically. files // files should be sorted alphabetically.
@@ -134,8 +133,7 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
* requested #2112 * requested #2112
*/ */
put("/api/v3/repos/:owner/:repository/contents/*")(writableUsersOnly { repository => put("/api/v3/repos/:owner/:repository/contents/*")(writableUsersOnly { repository =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount =>
JsonFormat(for { JsonFormat(for {
data <- extractFromJsonBody[CreateAFile] data <- extractFromJsonBody[CreateAFile]
} yield { } yield {
@@ -146,8 +144,7 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
} }
val paths = multiParams("splat").head.split("/") val paths = multiParams("splat").head.split("/")
val path = paths.take(paths.size - 1).toList.mkString("/") val path = paths.take(paths.size - 1).toList.mkString("/")
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
git =>
val fileInfo = getFileInfo(git, commit, path, false) val fileInfo = getFileInfo(git, commit, path, false)
fileInfo match { fileInfo match {

View File

@@ -15,16 +15,9 @@ import scala.concurrent.duration.Duration
import scala.util.Using import scala.util.Using
trait ApiRepositoryControllerBase extends ControllerBase { trait ApiRepositoryControllerBase extends ControllerBase {
self: RepositoryService self: RepositoryService & ApiGitReferenceControllerBase & RepositoryCreationService & AccountService &
with ApiGitReferenceControllerBase OwnerAuthenticator & UsersAuthenticator & GroupManagerAuthenticator & ReferrerAuthenticator &
with RepositoryCreationService ReadableUsersAuthenticator & WritableUsersAuthenticator =>
with AccountService
with OwnerAuthenticator
with UsersAuthenticator
with GroupManagerAuthenticator
with ReferrerAuthenticator
with ReadableUsersAuthenticator
with WritableUsersAuthenticator =>
/** /**
* i. List your repositories * i. List your repositories
@@ -189,7 +182,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
get("/api/v3/repos/:owner/:repository/tags")(referrersOnly { repository => get("/api/v3/repos/:owner/:repository/tags")(referrersOnly { repository =>
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
JsonFormat( JsonFormat(
self.getRef("tags", repository) repository.tags.map(tagInfo => ApiTag(tagInfo.name, RepositoryName(repository), tagInfo.commitId))
) )
} }
}) })

View File

@@ -7,7 +7,7 @@ import gitbucket.core.util.Implicits._
import gitbucket.core.util.{JGitUtil, ReferrerAuthenticator, WritableUsersAuthenticator} import gitbucket.core.util.{JGitUtil, ReferrerAuthenticator, WritableUsersAuthenticator}
trait ApiRepositoryStatusControllerBase extends ControllerBase { trait ApiRepositoryStatusControllerBase extends ControllerBase {
self: AccountService with CommitStatusService with ReferrerAuthenticator with WritableUsersAuthenticator => self: AccountService & CommitStatusService & ReferrerAuthenticator & WritableUsersAuthenticator =>
/* /*
* i. Create a status * i. Create a status
@@ -47,8 +47,7 @@ trait ApiRepositoryStatusControllerBase extends ControllerBase {
ref <- params.get("ref") ref <- params.get("ref")
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref) sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
} yield { } yield {
JsonFormat(getCommitStatusesWithCreator(repository.owner, repository.name, sha).map { JsonFormat(getCommitStatusesWithCreator(repository.owner, repository.name, sha).map { case (status, creator) =>
case (status, creator) =>
ApiCommitStatus(status, ApiUser(creator)) ApiCommitStatus(status, ApiUser(creator))
}) })
}) getOrElse NotFound() }) getOrElse NotFound()

View File

@@ -8,7 +8,7 @@ import gitbucket.core.util.Implicits._
import org.scalatra.NoContent import org.scalatra.NoContent
trait ApiRepositoryWebhookControllerBase extends ControllerBase { trait ApiRepositoryWebhookControllerBase extends ControllerBase {
self: RepositoryService with WebHookService with ReferrerAuthenticator with WritableUsersAuthenticator => self: RepositoryService & WebHookService & ReferrerAuthenticator & WritableUsersAuthenticator =>
/* /*
* i. List repository webhooks * i. List repository webhooks

View File

@@ -8,7 +8,7 @@ import gitbucket.core.util.StringUtil._
import org.scalatra.NoContent import org.scalatra.NoContent
trait ApiUserControllerBase extends ControllerBase { trait ApiUserControllerBase extends ControllerBase {
self: RepositoryService with AccountService with AdminAuthenticator with UsersAuthenticator => self: RepositoryService & AccountService & AdminAuthenticator & UsersAuthenticator =>
/** /**
* i. Get a single user * i. Get a single user

View File

@@ -3,7 +3,7 @@ package gitbucket.core.model
protected[model] trait TemplateComponent { self: Profile => protected[model] trait TemplateComponent { self: Profile =>
import profile.api._ import profile.api._
trait BasicTemplate { self: Table[_] => trait BasicTemplate { self: Table[?] =>
val userName = column[String]("USER_NAME") val userName = column[String]("USER_NAME")
val repositoryName = column[String]("REPOSITORY_NAME") val repositoryName = column[String]("REPOSITORY_NAME")
@@ -18,7 +18,7 @@ protected[model] trait TemplateComponent { self: Profile =>
(this.userName === userName) && (this.repositoryName === repositoryName) (this.userName === userName) && (this.repositoryName === repositoryName)
} }
trait IssueTemplate extends BasicTemplate { self: Table[_] => trait IssueTemplate extends BasicTemplate { self: Table[?] =>
val issueId = column[Int]("ISSUE_ID") val issueId = column[Int]("ISSUE_ID")
def byIssue(owner: String, repository: String, issueId: Int) = def byIssue(owner: String, repository: String, issueId: Int) =
@@ -28,7 +28,7 @@ protected[model] trait TemplateComponent { self: Profile =>
byRepository(userName, repositoryName) && (this.issueId === issueId) byRepository(userName, repositoryName) && (this.issueId === issueId)
} }
trait LabelTemplate extends BasicTemplate { self: Table[_] => trait LabelTemplate extends BasicTemplate { self: Table[?] =>
val labelId = column[Int]("LABEL_ID") val labelId = column[Int]("LABEL_ID")
val labelName = column[String]("LABEL_NAME") val labelName = column[String]("LABEL_NAME")
@@ -42,7 +42,7 @@ protected[model] trait TemplateComponent { self: Profile =>
byRepository(owner, repository) && (this.labelName === labelName.bind) byRepository(owner, repository) && (this.labelName === labelName.bind)
} }
trait PriorityTemplate extends BasicTemplate { self: Table[_] => trait PriorityTemplate extends BasicTemplate { self: Table[?] =>
val priorityId = column[Int]("PRIORITY_ID") val priorityId = column[Int]("PRIORITY_ID")
val priorityName = column[String]("PRIORITY_NAME") val priorityName = column[String]("PRIORITY_NAME")
@@ -56,7 +56,7 @@ protected[model] trait TemplateComponent { self: Profile =>
byRepository(owner, repository) && (this.priorityName === priorityName.bind) byRepository(owner, repository) && (this.priorityName === priorityName.bind)
} }
trait MilestoneTemplate extends BasicTemplate { self: Table[_] => trait MilestoneTemplate extends BasicTemplate { self: Table[?] =>
val milestoneId = column[Int]("MILESTONE_ID") val milestoneId = column[Int]("MILESTONE_ID")
def byMilestone(owner: String, repository: String, milestoneId: Int) = def byMilestone(owner: String, repository: String, milestoneId: Int) =
@@ -66,7 +66,7 @@ protected[model] trait TemplateComponent { self: Profile =>
byRepository(userName, repositoryName) && (this.milestoneId === milestoneId) byRepository(userName, repositoryName) && (this.milestoneId === milestoneId)
} }
trait CommitTemplate extends BasicTemplate { self: Table[_] => trait CommitTemplate extends BasicTemplate { self: Table[?] =>
val commitId = column[String]("COMMIT_ID") val commitId = column[String]("COMMIT_ID")
def byCommit(owner: String, repository: String, commitId: String) = def byCommit(owner: String, repository: String, commitId: String) =
@@ -76,7 +76,7 @@ protected[model] trait TemplateComponent { self: Profile =>
byRepository(userName, repositoryName) && (this.commitId === commitId) byRepository(userName, repositoryName) && (this.commitId === commitId)
} }
trait BranchTemplate extends BasicTemplate { self: Table[_] => trait BranchTemplate extends BasicTemplate { self: Table[?] =>
val branch = column[String]("BRANCH") val branch = column[String]("BRANCH")
def byBranch(owner: String, repository: String, branchName: String) = def byBranch(owner: String, repository: String, branchName: String) =
byRepository(owner, repository) && (branch === branchName.bind) byRepository(owner, repository) && (branch === branchName.bind)

View File

@@ -40,8 +40,8 @@ case class CustomField(
) )
trait CustomFieldBehavior { trait CustomFieldBehavior {
def createHtml(repository: RepositoryInfo, fieldId: Int, fieldName: String, constraints: Option[String])( def createHtml(repository: RepositoryInfo, fieldId: Int, fieldName: String, constraints: Option[String])(implicit
implicit context: Context context: Context
): String ): String
def fieldHtml( def fieldHtml(
repository: RepositoryInfo, repository: RepositoryInfo,
@@ -51,8 +51,8 @@ trait CustomFieldBehavior {
constraints: Option[String], constraints: Option[String],
value: String, value: String,
editable: Boolean editable: Boolean
)( )(implicit
implicit context: Context context: Context
): String ): String
def validate(name: String, constraints: Option[String], value: String, messages: Messages): Option[String] def validate(name: String, constraints: Option[String], value: String, messages: Messages): Option[String]
} }
@@ -181,10 +181,8 @@ object CustomFieldBehavior {
s"""<li><a href="javascript:void(0);" class="custom-field-option-$fieldId" data-value=""><i class="octicon octicon-x"></i> Clear ${StringUtil s"""<li><a href="javascript:void(0);" class="custom-field-option-$fieldId" data-value=""><i class="octicon octicon-x"></i> Clear ${StringUtil
.escapeHtml(fieldName)}</a></li>""" .escapeHtml(fieldName)}</a></li>"""
) )
constraints.foreach { constraints.foreach { x =>
x => x.split(",").map(_.trim).foreach { item =>
x.split(",").map(_.trim).foreach {
item =>
options.append(s"""<li> options.append(s"""<li>
| <a href="javascript:void(0);" class="custom-field-option-$fieldId" data-value="${StringUtil | <a href="javascript:void(0);" class="custom-field-option-$fieldId" data-value="${StringUtil
.escapeHtml(item)}"> .escapeHtml(item)}">
@@ -296,8 +294,8 @@ object CustomFieldBehavior {
constraints: Option[String], constraints: Option[String],
value: String, value: String,
editable: Boolean editable: Boolean
)( )(implicit
implicit context: Context context: Context
): String = { ): String = {
val sb = new StringBuilder val sb = new StringBuilder
if (value.nonEmpty) { if (value.nonEmpty) {

View File

@@ -1,16 +1,18 @@
package gitbucket.core.model package gitbucket.core.model
trait ProtectedBranchComponent extends TemplateComponent { self: Profile => trait ProtectedBranchComponent extends TemplateComponent { self: Profile =>
import profile.api._ import profile.api.*
import self._
lazy val ProtectedBranches = TableQuery[ProtectedBranches] lazy val ProtectedBranches = TableQuery[ProtectedBranches]
class ProtectedBranches(tag: Tag) extends Table[ProtectedBranch](tag, "PROTECTED_BRANCH") with BranchTemplate { class ProtectedBranches(tag: Tag) extends Table[ProtectedBranch](tag, "PROTECTED_BRANCH") with BranchTemplate {
val statusCheckAdmin = column[Boolean]("STATUS_CHECK_ADMIN") val statusCheckAdmin = column[Boolean]("STATUS_CHECK_ADMIN") // enforceAdmins
def * = (userName, repositoryName, branch, statusCheckAdmin).mapTo[ProtectedBranch] val requiredStatusCheck = column[Boolean]("REQUIRED_STATUS_CHECK")
def byPrimaryKey(userName: String, repositoryName: String, branch: String) = 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) 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) byBranch(userName, repositoryName, branch)
} }
@@ -22,8 +24,27 @@ trait ProtectedBranchComponent extends TemplateComponent { self: Profile =>
def * = def * =
(userName, repositoryName, branch, context).mapTo[ProtectedBranchContext] (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 ProtectedBranchContext(userName: String, repositoryName: String, branch: String, context: String)
case class ProtectedBranchRestriction(userName: String, repositoryName: String, branch: String, allowedUser: String)

View File

@@ -13,6 +13,7 @@ trait PullRequestComponent extends TemplateComponent { self: Profile =>
val commitIdFrom = column[String]("COMMIT_ID_FROM") val commitIdFrom = column[String]("COMMIT_ID_FROM")
val commitIdTo = column[String]("COMMIT_ID_TO") val commitIdTo = column[String]("COMMIT_ID_TO")
val isDraft = column[Boolean]("IS_DRAFT") val isDraft = column[Boolean]("IS_DRAFT")
val mergedCommitIds = column[String]("MERGED_COMMIT_IDS")
def * = def * =
( (
userName, userName,
@@ -24,12 +25,13 @@ trait PullRequestComponent extends TemplateComponent { self: Profile =>
requestBranch, requestBranch,
commitIdFrom, commitIdFrom,
commitIdTo, commitIdTo,
isDraft isDraft,
mergedCommitIds.?
).mapTo[PullRequest] ).mapTo[PullRequest]
def byPrimaryKey(userName: String, repositoryName: String, issueId: Int) = def byPrimaryKey(userName: String, repositoryName: String, issueId: Int): Rep[Boolean] =
byIssue(userName, repositoryName, issueId) 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) byIssue(userName, repositoryName, issueId)
} }
} }
@@ -44,5 +46,6 @@ case class PullRequest(
requestBranch: String, requestBranch: String,
commitIdFrom: String, commitIdFrom: String,
commitIdTo: String, commitIdTo: String,
isDraft: Boolean isDraft: Boolean,
mergedCommitIds: Option[String]
) )

View File

@@ -53,8 +53,7 @@ trait RepositoryComponent extends TemplateComponent { self: Profile =>
safeMode safeMode
) )
).shaped.<>( ).shaped.<>(
{ { case (repository, options) =>
case (repository, options) =>
Repository( Repository(
repository._1, repository._1,
repository._2, repository._2,
@@ -68,9 +67,10 @@ trait RepositoryComponent extends TemplateComponent { self: Profile =>
repository._10, repository._10,
repository._11, repository._11,
repository._12, repository._12,
RepositoryOptions.tupled.apply(options) RepositoryOptions.apply.tupled.apply(options)
) )
}, { (r: Repository) => },
{ (r: Repository) =>
Some( Some(
( (
( (

View File

@@ -41,8 +41,8 @@ trait GitRepositoryFilter {
* @param session the database session * @param session the database session
* @return true if allow accessing to repository, otherwise false. * @return true if allow accessing to repository, otherwise false.
*/ */
def filter(path: String, userName: Option[String], settings: SystemSettings, isUpdating: Boolean)( def filter(path: String, userName: Option[String], settings: SystemSettings, isUpdating: Boolean)(implicit
implicit session: Session session: Session
): Boolean ): Boolean
} }

View File

@@ -9,16 +9,16 @@ import profile.api._
trait IssueHook { trait IssueHook {
def created(issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = () def created(issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = ()
def addedComment(commentId: Int, content: String, issue: Issue, repository: RepositoryInfo)( def addedComment(commentId: Int, content: String, issue: Issue, repository: RepositoryInfo)(implicit
implicit session: Session, session: Session,
context: Context context: Context
): Unit = () ): Unit = ()
def deletedComment(commentId: Int, issue: Issue, repository: RepositoryInfo)( def deletedComment(commentId: Int, issue: Issue, repository: RepositoryInfo)(implicit
implicit session: Session, session: Session,
context: Context context: Context
): Unit = () ): Unit = ()
def updatedComment(commentId: Int, content: String, issue: Issue, repository: RepositoryInfo)( def updatedComment(commentId: Int, content: String, issue: Issue, repository: RepositoryInfo)(implicit
implicit session: Session, session: Session,
context: Context context: Context
): Unit = () ): Unit = ()
def closed(issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = () def closed(issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = ()
@@ -29,12 +29,12 @@ trait IssueHook {
assigner: Option[String], assigner: Option[String],
assigned: Option[String], assigned: Option[String],
oldAssigned: Option[String] oldAssigned: Option[String]
)( )(implicit
implicit session: Session, session: Session,
context: Context context: Context
): Unit = () ): Unit = ()
def closedByCommitComment(issue: Issue, repository: RepositoryInfo, message: String, pusher: Account)( def closedByCommitComment(issue: Issue, repository: RepositoryInfo, message: String, pusher: Account)(implicit
implicit session: Session session: Session
): Unit = () ): Unit = ()
} }

View File

@@ -340,24 +340,19 @@ abstract class Plugin {
* Register plugin functionality to PluginRegistry. * Register plugin functionality to PluginRegistry.
*/ */
def initialize(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Unit = { def initialize(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Unit = {
(images ++ images(registry, context, settings)).foreach { (images ++ images(registry, context, settings)).foreach { case (id, in) =>
case (id, in) =>
registry.addImage(id, in) registry.addImage(id, in)
} }
(controllers ++ controllers(registry, context, settings)).foreach { (controllers ++ controllers(registry, context, settings)).foreach { case (path, controller) =>
case (path, controller) =>
registry.addController(path, controller) registry.addController(path, controller)
} }
(anonymousAccessiblePaths ++ anonymousAccessiblePaths(registry, context, settings)).foreach { (anonymousAccessiblePaths ++ anonymousAccessiblePaths(registry, context, settings)).foreach { case (path) =>
case (path) =>
registry.addAnonymousAccessiblePath(path) registry.addAnonymousAccessiblePath(path)
} }
(javaScripts ++ javaScripts(registry, context, settings)).foreach { (javaScripts ++ javaScripts(registry, context, settings)).foreach { case (path, script) =>
case (path, script) =>
registry.addJavaScript(path, script) registry.addJavaScript(path, script)
} }
(renderers ++ renderers(registry, context, settings)).foreach { (renderers ++ renderers(registry, context, settings)).foreach { case (extension, renderer) =>
case (extension, renderer) =>
registry.addRenderer(extension, renderer) registry.addRenderer(extension, renderer)
} }
(repositoryRoutings ++ repositoryRoutings(registry, context, settings)).foreach { routing => (repositoryRoutings ++ repositoryRoutings(registry, context, settings)).foreach { routing =>

View File

@@ -80,7 +80,7 @@ class PluginRegistry {
def getAnonymousAccessiblePaths(): Seq[String] = anonymousAccessiblePaths.asScala.toSeq def getAnonymousAccessiblePaths(): Seq[String] = anonymousAccessiblePaths.asScala.toSeq
def addJavaScript(path: String, script: String): Unit = def addJavaScript(path: String, script: String): Unit =
javaScripts.add((path, script)) //javaScripts += ((path, script)) javaScripts.add((path, script)) // javaScripts += ((path, script))
def getJavaScript(currentPath: String): List[String] = def getJavaScript(currentPath: String): List[String] =
javaScripts.asScala.filter(x => currentPath.matches(x._1)).toList.map(_._2) javaScripts.asScala.filter(x => currentPath.matches(x._1)).toList.map(_._2)
@@ -307,7 +307,7 @@ object PluginRegistry {
conn, conn,
classLoader, classLoader,
DatabaseConfig.liquiDriver, DatabaseConfig.liquiDriver,
new Module(plugin.pluginId, plugin.versions: _*) new Module(plugin.pluginId, plugin.versions*)
) )
conn.commit() conn.commit()

View File

@@ -13,8 +13,8 @@ trait ReceiveHook {
command: ReceiveCommand, command: ReceiveCommand,
pusher: String, pusher: String,
mergePullRequest: Boolean mergePullRequest: Boolean
)( )(implicit
implicit session: Session session: Session
): Option[String] = None ): Option[String] = None
def postReceive( def postReceive(
@@ -24,8 +24,8 @@ trait ReceiveHook {
command: ReceiveCommand, command: ReceiveCommand,
pusher: String, pusher: String,
mergePullRequest: Boolean mergePullRequest: Boolean
)( )(implicit
implicit session: Session session: Session
): Unit = () ): Unit = ()
} }

View File

@@ -37,8 +37,7 @@ trait AccessTokenService {
def getAccountByAccessToken(token: String)(implicit s: Session): Option[Account] = def getAccountByAccessToken(token: String)(implicit s: Session): Option[Account] =
Accounts Accounts
.join(AccessTokens) .join(AccessTokens)
.filter { .filter { case (ac, t) =>
case (ac, t) =>
(ac.userName === t.userName) && (t.tokenHash === tokenToHash(token).bind) && (ac.removed === false.bind) (ac.userName === t.userName) && (t.tokenHash === tokenToHash(token).bind) && (ac.removed === false.bind)
} }
.map { case (ac, t) => ac } .map { case (ac, t) => ac }

View File

@@ -51,8 +51,8 @@ trait AccountFederationService {
* @param preferredUserName Username * @param preferredUserName Username
* @return Available username * @return Available username
*/ */
def findAvailableUserName(preferredUserName: Option[String], mailAddress: String)( def findAvailableUserName(preferredUserName: Option[String], mailAddress: String)(implicit
implicit s: Session s: Session
): Option[String] = { ): Option[String] = {
preferredUserName preferredUserName
.flatMap(n => extractSafeStringForUserName(n)) .flatMap(n => extractSafeStringForUserName(n))

View File

@@ -19,8 +19,8 @@ trait AccountService {
private val logger = LoggerFactory.getLogger(classOf[AccountService]) private val logger = LoggerFactory.getLogger(classOf[AccountService])
def authenticate(settings: SystemSettings, userName: String, password: String)( def authenticate(settings: SystemSettings, userName: String, password: String)(implicit
implicit s: Session s: Session
): Option[Account] = { ): Option[Account] = {
val account = if (password.isEmpty) { val account = if (password.isEmpty) {
None None
@@ -58,8 +58,8 @@ trait AccountService {
/** /**
* Authenticate by LDAP. * Authenticate by LDAP.
*/ */
private def ldapAuthentication(settings: SystemSettings, userName: String, password: String)( private def ldapAuthentication(settings: SystemSettings, userName: String, password: String)(implicit
implicit s: Session s: Session
): Option[Account] = { ): Option[Account] = {
LDAPUtil.authenticate(settings.ldap.get, userName, password) match { LDAPUtil.authenticate(settings.ldap.get, userName, password) match {
case Right(ldapUserInfo) => { case Right(ldapUserInfo) => {
@@ -112,15 +112,15 @@ trait AccountService {
def getAccountByUserName(userName: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] = def getAccountByUserName(userName: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] =
Accounts filter (t => (t.userName === userName.bind).&&(t.removed === false.bind, !includeRemoved)) firstOption Accounts filter (t => (t.userName === userName.bind).&&(t.removed === false.bind, !includeRemoved)) firstOption
def getAccountByUserNameIgnoreCase(userName: String, includeRemoved: Boolean = false)( def getAccountByUserNameIgnoreCase(userName: String, includeRemoved: Boolean = false)(implicit
implicit s: Session s: Session
): Option[Account] = ): Option[Account] =
Accounts filter ( Accounts filter (t =>
t => (t.userName.toLowerCase === userName.toLowerCase.bind).&&(t.removed === false.bind, !includeRemoved) (t.userName.toLowerCase === userName.toLowerCase.bind).&&(t.removed === false.bind, !includeRemoved)
) firstOption ) firstOption
def getAccountsByUserNames(userNames: Set[String], knowns: Set[Account], includeRemoved: Boolean = false)( def getAccountsByUserNames(userNames: Set[String], knowns: Set[Account], includeRemoved: Boolean = false)(implicit
implicit s: Session s: Session
): Map[String, Account] = { ): Map[String, Account] = {
val map = knowns.map(a => a.userName -> a).toMap val map = knowns.map(a => a.userName -> a).toMap
val needs = userNames -- map.keySet val needs = userNames -- map.keySet
@@ -135,17 +135,15 @@ trait AccountService {
} }
} }
def getAccountByMailAddress(mailAddress: String, includeRemoved: Boolean = false)( def getAccountByMailAddress(mailAddress: String, includeRemoved: Boolean = false)(implicit
implicit s: Session s: Session
): Option[Account] = ): Option[Account] =
(Accounts joinLeft AccountExtraMailAddresses on { case (a, e) => a.userName === e.userName }) (Accounts joinLeft AccountExtraMailAddresses on { case (a, e) => a.userName === e.userName })
.filter { .filter { case (a, x) =>
case (a, x) =>
((a.mailAddress.toLowerCase === mailAddress.toLowerCase.bind) || ((a.mailAddress.toLowerCase === mailAddress.toLowerCase.bind) ||
(x.map { e => (x.map { e =>
e.extraMailAddress.toLowerCase === mailAddress.toLowerCase.bind e.extraMailAddress.toLowerCase === mailAddress.toLowerCase.bind
} }.getOrElse(false.bind))).&&(a.removed === false.bind, !includeRemoved)
.getOrElse(false.bind))).&&(a.removed === false.bind, !includeRemoved)
} }
.map { case (a, e) => a } firstOption .map { case (a, e) => a } firstOption
@@ -267,8 +265,8 @@ trait AccountService {
group group
} }
def updateGroup(groupName: String, description: Option[String], url: Option[String], removed: Boolean)( def updateGroup(groupName: String, description: Option[String], url: Option[String], removed: Boolean)(implicit
implicit s: Session s: Session
): Unit = ): Unit =
Accounts Accounts
.filter(_.userName === groupName.bind) .filter(_.userName === groupName.bind)
@@ -277,8 +275,7 @@ trait AccountService {
def updateGroupMembers(groupName: String, members: List[(String, Boolean)])(implicit s: Session): Unit = { def updateGroupMembers(groupName: String, members: List[(String, Boolean)])(implicit s: Session): Unit = {
GroupMembers.filter(_.groupName === groupName.bind).delete GroupMembers.filter(_.groupName === groupName.bind).delete
members.foreach { members.foreach { case (userName, isManager) =>
case (userName, isManager) =>
GroupMembers insert GroupMember(groupName, userName, isManager) GroupMembers insert GroupMember(groupName, userName, isManager)
} }
} }
@@ -318,8 +315,8 @@ trait AccountService {
/* /*
* For account preference * For account preference
*/ */
def getAccountPreference(userName: String)( def getAccountPreference(userName: String)(implicit
implicit s: Session s: Session
): Option[AccountPreference] = { ): Option[AccountPreference] = {
AccountPreferences filter (_.byPrimaryKey(userName)) firstOption AccountPreferences filter (_.byPrimaryKey(userName)) firstOption
} }

View File

@@ -43,8 +43,7 @@ trait ActivityService {
def getRecentActivitiesByRepos(repos: Set[(String, String)])(implicit context: Context): List[Activity] = { def getRecentActivitiesByRepos(repos: Set[(String, String)])(implicit context: Context): List[Activity] = {
getActivities(includePublic = true) { activity => getActivities(includePublic = true) { activity =>
repos.exists { repos.exists { case (userName, repositoryName) =>
case (userName, repositoryName) =>
activity.userName == userName && activity.repositoryName == repositoryName activity.userName == userName && activity.repositoryName == repositoryName
} }
} }
@@ -65,10 +64,12 @@ trait ActivityService {
.get() .get()
) { reader => ) { reader =>
var json: String = null var json: String = null
while (list.length < 50 && { while (
list.length < 50 && {
json = reader.readLine(); json = reader.readLine();
json json
} != null) { } != null
) {
val activity = read[Activity](json) val activity = read[Activity](json)
if (filter(activity)) { if (filter(activity)) {
list += activity list += activity

View File

@@ -47,8 +47,8 @@ trait CommitStatusService {
) )
} }
def getCommitStatusWithSummary(userName: String, repositoryName: String, sha: String)( def getCommitStatusWithSummary(userName: String, repositoryName: String, sha: String)(implicit
implicit s: Session s: Session
): Option[(CommitState, List[CommitStatus])] = { ): Option[(CommitState, List[CommitStatus])] = {
val statuses = getCommitStatuses(userName, repositoryName, sha) val statuses = getCommitStatuses(userName, repositoryName, sha)
if (statuses.isEmpty) { if (statuses.isEmpty) {
@@ -62,18 +62,18 @@ trait CommitStatusService {
def getCommitStatus(userName: String, repositoryName: String, id: Int)(implicit s: Session): Option[CommitStatus] = def getCommitStatus(userName: String, repositoryName: String, id: Int)(implicit s: Session): Option[CommitStatus] =
CommitStatuses.filter(t => t.byPrimaryKey(id) && t.byRepository(userName, repositoryName)).firstOption CommitStatuses.filter(t => t.byPrimaryKey(id) && t.byRepository(userName, repositoryName)).firstOption
def getCommitStatus(userName: String, repositoryName: String, sha: String, context: String)( def getCommitStatus(userName: String, repositoryName: String, sha: String, context: String)(implicit
implicit s: Session s: Session
): Option[CommitStatus] = ): Option[CommitStatus] =
CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha) && t.context === context.bind).firstOption CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha) && t.context === context.bind).firstOption
def getCommitStatuses(userName: String, repositoryName: String, sha: String)( def getCommitStatuses(userName: String, repositoryName: String, sha: String)(implicit
implicit s: Session s: Session
): List[CommitStatus] = ): List[CommitStatus] =
byCommitStatus(userName, repositoryName, sha).list byCommitStatus(userName, repositoryName, sha).list
def getRecentStatusContexts(userName: String, repositoryName: String, time: java.util.Date)( def getRecentStatusContexts(userName: String, repositoryName: String, time: java.util.Date)(implicit
implicit s: Session s: Session
): List[String] = ): List[String] =
CommitStatuses CommitStatuses
.filter(t => t.byRepository(userName, repositoryName)) .filter(t => t.byRepository(userName, repositoryName))
@@ -82,8 +82,8 @@ trait CommitStatusService {
.map(_._1) .map(_._1)
.list .list
def getCommitStatusesWithCreator(userName: String, repositoryName: String, sha: String)( def getCommitStatusesWithCreator(userName: String, repositoryName: String, sha: String)(implicit
implicit s: Session s: Session
): List[(CommitStatus, Account)] = ): List[(CommitStatus, Account)] =
byCommitStatus(userName, repositoryName, sha) byCommitStatus(userName, repositoryName, sha)
.join(Accounts) .join(Accounts)

View File

@@ -16,10 +16,10 @@ import gitbucket.core.util.{FileUtil, StringUtil}
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
trait CommitsService { trait CommitsService {
self: ActivityService with PullRequestService with WebHookPullRequestReviewCommentService => self: ActivityService & PullRequestService & WebHookPullRequestReviewCommentService =>
def getCommitComments(owner: String, repository: String, commitId: String, includePullRequest: Boolean)( def getCommitComments(owner: String, repository: String, commitId: String, includePullRequest: Boolean)(implicit
implicit s: Session s: Session
) = ) =
CommitComments filter { t => CommitComments filter { t =>
t.byCommit(owner, repository, commitId) && (t.issueId.isEmpty || includePullRequest) t.byCommit(owner, repository, commitId) && (t.issueId.isEmpty || includePullRequest)
@@ -79,8 +79,7 @@ trait CommitsService {
val comment = getCommitComment(repository.owner, repository.name, commentId.toString).get val comment = getCommitComment(repository.owner, repository.name, commentId.toString).get
issueId match { issueId match {
case Some(issueId) => case Some(issueId) =>
getPullRequest(repository.owner, repository.name, issueId).foreach { getPullRequest(repository.owner, repository.name, issueId).foreach { case (issue, pullRequest) =>
case (issue, pullRequest) =>
val pullRequestCommentInfo = val pullRequestCommentInfo =
PullRequestCommentInfo(repository.owner, repository.name, loginAccount.userName, content, issueId) PullRequestCommentInfo(repository.owner, repository.name, loginAccount.userName, content, issueId)
recordActivity(pullRequestCommentInfo) recordActivity(pullRequestCommentInfo)
@@ -104,8 +103,8 @@ trait CommitsService {
commentId commentId
} }
def updateCommitCommentPosition(commentId: Int, commitId: String, oldLine: Option[Int], newLine: Option[Int])( def updateCommitCommentPosition(commentId: Int, commitId: String, oldLine: Option[Int], newLine: Option[Int])(implicit
implicit s: Session s: Session
): Unit = ): Unit =
CommitComments CommitComments
.filter(_.byPrimaryKey(commentId)) .filter(_.byPrimaryKey(commentId))

View File

@@ -9,8 +9,8 @@ trait CustomFieldsService {
def getCustomFields(owner: String, repository: String)(implicit s: Session): List[CustomField] = def getCustomFields(owner: String, repository: String)(implicit s: Session): List[CustomField] =
CustomFields.filter(_.byRepository(owner, repository)).sortBy(_.fieldId asc).list CustomFields.filter(_.byRepository(owner, repository)).sortBy(_.fieldId asc).list
def getCustomFieldsWithValue(owner: String, repository: String, issueId: Int)( def getCustomFieldsWithValue(owner: String, repository: String, issueId: Int)(implicit
implicit s: Session s: Session
): List[(CustomField, Option[IssueCustomField])] = { ): List[(CustomField, Option[IssueCustomField])] = {
CustomFields CustomFields
.filter(_.byRepository(owner, repository)) .filter(_.byRepository(owner, repository))
@@ -52,8 +52,8 @@ trait CustomFieldsService {
constraints: Option[String], constraints: Option[String],
enableForIssues: Boolean, enableForIssues: Boolean,
enableForPullRequests: Boolean enableForPullRequests: Boolean
)( )(implicit
implicit s: Session s: Session
): Unit = ): Unit =
CustomFields CustomFields
.filter(_.byPrimaryKey(owner, repository, fieldId)) .filter(_.byPrimaryKey(owner, repository, fieldId))

View File

@@ -17,8 +17,7 @@ trait GpgKeyService {
def addGpgPublicKey(userName: String, title: String, publicKey: String)(implicit s: Session): Unit = { def addGpgPublicKey(userName: String, title: String, publicKey: String)(implicit s: Session): Unit = {
val pubKeyOf = new BcPGPObjectFactory(new ArmoredInputStream(new ByteArrayInputStream(publicKey.getBytes))) val pubKeyOf = new BcPGPObjectFactory(new ArmoredInputStream(new ByteArrayInputStream(publicKey.getBytes)))
pubKeyOf.iterator().asScala.foreach { pubKeyOf.iterator().asScala.foreach { case keyRing: PGPPublicKeyRing =>
case keyRing: PGPPublicKeyRing =>
val key = keyRing.getPublicKey() val key = keyRing.getPublicKey()
GpgKeys.insert(GpgKey(userName = userName, gpgKeyId = key.getKeyID, title = title, publicKey = publicKey)) GpgKeys.insert(GpgKey(userName = userName, gpgKeyId = key.getKeyID, title = title, publicKey = publicKey))
} }

View File

@@ -16,12 +16,8 @@ import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
trait HandleCommentService { trait HandleCommentService {
self: RepositoryService self: RepositoryService & IssuesService & ActivityService & WebHookService & WebHookIssueCommentService &
with IssuesService WebHookPullRequestService =>
with ActivityService
with WebHookService
with WebHookIssueCommentService
with WebHookPullRequestService =>
/** /**
* @see [[https://github.com/gitbucket/gitbucket/wiki/CommentAction]] * @see [[https://github.com/gitbucket/gitbucket/wiki/CommentAction]]
@@ -134,8 +130,8 @@ trait HandleCommentService {
} }
} }
def deleteCommentByApi(repoInfo: RepositoryInfo, comment: IssueComment, issue: Issue)( def deleteCommentByApi(repoInfo: RepositoryInfo, comment: IssueComment, issue: Issue)(implicit
implicit context: Context, context: Context,
s: Session s: Session
): Option[IssueComment] = context.loginAccount.flatMap { _ => ): Option[IssueComment] = context.loginAccount.flatMap { _ =>
comment.action match { comment.action match {

View File

@@ -10,7 +10,7 @@ import gitbucket.core.util.Implicits._
trait IssueCreationService { trait IssueCreationService {
self: RepositoryService with WebHookIssueCommentService with LabelsService with IssuesService with ActivityService => self: RepositoryService & WebHookIssueCommentService & LabelsService & IssuesService & ActivityService =>
def createIssue( def createIssue(
repository: RepositoryInfo, repository: RepositoryInfo,

View File

@@ -26,7 +26,7 @@ import gitbucket.core.plugin.PluginRegistry
import scala.jdk.CollectionConverters._ import scala.jdk.CollectionConverters._
trait IssuesService { trait IssuesService {
self: AccountService with RepositoryService with LabelsService with PrioritiesService with MilestonesService => self: AccountService & RepositoryService & LabelsService & PrioritiesService & MilestonesService =>
import IssuesService._ import IssuesService._
def getIssue(owner: String, repository: String, issueId: String)(implicit s: Session) = def getIssue(owner: String, repository: String, issueId: String)(implicit s: Session) =
@@ -41,8 +41,8 @@ trait IssuesService {
IssueComments filter (_.byIssue(owner, repository, issueId)) sortBy (_.commentId asc) list IssueComments filter (_.byIssue(owner, repository, issueId)) sortBy (_.commentId asc) list
/** @return IssueComment and commentedUser and Issue */ /** @return IssueComment and commentedUser and Issue */
def getCommentsForApi(owner: String, repository: String, issueId: Int)( def getCommentsForApi(owner: String, repository: String, issueId: Int)(implicit
implicit s: Session s: Session
): List[(IssueComment, Account, Issue)] = ): List[(IssueComment, Account, Issue)] =
IssueComments IssueComments
.filter(_.byIssue(owner, repository, issueId)) .filter(_.byIssue(owner, repository, issueId))
@@ -54,8 +54,8 @@ trait IssuesService {
.map { case t1 ~ t2 ~ t3 => (t1, t2, t3) } .map { case t1 ~ t2 ~ t3 => (t1, t2, t3) }
.list .list
def getMergedComment(owner: String, repository: String, issueId: Int)( def getMergedComment(owner: String, repository: String, issueId: Int)(implicit
implicit s: Session s: Session
): Option[(IssueComment, Account)] = { ): Option[(IssueComment, Account)] = {
IssueComments IssueComments
.filter(_.byIssue(owner, repository, issueId)) .filter(_.byIssue(owner, repository, issueId))
@@ -74,8 +74,8 @@ trait IssuesService {
else None else None
} }
def getCommentForApi(owner: String, repository: String, commentId: Int)( def getCommentForApi(owner: String, repository: String, commentId: Int)(implicit
implicit s: Session s: Session
): Option[(IssueComment, Account, Issue)] = ): Option[(IssueComment, Account, Issue)] =
IssueComments IssueComments
.filter(_.byRepository(owner, repository)) .filter(_.byRepository(owner, repository))
@@ -91,8 +91,7 @@ trait IssuesService {
def getIssueLabels(owner: String, repository: String, issueId: Int)(implicit s: Session): List[Label] = { def getIssueLabels(owner: String, repository: String, issueId: Int)(implicit s: Session): List[Label] = {
IssueLabels IssueLabels
.join(Labels) .join(Labels)
.on { .on { case t1 ~ t2 =>
case t1 ~ t2 =>
t1.byLabel(t2.userName, t2.repositoryName, t2.labelId) t1.byLabel(t2.userName, t2.repositoryName, t2.labelId)
} }
.filter { case t1 ~ t2 => t1.byIssue(owner, repository, issueId) } .filter { case t1 ~ t2 => t1.byIssue(owner, repository, issueId) }
@@ -100,8 +99,8 @@ trait IssuesService {
.list .list
} }
def getIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int)( def getIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int)(implicit
implicit s: Session s: Session
): Option[IssueLabel] = { ): Option[IssueLabel] = {
IssueLabels filter (_.byPrimaryKey(owner, repository, issueId, labelId)) firstOption IssueLabels filter (_.byPrimaryKey(owner, repository, issueId, labelId)) firstOption
} }
@@ -114,8 +113,8 @@ trait IssuesService {
* @param repos Tuple of the repository owner and the repository name * @param repos Tuple of the repository owner and the repository name
* @return the count of the search result * @return the count of the search result
*/ */
def countIssue(condition: IssueSearchCondition, searchOption: IssueSearchOption, repos: (String, String)*)( def countIssue(condition: IssueSearchCondition, searchOption: IssueSearchOption, repos: (String, String)*)(implicit
implicit s: Session s: Session
): Int = { ): Int = {
Query(searchIssueQuery(repos, condition, searchOption).length).first Query(searchIssueQuery(repos, condition, searchOption).length).first
} }
@@ -136,21 +135,17 @@ trait IssuesService {
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), IssueSearchOption.Issues) searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), IssueSearchOption.Issues)
.join(IssueLabels) .join(IssueLabels)
.on { .on { case t1 ~ t2 =>
case t1 ~ t2 =>
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
} }
.join(Labels) .join(Labels)
.on { .on { case t1 ~ t2 ~ t3 =>
case t1 ~ t2 ~ t3 =>
t2.byLabel(t3.userName, t3.repositoryName, t3.labelId) t2.byLabel(t3.userName, t3.repositoryName, t3.labelId)
} }
.groupBy { .groupBy { case t1 ~ t2 ~ t3 =>
case t1 ~ t2 ~ t3 =>
t3.labelName t3.labelName
} }
.map { .map { case (labelName, t) =>
case (labelName, t) =>
labelName -> t.length labelName -> t.length
} }
.list .list
@@ -173,16 +168,13 @@ trait IssuesService {
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), IssueSearchOption.Issues) searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), IssueSearchOption.Issues)
.join(Priorities) .join(Priorities)
.on { .on { case t1 ~ t2 =>
case t1 ~ t2 =>
t1.byPriority(t2.userName, t2.repositoryName, t2.priorityId) t1.byPriority(t2.userName, t2.repositoryName, t2.priorityId)
} }
.groupBy { .groupBy { case t1 ~ t2 =>
case t1 ~ t2 =>
t2.priorityName t2.priorityName
} }
.map { .map { case (priorityName, t) =>
case (priorityName, t) =>
priorityName -> t.length priorityName -> t.length
} }
.list .list
@@ -221,8 +213,7 @@ trait IssuesService {
.joinLeft(IssueAssignees) .joinLeft(IssueAssignees)
.on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 ~ t8 => t1.byIssue(t8.userName, t8.repositoryName, t8.issueId) } .on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 ~ t8 => t1.byIssue(t8.userName, t8.repositoryName, t8.issueId) }
.sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 ~ t8 => i asc } .sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 ~ t8 => i asc }
.map { .map { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 ~ t8 =>
case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 ~ t8 =>
( (
t1, t1,
t2.commentCount, t2.commentCount,
@@ -264,8 +255,8 @@ trait IssuesService {
/** for api /** for api
* @return (issue, issueUser, Seq(assigneeUsers)) * @return (issue, issueUser, Seq(assigneeUsers))
*/ */
def searchIssueByApi(condition: IssueSearchCondition, offset: Int, limit: Int, repos: (String, String)*)( def searchIssueByApi(condition: IssueSearchCondition, offset: Int, limit: Int, repos: (String, String)*)(implicit
implicit s: Session s: Session
): List[(Issue, Account, List[Account])] = { ): List[(Issue, Account, List[Account])] = {
// get issues and comment count and labels // get issues and comment count and labels
searchIssueQueryBase(condition, IssueSearchOption.Issues, offset, limit, repos) searchIssueQueryBase(condition, IssueSearchOption.Issues, offset, limit, repos)
@@ -278,12 +269,10 @@ trait IssuesService {
.sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 => i asc } .sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 => i asc }
.map { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 => (t1, t3, t5) } .map { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 => (t1, t3, t5) }
.list .list
.groupBy { .groupBy { case (issue, account, _) =>
case (issue, account, _) =>
(issue, account) (issue, account)
} }
.map { .map { case (_, values) =>
case (_, values) =>
(values.head._1, values.head._2, values.flatMap(_._3)) (values.head._1, values.head._2, values.flatMap(_._3))
} }
.toList .toList
@@ -312,12 +301,10 @@ trait IssuesService {
.sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 ~ t8 => i asc } .sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 ~ t8 => i asc }
.map { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 ~ t8 => (t1, t5, t2.commentCount, t3, t4, t6, t8) } .map { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 ~ t8 => (t1, t5, t2.commentCount, t3, t4, t6, t8) }
.list .list
.groupBy { .groupBy { case (issue, openedUser, commentCount, pullRequest, repository, account, assignedUser) =>
case (issue, openedUser, commentCount, pullRequest, repository, account, assignedUser) =>
(issue, openedUser, commentCount, pullRequest, repository, account) (issue, openedUser, commentCount, pullRequest, repository, account)
} }
.map { .map { case (_, values) =>
case (_, values) =>
( (
values.head._1, values.head._1,
values.head._2, values.head._2,
@@ -344,8 +331,7 @@ trait IssuesService {
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
} }
.sortBy { case (t1, t2) => t1.issueId desc } .sortBy { case (t1, t2) => t1.issueId desc }
.sortBy { .sortBy { case (t1, t2) =>
case (t1, t2) =>
condition.sort match { condition.sort match {
case "created" => case "created" =>
condition.direction match { condition.direction match {
@@ -380,8 +366,8 @@ trait IssuesService {
repos: Seq[(String, String)], repos: Seq[(String, String)],
condition: IssueSearchCondition, condition: IssueSearchCondition,
searchOption: IssueSearchOption searchOption: IssueSearchOption
)( )(implicit
implicit s: Session s: Session
) = { ) = {
val query = Issues filter { t1 => val query = Issues filter { t1 =>
(if (repos.sizeIs == 1) { (if (repos.sizeIs == 1) {
@@ -395,7 +381,7 @@ trait IssuesService {
case _ => t1.closed === true || t1.closed === false case _ => t1.closed === true || t1.closed === false
}).&&(t1.milestoneId.? isEmpty, condition.milestone.contains(None)) }).&&(t1.milestoneId.? isEmpty, condition.milestone.contains(None))
.&&(t1.priorityId.? isEmpty, condition.priority.contains(None)) .&&(t1.priorityId.? isEmpty, condition.priority.contains(None))
//.&&(t1.assignedUserName.? isEmpty, condition.assigned == Some(None)) // .&&(t1.assignedUserName.? isEmpty, condition.assigned == Some(None))
.&&(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) && .&&(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) &&
(searchOption match { (searchOption match {
case IssueSearchOption.Issues => t1.pullRequest === false case IssueSearchOption.Issues => t1.pullRequest === false
@@ -451,17 +437,24 @@ trait IssuesService {
// Mentioned filter // Mentioned filter
.&&( .&&(
(t1.openedUserName === condition.mentioned.get.bind) || (IssueAssignees filter { t1 => (t1.openedUserName === condition.mentioned.get.bind) || (IssueAssignees filter { t1 =>
t1.byIssue(t1.userName, t1.repositoryName, t1.issueId) && t1.assigneeUserName === condition.mentioned.get.bind t1.byIssue(
t1.userName,
t1.repositoryName,
t1.issueId
) && t1.assigneeUserName === condition.mentioned.get.bind
} exists) || } exists) ||
(IssueComments filter { t2 => (IssueComments filter { t2 =>
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) && (t2.commentedUserName === condition.mentioned.get.bind) (t2.byIssue(
t1.userName,
t1.repositoryName,
t1.issueId
)) && (t2.commentedUserName === condition.mentioned.get.bind)
} exists), } exists),
condition.mentioned.isDefined condition.mentioned.isDefined
) )
} }
condition.others.foldLeft(query) { condition.others.foldLeft(query) { case (query, cond) =>
case (query, cond) =>
def condQuery(f: Rep[String] => Rep[Boolean]): Query[Profile.Issues, Issue, Seq] = { def condQuery(f: Rep[String] => Rep[Boolean]): Query[Profile.Issues, Issue, Seq] = {
query.filter { t1 => query.filter { t1 =>
IssueCustomFields IssueCustomFields
@@ -469,8 +462,7 @@ trait IssuesService {
.on { (t2, t3) => .on { (t2, t3) =>
t2.userName === t3.userName && t2.repositoryName === t3.repositoryName && t2.fieldId === t3.fieldId t2.userName === t3.userName && t2.repositoryName === t3.repositoryName && t2.fieldId === t3.fieldId
} }
.filter { .filter { case (t2, t3) =>
case (t2, t3) =>
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) && t3.fieldName === cond.name.bind && f( t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) && t3.fieldName === cond.name.bind && f(
t2.value t2.value
) )
@@ -527,7 +519,8 @@ trait IssuesService {
} }
def registerIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int, insertComment: Boolean = false)( def registerIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int, insertComment: Boolean = false)(
implicit context: Context, implicit
context: Context,
s: Session s: Session
): Int = { ): Int = {
if (insertComment) { if (insertComment) {
@@ -546,7 +539,8 @@ trait IssuesService {
} }
def deleteIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int, insertComment: Boolean = false)( def deleteIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int, insertComment: Boolean = false)(
implicit context: Context, implicit
context: Context,
s: Session s: Session
): Int = { ): Int = {
if (insertComment) { if (insertComment) {
@@ -564,8 +558,8 @@ trait IssuesService {
IssueLabels filter (_.byPrimaryKey(owner, repository, issueId, labelId)) delete IssueLabels filter (_.byPrimaryKey(owner, repository, issueId, labelId)) delete
} }
def deleteAllIssueLabels(owner: String, repository: String, issueId: Int, insertComment: Boolean = false)( def deleteAllIssueLabels(owner: String, repository: String, issueId: Int, insertComment: Boolean = false)(implicit
implicit context: Context, context: Context,
s: Session s: Session
): Int = { ): Int = {
if (insertComment) { if (insertComment) {
@@ -604,8 +598,8 @@ trait IssuesService {
) )
} }
def updateIssue(owner: String, repository: String, issueId: Int, title: String, content: Option[String])( def updateIssue(owner: String, repository: String, issueId: Int, title: String, content: Option[String])(implicit
implicit s: Session s: Session
): Int = { ): Int = {
Issues Issues
.filter(_.byPrimaryKey(owner, repository, issueId)) .filter(_.byPrimaryKey(owner, repository, issueId))
@@ -624,8 +618,8 @@ trait IssuesService {
.update(true) .update(true)
} }
def getIssueAssignees(owner: String, repository: String, issueId: Int)( def getIssueAssignees(owner: String, repository: String, issueId: Int)(implicit
implicit s: Session s: Session
): List[IssueAssignee] = { ): List[IssueAssignee] = {
IssueAssignees.filter(_.byIssue(owner, repository, issueId)).sortBy(_.assigneeUserName).list IssueAssignees.filter(_.byIssue(owner, repository, issueId)).sortBy(_.assigneeUserName).list
} }
@@ -636,8 +630,8 @@ trait IssuesService {
issueId: Int, issueId: Int,
assigneeUserName: String, assigneeUserName: String,
insertComment: Boolean = false insertComment: Boolean = false
)( )(implicit
implicit context: Context, context: Context,
s: Session s: Session
): Int = { ): Int = {
val assigner = context.loginAccount.map(_.userName) val assigner = context.loginAccount.map(_.userName)
@@ -665,8 +659,8 @@ trait IssuesService {
issueId: Int, issueId: Int,
assigneeUserName: String, assigneeUserName: String,
insertComment: Boolean = false insertComment: Boolean = false
)( )(implicit
implicit context: Context, context: Context,
s: Session s: Session
): Int = { ): Int = {
val assigner = context.loginAccount.map(_.userName) val assigner = context.loginAccount.map(_.userName)
@@ -688,8 +682,8 @@ trait IssuesService {
IssueAssignees filter (_.byPrimaryKey(owner, repository, issueId, assigneeUserName)) delete IssueAssignees filter (_.byPrimaryKey(owner, repository, issueId, assigneeUserName)) delete
} }
def deleteAllIssueAssignees(owner: String, repository: String, issueId: Int, insertComment: Boolean = false)( def deleteAllIssueAssignees(owner: String, repository: String, issueId: Int, insertComment: Boolean = false)(implicit
implicit context: Context, context: Context,
s: Session s: Session
): Int = { ): Int = {
val assigner = context.loginAccount.map(_.userName) val assigner = context.loginAccount.map(_.userName)
@@ -773,15 +767,15 @@ trait IssuesService {
.update(priorityId, currentDate) .update(priorityId, currentDate)
} }
def updateComment(owner: String, repository: String, issueId: Int, commentId: Int, content: String)( def updateComment(owner: String, repository: String, issueId: Int, commentId: Int, content: String)(implicit
implicit s: Session s: Session
): Int = { ): Int = {
Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(_.updatedDate).update(currentDate) Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(_.updatedDate).update(currentDate)
IssueComments.filter(_.byPrimaryKey(commentId)).map(t => (t.content, t.updatedDate)).update(content, currentDate) IssueComments.filter(_.byPrimaryKey(commentId)).map(t => (t.content, t.updatedDate)).update(content, currentDate)
} }
def deleteComment(owner: String, repository: String, issueId: Int, commentId: Int)( def deleteComment(owner: String, repository: String, issueId: Int, commentId: Int)(implicit
implicit context: Context, context: Context,
s: Session s: Session
): Int = { ): Int = {
Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(_.updatedDate).update(currentDate) Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(_.updatedDate).update(currentDate)
@@ -820,10 +814,10 @@ trait IssuesService {
* @param query the keywords separated by whitespace. * @param query the keywords separated by whitespace.
* @return issues with comment count and matched content of issue or comment * @return issues with comment count and matched content of issue or comment
*/ */
def searchIssuesByKeyword(owner: String, repository: String, query: String, pullRequest: Boolean)( def searchIssuesByKeyword(owner: String, repository: String, query: String, pullRequest: Boolean)(implicit
implicit s: Session s: Session
): List[(Issue, Int, String)] = { ): List[(Issue, Int, String)] = {
//import slick.driver.JdbcDriver.likeEncode // import slick.driver.JdbcDriver.likeEncode
val keywords = splitWords(query.toLowerCase) val keywords = splitWords(query.toLowerCase)
// Search Issue // Search Issue
@@ -832,12 +826,10 @@ trait IssuesService {
t.byRepository(owner, repository) && t.pullRequest === pullRequest.bind t.byRepository(owner, repository) && t.pullRequest === pullRequest.bind
} }
.join(IssueOutline) .join(IssueOutline)
.on { .on { case (t1, t2) =>
case (t1, t2) =>
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
} }
.filter { .filter { case (t1, t2) =>
case (t1, t2) =>
keywords keywords
.map { keyword => .map { keyword =>
(t1.title.toLowerCase.like(s"%${likeEncode(keyword)}%", '^')) || (t1.title.toLowerCase.like(s"%${likeEncode(keyword)}%", '^')) ||
@@ -845,8 +837,7 @@ trait IssuesService {
} }
.reduceLeft(_ && _) .reduceLeft(_ && _)
} }
.map { .map { case (t1, t2) =>
case (t1, t2) =>
(t1, 0, t1.content.?, t2.commentCount) (t1, 0, t1.content.?, t2.commentCount)
} }
@@ -854,17 +845,14 @@ trait IssuesService {
val comments = IssueComments val comments = IssueComments
.filter(_.byRepository(owner, repository)) .filter(_.byRepository(owner, repository))
.join(Issues) .join(Issues)
.on { .on { case (t1, t2) =>
case (t1, t2) =>
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
} }
.join(IssueOutline) .join(IssueOutline)
.on { .on { case ((t1, t2), t3) =>
case ((t1, t2), t3) =>
t2.byIssue(t3.userName, t3.repositoryName, t3.issueId) t2.byIssue(t3.userName, t3.repositoryName, t3.issueId)
} }
.filter { .filter { case ((t1, t2), t3) =>
case ((t1, t2), t3) =>
t2.pullRequest === pullRequest.bind && t2.pullRequest === pullRequest.bind &&
keywords keywords
.map { query => .map { query =>
@@ -872,20 +860,17 @@ trait IssuesService {
} }
.reduceLeft(_ && _) .reduceLeft(_ && _)
} }
.map { .map { case ((t1, t2), t3) =>
case ((t1, t2), t3) =>
(t2, t1.commentId, t1.content.?, t3.commentCount) (t2, t1.commentId, t1.content.?, t3.commentCount)
} }
issues issues
.union(comments) .union(comments)
.sortBy { .sortBy { case (issue, commentId, _, _) =>
case (issue, commentId, _, _) =>
issue.issueId.desc -> commentId issue.issueId.desc -> commentId
} }
.list .list
.splitWith { .splitWith { case ((issue1, _, _, _), (issue2, _, _, _)) =>
case ((issue1, _, _, _), (issue2, _, _, _)) =>
issue1.issueId == issue2.issueId issue1.issueId == issue2.issueId
} }
.map { .map {
@@ -896,8 +881,8 @@ trait IssuesService {
.toList .toList
} }
def closeIssuesFromMessage(message: String, userName: String, owner: String, repository: String)( def closeIssuesFromMessage(message: String, userName: String, owner: String, repository: String)(implicit
implicit s: Session s: Session
): Seq[Int] = { ): Seq[Int] = {
extractCloseId(message).flatMap { issueId => extractCloseId(message).flatMap { issueId =>
for (issue <- getIssue(owner, repository, issueId) if !issue.closed) yield { for (issue <- getIssue(owner, repository, issueId) if !issue.closed) yield {
@@ -911,8 +896,7 @@ trait IssuesService {
def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String, loginAccount: Account)( def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String, loginAccount: Account)(
implicit s: Session implicit s: Session
): Unit = { ): Unit = {
extractGlobalIssueId(message).foreach { extractGlobalIssueId(message).foreach { case (_referredOwner, _referredRepository, referredIssueId) =>
case (_referredOwner, _referredRepository, referredIssueId) =>
val referredOwner = _referredOwner.getOrElse(owner) val referredOwner = _referredOwner.getOrElse(owner)
val referredRepository = _referredRepository.getOrElse(repository) val referredRepository = _referredRepository.getOrElse(repository)
getRepository(referredOwner, referredRepository).foreach { repo => getRepository(referredOwner, referredRepository).foreach { repo =>
@@ -923,12 +907,13 @@ trait IssuesService {
} else { } else {
(s"${fromIssue.issueId}:${owner}:${repository}:${fromIssue.title}", "refer_global") (s"${fromIssue.issueId}:${owner}:${repository}:${fromIssue.title}", "refer_global")
} }
referredIssueId.foreach( referredIssueId.foreach(x =>
x =>
// Not add if refer comment already exist. // Not add if refer comment already exist.
if (!getComments(referredOwner, referredRepository, x.toInt).exists { x => if (
!getComments(referredOwner, referredRepository, x.toInt).exists { x =>
(x.action == "refer" || x.action == "refer_global") && x.content == content (x.action == "refer" || x.action == "refer_global") && x.content == content
}) { }
) {
createComment( createComment(
referredOwner, referredOwner,
referredRepository, referredRepository,
@@ -990,15 +975,15 @@ object IssuesService {
groups: Set[String] = Set.empty, groups: Set[String] = Set.empty,
others: Seq[CustomFieldCondition] = Nil others: Seq[CustomFieldCondition] = Nil
) { ) {
def isEmpty: Boolean = { def isEmpty: Boolean = {
labels.isEmpty && milestone.isEmpty && author.isEmpty && assigned.isEmpty && labels.isEmpty && milestone.isEmpty && author.isEmpty && assigned.isEmpty &&
state == "open" && sort == "created" && direction == "desc" && visibility.isEmpty state == "open" && sort == "created" && direction == "desc" && visibility.isEmpty && others.isEmpty
} }
def nonEmpty: Boolean = !isEmpty def nonEmpty: Boolean = !isEmpty
def toFilterString: String = def toFilterString: String = if (isEmpty) ""
else {
( (
List( List(
Some(s"is:${state}"), Some(s"is:${state}"),
@@ -1040,6 +1025,7 @@ object IssuesService {
} ++ } ++
groups.map(group => s"group:${group}") groups.map(group => s"group:${group}")
).mkString(" ") ).mkString(" ")
}
def toURL: String = { def toURL: String = {
"?" + (Seq( "?" + (Seq(
@@ -1090,8 +1076,7 @@ object IssuesService {
dim(0) -> dim(1) dim(0) -> dim(1)
} }
.groupBy(_._1) .groupBy(_._1)
.map { .map { case (key, values) =>
case (key, values) =>
key -> values.map(_._2).toSeq key -> values.map(_._2).toSeq
} }
@@ -1123,8 +1108,7 @@ object IssuesService {
val dim = x.split(">") val dim = x.split(">")
dim(0) -> ("gt", dim(1)) dim(0) -> ("gt", dim(1))
} }
.map { .map { case (key, (operator, value)) =>
case (key, (operator, value)) =>
CustomFieldCondition(key.stripPrefix("custom."), value, operator) CustomFieldCondition(key.stripPrefix("custom."), value, operator)
} }
.toSeq .toSeq
@@ -1134,7 +1118,7 @@ object IssuesService {
conditions.get("milestone").flatMap(_.headOption) match { conditions.get("milestone").flatMap(_.headOption) match {
case None => None case None => None
case Some("none") => Some(None) case Some("none") => Some(None)
case Some(x) => Some(Some(x)) //milestones.get(x).map(x => Some(x)) case Some(x) => Some(Some(x)) // milestones.get(x).map(x => Some(x))
}, },
conditions.get("priority").map(_.headOption), // TODO conditions.get("priority").map(_.headOption), // TODO
conditions.get("author").flatMap(_.headOption), conditions.get("author").flatMap(_.headOption),

View File

@@ -30,8 +30,8 @@ trait LabelsService {
createLabel(owner, repository, labelName, color) createLabel(owner, repository, labelName, color)
} }
def updateLabel(owner: String, repository: String, labelId: Int, labelName: String, color: String)( def updateLabel(owner: String, repository: String, labelId: Int, labelName: String, color: String)(implicit
implicit s: Session s: Session
): Unit = ): Unit =
Labels Labels
.filter(_.byPrimaryKey(owner, repository, labelId)) .filter(_.byPrimaryKey(owner, repository, labelId))

View File

@@ -7,7 +7,7 @@ import gitbucket.core.plugin.{PluginRegistry, ReceiveHook}
import gitbucket.core.service.RepositoryService.RepositoryInfo import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory._
import gitbucket.core.util.{JGitUtil, LockUtil} 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.model.activity.{CloseIssueInfo, MergeInfo, PushInfo}
import gitbucket.core.service.SystemSettingsService.SystemSettings import gitbucket.core.service.SystemSettingsService.SystemSettings
import gitbucket.core.service.WebHookService.WebHookPushPayload 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.lib.{CommitBuilder, ObjectId, PersonIdent, Repository}
import org.eclipse.jgit.revwalk.{RevCommit, RevWalk} import org.eclipse.jgit.revwalk.{RevCommit, RevWalk}
import scala.jdk.CollectionConverters._ import scala.jdk.CollectionConverters.*
import scala.util.Using import scala.util.Using
trait MergeService { trait MergeService {
self: AccountService self: AccountService & ActivityService & IssuesService & RepositoryService & PullRequestService &
with ActivityService WebHookPullRequestService & WebHookService =>
with IssuesService
with RepositoryService
with PullRequestService
with WebHookPullRequestService
with WebHookService =>
import MergeService._ import MergeService.*
/** /**
* Checks whether conflict will be caused in merging within pull request. * Checks whether conflict will be caused in merging within pull request.
@@ -66,15 +61,16 @@ trait MergeService {
repository: RepositoryInfo, repository: RepositoryInfo,
branch: String, branch: String,
issueId: Int, issueId: Int,
commits: Seq[RevCommit],
message: String, message: String,
loginAccount: Account, loginAccount: Account,
settings: SystemSettings 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 beforeCommitId = git.getRepository.resolve(s"refs/heads/${branch}")
val afterCommitId = new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks()) val mergeResult = new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks())
.merge(message, new PersonIdent(loginAccount.fullName, loginAccount.mailAddress), loginAccount.userName) .merge(message, new PersonIdent(loginAccount.fullName, loginAccount.mailAddress), loginAccount.userName, commits)
callWebHook(git, repository, branch, beforeCommitId, afterCommitId, loginAccount, settings) callWebHook(git, repository, branch, beforeCommitId, mergeResult.newCommitId, loginAccount, settings)
afterCommitId mergeResult
} }
/** rebase to the head of the pull request branch */ /** rebase to the head of the pull request branch */
@@ -86,13 +82,13 @@ trait MergeService {
commits: Seq[RevCommit], commits: Seq[RevCommit],
loginAccount: Account, loginAccount: Account,
settings: SystemSettings 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 beforeCommitId = git.getRepository.resolve(s"refs/heads/${branch}")
val afterCommitId = val mergeResult =
new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks()) new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks())
.rebase(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress), loginAccount.userName, commits) .rebase(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress), loginAccount.userName, commits)
callWebHook(git, repository, branch, beforeCommitId, afterCommitId, loginAccount, settings) callWebHook(git, repository, branch, beforeCommitId, mergeResult.newCommitId, loginAccount, settings)
afterCommitId mergeResult
} }
/** squash commits in the pull request and append it */ /** squash commits in the pull request and append it */
@@ -104,13 +100,13 @@ trait MergeService {
message: String, message: String,
loginAccount: Account, loginAccount: Account,
settings: SystemSettings 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 beforeCommitId = git.getRepository.resolve(s"refs/heads/${branch}")
val afterCommitId = val mergeResult =
new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks()) new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks())
.squash(message, new PersonIdent(loginAccount.fullName, loginAccount.mailAddress), loginAccount.userName) .squash(message, new PersonIdent(loginAccount.fullName, loginAccount.mailAddress), loginAccount.userName)
callWebHook(git, repository, branch, beforeCommitId, afterCommitId, loginAccount, settings) callWebHook(git, repository, branch, beforeCommitId, mergeResult.newCommitId, loginAccount, settings)
afterCommitId mergeResult
} }
private def callWebHook( private def callWebHook(
@@ -121,8 +117,8 @@ trait MergeService {
afterCommitId: ObjectId, afterCommitId: ObjectId,
loginAccount: Account, loginAccount: Account,
settings: SystemSettings settings: SystemSettings
)( )(implicit
implicit s: Session, s: Session,
c: JsonFormat.Context c: JsonFormat.Context
): Unit = { ): Unit = {
callWebHookOf(repository.owner, repository.name, WebHook.Push, settings) { callWebHookOf(repository.owner, repository.name, WebHook.Push, settings) {
@@ -220,7 +216,14 @@ trait MergeService {
requestRepositoryName: String, requestRepositoryName: String,
requestBranch: String requestBranch: String
): Option[String] = ): Option[String] =
tryMergeRemote(userName, repositoryName, branch, requestUserName, requestRepositoryName, requestBranch).left.toOption tryMergeRemote(
userName,
repositoryName,
branch,
requestUserName,
requestRepositoryName,
requestBranch
).left.toOption
def pullRemote( def pullRemote(
localRepository: RepositoryInfo, localRepository: RepositoryInfo,
@@ -236,8 +239,14 @@ trait MergeService {
val localRepositoryName = localRepository.name val localRepositoryName = localRepository.name
val remoteUserName = remoteRepository.owner val remoteUserName = remoteRepository.owner
val remoteRepositoryName = remoteRepository.name val remoteRepositoryName = remoteRepository.name
tryMergeRemote(localUserName, localRepositoryName, localBranch, remoteUserName, remoteRepositoryName, remoteBranch).map { tryMergeRemote(
case (newTreeId, oldBaseId, oldHeadId) => localUserName,
localRepositoryName,
localBranch,
remoteUserName,
remoteRepositoryName,
remoteBranch
).map { case (newTreeId, oldBaseId, oldHeadId) =>
Using.resource(Git.open(getRepositoryDir(localUserName, localRepositoryName))) { git => Using.resource(Git.open(getRepositoryDir(localUserName, localRepositoryName))) { git =>
val existIds = JGitUtil.getAllCommitIds(git).toSet val existIds = JGitUtil.getAllCommitIds(git).toSet
@@ -329,7 +338,7 @@ trait MergeService {
strategy: String, strategy: String,
isDraft: Boolean, isDraft: Boolean,
settings: SystemSettings 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 (!isDraft) {
if (repository.repository.options.mergeOptions.split(",").contains(strategy)) { if (repository.repository.options.mergeOptions.split(",").contains(strategy)) {
LockUtil.lock(s"${repository.owner}/${repository.name}") { LockUtil.lock(s"${repository.owner}/${repository.name}") {
@@ -343,7 +352,8 @@ trait MergeService {
pullRequest.commitIdFrom, pullRequest.commitIdFrom,
pullRequest.requestUserName, pullRequest.requestUserName,
pullRequest.requestRepositoryName, pullRequest.requestRepositoryName,
pullRequest.commitIdTo pullRequest.commitIdTo,
settings
) )
// merge git repository // merge git repository
@@ -371,7 +381,7 @@ trait MergeService {
"merge" "merge"
) )
createComment(repository.owner, repository.name, loginAccount.userName, issueId, "Close", "close") createComment(repository.owner, repository.name, loginAccount.userName, issueId, "Close", "close")
updateClosed(repository.owner, repository.name, issueId, true) updateClosed(repository.owner, repository.name, issueId, closed = true)
// record activity // record activity
val mergeInfo = val mergeInfo =
@@ -484,7 +494,7 @@ trait MergeService {
commits: Seq[Seq[CommitInfo]], commits: Seq[Seq[CommitInfo]],
receiveHooks: Seq[ReceiveHook], receiveHooks: Seq[ReceiveHook],
settings: SystemSettings settings: SystemSettings
)(implicit s: Session, c: JsonFormat.Context): Option[ObjectId] = { )(implicit s: Session, c: JsonFormat.Context): Option[MergeResult] = {
val revCommits = Using val revCommits = Using
.resource(new RevWalk(git.getRepository)) { revWalk => .resource(new RevWalk(git.getRepository)) { revWalk =>
commits.flatten.map { commit => commits.flatten.map { commit =>
@@ -501,6 +511,7 @@ trait MergeService {
repository, repository,
pullRequest.branch, pullRequest.branch,
issue.issueId, issue.issueId,
revCommits,
s"Merge pull request #${issue.issueId} from ${pullRequest.requestUserName}/${pullRequest.requestBranch}\n\n" + message, s"Merge pull request #${issue.issueId} from ${pullRequest.requestUserName}/${pullRequest.requestBranch}\n\n" + message,
loginAccount, loginAccount,
settings settings
@@ -549,7 +560,7 @@ object MergeService {
): ObjectId = { ): ObjectId = {
val mergeCommit = new CommitBuilder() val mergeCommit = new CommitBuilder()
mergeCommit.setTreeId(treeId) mergeCommit.setTreeId(treeId)
mergeCommit.setParentIds(parents: _*) mergeCommit.setParentIds(parents*)
mergeCommit.setAuthor(committer) mergeCommit.setAuthor(committer)
mergeCommit.setCommitter(committer) mergeCommit.setCommitter(committer)
mergeCommit.setMessage(message) mergeCommit.setMessage(message)
@@ -591,13 +602,13 @@ object MergeService {
private val mergedBranchName = s"refs/pull/${issueId}/merge" private val mergedBranchName = s"refs/pull/${issueId}/merge"
private val conflictedBranchName = s"refs/pull/${issueId}/conflict" private val conflictedBranchName = s"refs/pull/${issueId}/conflict"
lazy val mergeBaseTip = git.getRepository.resolve(s"refs/heads/${branch}") lazy val mergeBaseTip: ObjectId = git.getRepository.resolve(s"refs/heads/${branch}")
lazy val mergeTip = git.getRepository.resolve(s"refs/pull/${issueId}/head") lazy val mergeTip: ObjectId = git.getRepository.resolve(s"refs/pull/${issueId}/head")
def checkConflictCache(): Option[Option[String]] = { def checkConflictCache(): Option[Option[String]] = {
Option(git.getRepository.resolve(mergedBranchName)) Option(git.getRepository.resolve(mergedBranchName))
.flatMap { merged => .flatMap { merged =>
if (parseCommit(merged).getParents().toSet == Set(mergeBaseTip, mergeTip)) { if (parseCommit(merged).getParents.toSet == Set(mergeBaseTip, mergeTip)) {
// merged branch exists // merged branch exists
Some(None) Some(None)
} else { } else {
@@ -606,7 +617,7 @@ object MergeService {
} }
.orElse(Option(git.getRepository.resolve(conflictedBranchName)).flatMap { conflicted => .orElse(Option(git.getRepository.resolve(conflictedBranchName)).flatMap { conflicted =>
val commit = parseCommit(conflicted) val commit = parseCommit(conflicted)
if (commit.getParents().toSet == Set(mergeBaseTip, mergeTip)) { if (commit.getParents.toSet == Set(mergeBaseTip, mergeTip)) {
// conflict branch exists // conflict branch exists
Some(Some(commit.getFullMessage)) Some(Some(commit.getFullMessage))
} else { } else {
@@ -621,7 +632,8 @@ object MergeService {
def checkConflictForce(): Option[String] = { def checkConflictForce(): Option[String] = {
val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true) val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true)
val conflicted = try { val conflicted =
try {
!merger.merge(mergeBaseTip, mergeTip) !merger.merge(mergeBaseTip, mergeTip)
} catch { } catch {
case e: NoMergeBaseException => true case e: NoMergeBaseException => true
@@ -641,14 +653,16 @@ object MergeService {
None None
} else { } else {
val message = createConflictMessage(mergeTip, mergeBaseTip, merger) 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() git.branchDelete().setForce(true).setBranchNames(mergedBranchName).call()
Some(message) Some(message)
} }
} }
// update branch from cache // 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) { if (checkConflict().isDefined) {
throw new RuntimeException("This pull request can't merge automatically.") throw new RuntimeException("This pull request can't merge automatically.")
} }
@@ -656,7 +670,7 @@ object MergeService {
throw new RuntimeException(s"Not found branch ${mergedBranchName}") throw new RuntimeException(s"Not found branch ${mergedBranchName}")
}) })
// creates merge commit // 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 refName = s"refs/heads/${branch}"
val currentObjectId = git.getRepository.resolve(refName) val currentObjectId = git.getRepository.resolve(refName)
@@ -680,10 +694,10 @@ object MergeService {
hook.postReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true) 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) { if (checkConflict().isDefined) {
throw new RuntimeException("This pull request can't merge automatically.") throw new RuntimeException("This pull request can't merge automatically.")
} }
@@ -703,11 +717,13 @@ object MergeService {
val mergeBaseTipCommit = Using.resource(new RevWalk(git.getRepository))(_.parseCommit(mergeBaseTip)) val mergeBaseTipCommit = Using.resource(new RevWalk(git.getRepository))(_.parseCommit(mergeBaseTip))
var previousId = mergeBaseTipCommit.getId var previousId = mergeBaseTipCommit.getId
val mergedCommitIds = Seq.newBuilder[String]
Using.resource(git.getRepository.newObjectInserter) { inserter => Using.resource(git.getRepository.newObjectInserter) { inserter =>
commits.foreach { commit => commits.foreach { commit =>
val nextCommit = _cloneCommit(commit, previousId, mergeBaseTipCommit.getId) val nextCommit = _cloneCommit(commit, previousId, mergeBaseTipCommit.getId)
previousId = inserter.insert(nextCommit) previousId = inserter.insert(nextCommit)
mergedCommitIds += previousId.name()
} }
inserter.flush() inserter.flush()
} }
@@ -735,10 +751,10 @@ object MergeService {
hook.postReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true) 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) { if (checkConflict().isDefined) {
throw new RuntimeException("This pull request can't merge automatically.") throw new RuntimeException("This pull request can't merge automatically.")
} }
@@ -794,7 +810,7 @@ object MergeService {
hook.postReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true) hook.postReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true)
} }
objectId MergeResult(objectId, Seq(newCommitId.name()))
} }
// return treeId // return treeId
@@ -813,4 +829,5 @@ object MergeService {
mergeResults.asScala.map { case (key, _) => "- `" + key + "`\n" }.mkString mergeResults.asScala.map { case (key, _) => "- `" + key + "`\n" }.mkString
} }
case class MergeResult(newCommitId: ObjectId, mergedCommitId: Seq[String])
} }

View File

@@ -46,8 +46,8 @@ trait MilestonesService {
def getMilestone(owner: String, repository: String, milestoneId: Int)(implicit s: Session): Option[Milestone] = def getMilestone(owner: String, repository: String, milestoneId: Int)(implicit s: Session): Option[Milestone] =
Milestones.filter(_.byPrimaryKey(owner, repository, milestoneId)).firstOption Milestones.filter(_.byPrimaryKey(owner, repository, milestoneId)).firstOption
def getMilestonesWithIssueCount(owner: String, repository: String)( def getMilestonesWithIssueCount(owner: String, repository: String)(implicit
implicit s: Session s: Session
): List[(Milestone, Int, Int)] = { ): List[(Milestone, Int, Int)] = {
val counts = Issues val counts = Issues
.filter { t => .filter { t =>
@@ -78,8 +78,7 @@ trait MilestonesService {
def getApiMilestone(repository: RepositoryInfo, milestoneId: Int)(implicit s: Session): Option[ApiMilestone] = { def getApiMilestone(repository: RepositoryInfo, milestoneId: Int)(implicit s: Session): Option[ApiMilestone] = {
getMilestonesWithIssueCount(repository.owner, repository.name) getMilestonesWithIssueCount(repository.owner, repository.name)
.find(p => p._1.milestoneId == milestoneId) .find(p => p._1.milestoneId == milestoneId)
.map( .map(milestoneWithIssue =>
milestoneWithIssue =>
ApiMilestone( ApiMilestone(
repository.repository, repository.repository,
milestoneWithIssue._1, milestoneWithIssue._1,

View File

@@ -59,8 +59,7 @@ trait PrioritiesService {
Priorities Priorities
.filter(_.byRepository(owner, repository)) .filter(_.byRepository(owner, repository))
.list .list
.foreach( .foreach(p =>
p =>
Priorities Priorities
.filter(_.byPrimaryKey(owner, repository, p.priorityId)) .filter(_.byPrimaryKey(owner, repository, p.priorityId))
.map(_.ordering) .map(_.ordering)
@@ -93,8 +92,7 @@ trait PrioritiesService {
.map(_.isDefault) .map(_.isDefault)
.update(false) .update(false)
priorityId.foreach( priorityId.foreach(id =>
id =>
Priorities Priorities
.filter(_.byPrimaryKey(owner, repository, id)) .filter(_.byPrimaryKey(owner, repository, id))
.map(_.isDefault) .map(_.isDefault)

View File

@@ -1,34 +1,44 @@
package gitbucket.core.service package gitbucket.core.service
import gitbucket.core.model.{Session => _, _}
import gitbucket.core.plugin.ReceiveHook import gitbucket.core.plugin.ReceiveHook
import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile.*
import gitbucket.core.model.Profile.profile.blockingApi._ import gitbucket.core.model.Profile.profile.blockingApi.*
import gitbucket.core.model.{CommitState, ProtectedBranch, ProtectedBranchContext, ProtectedBranchRestriction, Role}
import gitbucket.core.util.SyntaxSugars.*
import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack} import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack}
trait ProtectedBranchService { trait ProtectedBranchService {
import ProtectedBranchService._ import ProtectedBranchService._
private def getProtectedBranchInfoOpt(owner: String, repository: String, branch: String)( private def getProtectedBranchInfoOpt(owner: String, repository: String, branch: String)(implicit
implicit session: Session session: Session
): Option[ProtectedBranchInfo] = ): Option[ProtectedBranchInfo] =
ProtectedBranches ProtectedBranches
.joinLeft(ProtectedBranchContexts) .joinLeft(ProtectedBranchContexts)
.on { case (pb, c) => pb.byBranch(c.userName, c.repositoryName, c.branch) } .on { case pb ~ c => pb.byBranch(c.userName, c.repositoryName, c.branch) }
.map { case (pb, c) => pb -> c.map(_.context) } .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)) .filter(_._1.byPrimaryKey(owner, repository, branch))
.list .list
.groupBy(_._1) .groupBy(_._1)
.headOption .headOption
.map { p => .map { (p: (ProtectedBranch, List[(ProtectedBranch, (Option[String], Option[String]))])) =>
p._1 -> p._2.flatMap(_._2) p._1 -> (p._2.flatMap(_._2._1), p._2.flatMap(_._2._2))
} }
.map { .map { case (t1, (contexts, users)) =>
case (t1, contexts) => new ProtectedBranchInfo(
new ProtectedBranchInfo(t1.userName, t1.repositoryName, t1.branch, true, contexts, t1.statusCheckAdmin) 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)( def getProtectedBranchInfo(owner: String, repository: String, branch: String)(implicit
implicit session: Session session: Session
): ProtectedBranchInfo = ): ProtectedBranchInfo =
getProtectedBranchInfoOpt(owner, repository, branch).getOrElse( getProtectedBranchInfoOpt(owner, repository, branch).getOrElse(
ProtectedBranchInfo.disabled(owner, repository, branch) ProtectedBranchInfo.disabled(owner, repository, branch)
@@ -41,19 +51,32 @@ trait ProtectedBranchService {
owner: String, owner: String,
repository: String, repository: String,
branch: String, branch: String,
includeAdministrators: Boolean, enforceAdmins: Boolean,
contexts: Seq[String] requiredStatusCheck: Boolean,
contexts: Seq[String],
restrictions: Boolean,
restrictionsUsers: Seq[String]
)(implicit session: Session): Unit = { )(implicit session: Session): Unit = {
disableBranchProtection(owner, repository, branch) disableBranchProtection(owner, repository, branch)
ProtectedBranches.insert(new ProtectedBranch(owner, repository, branch, includeAdministrators && contexts.nonEmpty)) ProtectedBranches.insert(
contexts.map { context => ProtectedBranch(owner, repository, branch, enforceAdmins, requiredStatusCheck, restrictions)
ProtectedBranchContexts.insert(new ProtectedBranchContext(owner, repository, branch, context)) )
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 = def disableBranchProtection(owner: String, repository: String, branch: String)(implicit session: Session): Unit =
ProtectedBranches.filter(_.byPrimaryKey(owner, repository, branch)).delete ProtectedBranches.filter(_.byPrimaryKey(owner, repository, branch)).delete
} }
object ProtectedBranchService { object ProtectedBranchService {
@@ -88,9 +111,11 @@ object ProtectedBranchService {
val branch = command.getRefName.stripPrefix("refs/heads/") val branch = command.getRefName.stripPrefix("refs/heads/")
if (branch != command.getRefName) { if (branch != command.getRefName) {
val repositoryInfo = getRepository(owner, repository) val repositoryInfo = getRepository(owner, repository)
if (command.getType == ReceiveCommand.Type.DELETE && repositoryInfo.exists( if (
command.getType == ReceiveCommand.Type.DELETE && repositoryInfo.exists(
_.repository.defaultBranch == branch _.repository.defaultBranch == branch
)) { )
) {
Some(s"refusing to delete the branch: ${command.getRefName}.") Some(s"refusing to delete the branch: ${command.getRefName}.")
} else { } else {
getProtectedBranchInfo(owner, repository, branch).getStopReason( getProtectedBranchInfo(owner, repository, branch).getStopReason(
@@ -100,6 +125,7 @@ object ProtectedBranchService {
) )
} }
} else { } else {
println("-> else")
None None
} }
} }
@@ -116,20 +142,23 @@ object ProtectedBranchService {
* When enabled, commits must first be pushed to another branch, * When enabled, commits must first be pushed to another branch,
* then merged or pushed directly to test after status checks have passed. * then merged or pushed directly to test after status checks have passed.
*/ */
contexts: Seq[String], contexts: Option[Seq[String]],
/** /**
* Include administrators * Include administrators
* Enforce required status checks for repository 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 ) extends AccountService
with RepositoryService with RepositoryService
with CommitStatusService { with CommitStatusService {
def isAdministrator(pusher: String)(implicit session: Session): Boolean = def isAdministrator(pusher: String)(implicit session: Session): Boolean =
pusher == owner || getGroupMembers(owner).exists(gm => gm.userName == pusher && gm.isManager) || pusher == owner || getGroupMembers(owner).exists(gm => gm.userName == pusher && gm.isManager) ||
getCollaborators(owner, repository).exists { getCollaborators(owner, repository).exists { case (collaborator, isGroup) =>
case (collaborator, isGroup) =>
if (collaborator.role == Role.ADMIN.name) { if (collaborator.role == Role.ADMIN.name) {
if (isGroup) { if (isGroup) {
getGroupMembers(collaborator.collaboratorName).exists(gm => gm.userName == pusher) getGroupMembers(collaborator.collaboratorName).exists(gm => gm.userName == pusher)
@@ -144,46 +173,70 @@ object ProtectedBranchService {
* Can't be deleted * Can't be deleted
* Can't have changes merged into them until required status checks pass * Can't have changes merged into them until required status checks pass
*/ */
def getStopReason(isAllowNonFastForwards: Boolean, command: ReceiveCommand, pusher: String)( def getStopReason(isAllowNonFastForwards: Boolean, command: ReceiveCommand, pusher: String)(implicit
implicit session: Session session: Session
): Option[String] = { ): Option[String] = {
if (enabled) { if (enabled) {
command.getType() match { command.getType match {
case ReceiveCommand.Type.UPDATE_NONFASTFORWARD if isAllowNonFastForwards => case ReceiveCommand.Type.UPDATE_NONFASTFORWARD if isAllowNonFastForwards =>
Some("Cannot force-push to a protected branch") Some("Cannot force-push to a protected branch")
case ReceiveCommand.Type.UPDATE | ReceiveCommand.Type.UPDATE_NONFASTFORWARD if !isPushAllowed(pusher) =>
Some("You do not have permission to push to this branch")
case ReceiveCommand.Type.UPDATE | ReceiveCommand.Type.UPDATE_NONFASTFORWARD if needStatusCheck(pusher) => case ReceiveCommand.Type.UPDATE | ReceiveCommand.Type.UPDATE_NONFASTFORWARD if needStatusCheck(pusher) =>
unSuccessedContexts(command.getNewId.name) match { unSuccessedContexts(command.getNewId.name) match {
case s if s.sizeIs == 1 => Some(s"""Required status check "${s.toSeq(0)}" is expected""") 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.size} required status checks are 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 _ => None
} }
case ReceiveCommand.Type.DELETE => case ReceiveCommand.Type.DELETE =>
Some("Cannot delete a protected branch") Some("You do not have permission to push to this branch")
case _ => None case _ => None
} }
} else { } else {
None None
} }
} }
def unSuccessedContexts(sha1: String)(implicit session: Session): Set[String] =
if (contexts.isEmpty) { def unSuccessedContexts(sha1: String)(implicit session: Session): Set[String] = {
Set.empty contexts match {
} else { case None => Set.empty
contexts.toSet -- getCommitStatuses(owner, repository, sha1) case Some(x) if x.isEmpty => Set.empty
case Some(x) =>
x.toSet -- getCommitStatuses(owner, repository, sha1)
.filter(_.state == CommitState.SUCCESS) .filter(_.state == CommitState.SUCCESS)
.map(_.context) .map(_.context)
.toSet .toSet
} }
}
def needStatusCheck(pusher: String)(implicit session: Session): Boolean = pusher match { def needStatusCheck(pusher: String)(implicit session: Session): Boolean = pusher match {
case _ if !enabled => false case _ if !enabled => false
case _ if contexts.isEmpty => false case _ if contexts.isEmpty => false
case _ if includeAdministrators => true case _ if enforceAdmins => true
case p if isAdministrator(p) => false case p if isAdministrator(p) => false
case _ => true 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 { object ProtectedBranchInfo {
def disabled(owner: String, repository: String, branch: String): ProtectedBranchInfo = def disabled(owner: String, repository: String, branch: String): ProtectedBranchInfo = {
ProtectedBranchInfo(owner, repository, branch, false, Nil, false) ProtectedBranchInfo(
owner,
repository,
branch,
enabled = false,
contexts = None,
enforceAdmins = false,
restrictionsUsers = None
)
}
} }
} }

View File

@@ -2,40 +2,35 @@ package gitbucket.core.service
import com.github.difflib.DiffUtils import com.github.difflib.DiffUtils
import com.github.difflib.patch.DeltaType import com.github.difflib.patch.DeltaType
import gitbucket.core.model.{CommitComments => _, Session => _, _}
import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile.blockingApi._
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.api.JsonFormat import gitbucket.core.api.JsonFormat
import gitbucket.core.controller.Context import gitbucket.core.controller.Context
import gitbucket.core.model.Profile.*
import gitbucket.core.model.Profile.profile.blockingApi.*
import gitbucket.core.model.activity.OpenPullRequestInfo import gitbucket.core.model.activity.OpenPullRequestInfo
import gitbucket.core.model.{CommitComments => _, Session => _, *}
import gitbucket.core.plugin.PluginRegistry import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.service.SystemSettingsService.SystemSettings import gitbucket.core.service.SystemSettingsService.SystemSettings
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory.*
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits.*
import gitbucket.core.util.JGitUtil import gitbucket.core.util.JGitUtil
import gitbucket.core.util.StringUtil._ import gitbucket.core.util.JGitUtil.{CommitInfo, DiffInfo, getBranchesNoMergeInfo}
import gitbucket.core.util.JGitUtil.{CommitInfo, DiffInfo, getBranches} import gitbucket.core.util.StringUtil.*
import gitbucket.core.view import gitbucket.core.view
import gitbucket.core.view.helpers import gitbucket.core.view.helpers
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.ObjectId import org.eclipse.jgit.lib.ObjectId
import scala.jdk.CollectionConverters._ import scala.jdk.CollectionConverters.*
import scala.util.Using import scala.util.Using
trait PullRequestService { trait PullRequestService {
self: IssuesService self: IssuesService & CommitsService & WebHookService & WebHookPullRequestService & RepositoryService & MergeService &
with CommitsService ActivityService =>
with WebHookService import PullRequestService.*
with WebHookPullRequestService
with RepositoryService
with MergeService
with ActivityService =>
import PullRequestService._
def getPullRequest(owner: String, repository: String, issueId: Int)( def getPullRequest(owner: String, repository: String, issueId: Int)(implicit
implicit s: Session s: Session
): Option[(Issue, PullRequest)] = ): Option[(Issue, PullRequest)] =
getIssue(owner, repository, issueId.toString).flatMap { issue => getIssue(owner, repository, issueId.toString).flatMap { issue =>
PullRequests.filter(_.byPrimaryKey(owner, repository, issueId)).firstOption.map { pullreq => PullRequests.filter(_.byPrimaryKey(owner, repository, issueId)).firstOption.map { pullreq =>
@@ -43,24 +38,24 @@ trait PullRequestService {
} }
} }
def updateCommitId(owner: String, repository: String, issueId: Int, commitIdTo: String, commitIdFrom: String)( def updateCommitId(owner: String, repository: String, issueId: Int, commitIdTo: String, commitIdFrom: String)(implicit
implicit s: Session s: Session
): Unit = ): Unit =
PullRequests PullRequests
.filter(_.byPrimaryKey(owner, repository, issueId)) .filter(_.byPrimaryKey(owner, repository, issueId))
.map(pr => pr.commitIdTo -> pr.commitIdFrom) .map(pr => pr.commitIdTo -> pr.commitIdFrom)
.update((commitIdTo, commitIdFrom)) .update((commitIdTo, commitIdFrom))
def updateDraftToPullRequest(owner: String, repository: String, issueId: Int)( def updateDraftToPullRequest(owner: String, repository: String, issueId: Int)(implicit
implicit s: Session s: Session
): Unit = ): Unit =
PullRequests PullRequests
.filter(_.byPrimaryKey(owner, repository, issueId)) .filter(_.byPrimaryKey(owner, repository, issueId))
.map(pr => pr.isDraft) .map(pr => pr.isDraft)
.update(false) .update(false)
def updateBaseBranch(owner: String, repository: String, issueId: Int, baseBranch: String, commitIdTo: String)( def updateBaseBranch(owner: String, repository: String, issueId: Int, baseBranch: String, commitIdTo: String)(implicit
implicit s: Session s: Session
): Unit = { ): Unit = {
PullRequests PullRequests
.filter(_.byPrimaryKey(owner, repository, issueId)) .filter(_.byPrimaryKey(owner, repository, issueId))
@@ -68,16 +63,24 @@ trait PullRequestService {
.update((baseBranch, commitIdTo)) .update((baseBranch, commitIdTo))
} }
def getPullRequestCountGroupByUser(closed: Boolean, owner: Option[String], repository: Option[String])( def updateMergedCommitIds(owner: String, repository: String, issueId: Int, mergedCommitIds: Seq[String])(implicit
implicit s: Session 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] = ): List[PullRequestCount] =
PullRequests PullRequests
.join(Issues) .join(Issues)
.on { (t1, t2) => .on { (t1, t2) =>
t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId)
} }
.filter { .filter { case (t1, t2) =>
case (t1, t2) =>
(t2.closed === closed.bind) (t2.closed === closed.bind)
.&&(t1.userName === owner.get.bind, owner.isDefined) .&&(t1.userName === owner.get.bind, owner.isDefined)
.&&(t1.repositoryName === repository.get.bind, repository.isDefined) .&&(t1.repositoryName === repository.get.bind, repository.isDefined)
@@ -132,7 +135,8 @@ trait PullRequestService {
requestBranch, requestBranch,
commitIdFrom, commitIdFrom,
commitIdTo, commitIdTo,
isDraft isDraft,
None
) )
// fetch requested branch // fetch requested branch
@@ -182,8 +186,7 @@ trait PullRequestService {
.on { (t1, t2) => .on { (t1, t2) =>
t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId)
} }
.filter { .filter { case (t1, t2) =>
case (t1, t2) =>
(t1.requestUserName === userName.bind) (t1.requestUserName === userName.bind)
.&&(t1.requestRepositoryName === repositoryName.bind) .&&(t1.requestRepositoryName === repositoryName.bind)
.&&(t1.requestBranch === branch.bind) .&&(t1.requestBranch === branch.bind)
@@ -200,8 +203,7 @@ trait PullRequestService {
.on { (t1, t2) => .on { (t1, t2) =>
t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId)
} }
.filter { .filter { case (t1, t2) =>
case (t1, t2) =>
(t1.requestUserName === userName.bind) (t1.requestUserName === userName.bind)
.&&(t1.requestRepositoryName === repositoryName.bind) .&&(t1.requestRepositoryName === repositoryName.bind)
.&&(t1.branch === branch.bind) .&&(t1.branch === branch.bind)
@@ -217,16 +219,15 @@ trait PullRequestService {
* 2. return if exists pull request to other branch * 2. return if exists pull request to other branch
* 2. return None * 2. return None
*/ */
def getPullRequestFromBranch(userName: String, repositoryName: String, branch: String, defaultBranch: String)( def getPullRequestFromBranch(userName: String, repositoryName: String, branch: String, defaultBranch: String)(implicit
implicit s: Session s: Session
): Option[(PullRequest, Issue)] = ): Option[(PullRequest, Issue)] =
PullRequests PullRequests
.join(Issues) .join(Issues)
.on { (t1, t2) => .on { (t1, t2) =>
t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId)
} }
.filter { .filter { case (t1, t2) =>
case (t1, t2) =>
(t1.requestUserName === userName.bind) && (t1.requestUserName === userName.bind) &&
(t1.requestRepositoryName === repositoryName.bind) && (t1.requestRepositoryName === repositoryName.bind) &&
(t1.requestBranch === branch.bind) && (t1.requestBranch === branch.bind) &&
@@ -247,8 +248,8 @@ trait PullRequestService {
pusherAccount: Account, pusherAccount: Account,
action: String, action: String,
settings: SystemSettings settings: SystemSettings
)( )(implicit
implicit s: Session, s: Session,
c: JsonFormat.Context c: JsonFormat.Context
): Unit = { ): Unit = {
getPullRequestsByRequest(owner, repository, branch, Some(false)).foreach { pullreq => getPullRequestsByRequest(owner, repository, branch, Some(false)).foreach { pullreq =>
@@ -273,8 +274,7 @@ trait PullRequestService {
(file, commentId, Left(oldLine)) (file, commentId, Left(oldLine))
} }
.groupBy { case (file, _, _) => file } .groupBy { case (file, _, _) => file }
.map { .map { case (file, comments) =>
case (file, comments) =>
file -> file ->
comments.map { case (_, commentId, lineNumber) => (commentId, lineNumber) } comments.map { case (_, commentId, lineNumber) => (commentId, lineNumber) }
} }
@@ -285,7 +285,8 @@ trait PullRequestService {
pullreq.requestUserName, pullreq.requestUserName,
pullreq.requestRepositoryName, pullreq.requestRepositoryName,
pullreq.commitIdTo, pullreq.commitIdTo,
commitIdTo commitIdTo,
settings
) )
// Update commit id in the PULL_REQUEST table // Update commit id in the PULL_REQUEST table
@@ -312,18 +313,17 @@ trait PullRequestService {
body: Option[String], body: Option[String],
state: Option[String], state: Option[String],
base: Option[String] base: Option[String]
)( )(implicit
implicit s: Session, s: Session,
c: JsonFormat.Context c: JsonFormat.Context
): Unit = { ): Unit = {
getPullRequest(repository.owner, repository.name, issueId).foreach { getPullRequest(repository.owner, repository.name, issueId).foreach { case (issue, pr) =>
case (issue, pr) =>
if (Repositories.filter(_.byRepository(pr.userName, pr.repositoryName)).exists.run) { if (Repositories.filter(_.byRepository(pr.userName, pr.repositoryName)).exists.run) {
// Update base branch // Update base branch
base.foreach { _base => base.foreach { _base =>
if (pr.branch != _base) { if (pr.branch != _base) {
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
getBranches(git, repository.repository.defaultBranch, origin = true) getBranchesNoMergeInfo(git)
.find(_.name == _base) .find(_.name == _base)
.foreach(br => updateBaseBranch(repository.owner, repository.name, issueId, br.name, br.commitId)) .foreach(br => updateBaseBranch(repository.owner, repository.name, issueId, br.name, br.commitId))
} }
@@ -388,8 +388,7 @@ trait PullRequestService {
.on { (t1, t2) => .on { (t1, t2) =>
t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId)
} }
.filter { .filter { case (t1, t2) =>
case (t1, t2) =>
(t1.userName === userName.bind) && (t1.userName === userName.bind) &&
(t1.repositoryName === repositoryName.bind) && (t1.repositoryName === repositoryName.bind) &&
(t1.branch === toBranch.bind) && (t1.branch === toBranch.bind) &&
@@ -407,22 +406,22 @@ trait PullRequestService {
userName: String, userName: String,
repositoryName: String, repositoryName: String,
oldCommitId: String, oldCommitId: String,
newCommitId: String newCommitId: String,
settings: SystemSettings
)(implicit s: Session): Unit = { )(implicit s: Session): Unit = {
val (_, diffs) = getRequestCompareInfo(userName, repositoryName, oldCommitId, userName, repositoryName, newCommitId) val (_, diffs) =
getRequestCompareInfo(userName, repositoryName, oldCommitId, userName, repositoryName, newCommitId, settings)
val patchs = positions.map { val patchs = positions.map { case (file, _) =>
case (file, _) =>
diffs diffs
.find(x => x.oldPath == file) .find(x => x.oldPath == file)
.map { diff => .map { diff =>
(diff.oldContent, diff.newContent) match { (diff.oldContent, diff.newContent) match {
case (Some(oldContent), Some(newContent)) => { case (Some(oldContent), Some(newContent)) =>
val oldLines = convertLineSeparator(oldContent, "LF").split("\n") val oldLines = convertLineSeparator(oldContent, "LF").split("\n")
val newLines = convertLineSeparator(newContent, "LF").split("\n") val newLines = convertLineSeparator(newContent, "LF").split("\n")
file -> Option(DiffUtils.diff(oldLines.toList.asJava, newLines.toList.asJava)) file -> Option(DiffUtils.diff(oldLines.toList.asJava, newLines.toList.asJava))
}
case _ => case _ =>
file -> None file -> None
} }
@@ -432,12 +431,10 @@ trait PullRequestService {
} }
} }
positions.foreach { positions.foreach { case (file, comments) =>
case (file, comments) =>
patchs(file) match { patchs(file) match {
case Some(patch) => case Some(patch) =>
file -> comments.foreach { file -> comments.foreach { case (commentId, lineNumber) =>
case (commentId, lineNumber) =>
lineNumber match { lineNumber match {
case Left(oldLine) => updateCommitCommentPosition(commentId, newCommitId, Some(oldLine), None) case Left(oldLine) => updateCommitCommentPosition(commentId, newCommitId, Some(oldLine), None)
case Right(newLine) => case Right(newLine) =>
@@ -445,7 +442,9 @@ trait PullRequestService {
patch.getDeltas.asScala.filter(_.getSource.getPosition < newLine).foreach { delta => patch.getDeltas.asScala.filter(_.getSource.getPosition < newLine).foreach { delta =>
delta.getType match { delta.getType match {
case DeltaType.CHANGE => case DeltaType.CHANGE =>
if (delta.getSource.getPosition <= newLine - 1 && newLine <= delta.getSource.getPosition + delta.getTarget.getLines.size) { if (
delta.getSource.getPosition <= newLine - 1 && newLine <= delta.getSource.getPosition + delta.getTarget.getLines.size
) {
counter = -1 counter = -1
} else { } else {
counter = counter + (delta.getTarget.getLines.size - delta.getSource.getLines.size) counter = counter + (delta.getTarget.getLines.size - delta.getSource.getLines.size)
@@ -461,8 +460,7 @@ trait PullRequestService {
} }
} }
case _ => case _ =>
comments.foreach { comments.foreach { case (commentId, lineNumber) =>
case (commentId, lineNumber) =>
lineNumber match { lineNumber match {
case Right(oldLine) => updateCommitCommentPosition(commentId, newCommitId, Some(oldLine), None) case Right(oldLine) => updateCommitCommentPosition(commentId, newCommitId, Some(oldLine), None)
case Left(newLine) => updateCommitCommentPosition(commentId, newCommitId, None, Some(newLine)) case Left(newLine) => updateCommitCommentPosition(commentId, newCommitId, None, Some(newLine))
@@ -512,7 +510,8 @@ trait PullRequestService {
branch: String, branch: String,
requestUserName: String, requestUserName: String,
requestRepositoryName: String, requestRepositoryName: String,
requestCommitId: String requestCommitId: String,
settings: SystemSettings
): (Seq[Seq[CommitInfo]], Seq[DiffInfo]) = ): (Seq[Seq[CommitInfo]], Seq[DiffInfo]) =
Using.resources( Using.resources(
Git.open(getRepositoryDir(userName, repositoryName)), Git.open(getRepositoryDir(userName, repositoryName)),
@@ -535,13 +534,21 @@ trait PullRequestService {
} }
// TODO Isolate to an another method? // TODO Isolate to an another method?
val diffs = JGitUtil.getDiffs(newGit, Some(oldId.getName), newId.getName, true, false) val diffs = JGitUtil.getDiffs(
git = newGit,
from = Some(oldId.getName),
to = newId.getName,
fetchContent = true,
makePatch = false,
maxFiles = settings.repositoryViewer.maxDiffFiles,
maxLines = settings.repositoryViewer.maxDiffLines
)
(commits, diffs) (commits, diffs)
} }
def getPullRequestComments(userName: String, repositoryName: String, issueId: Int, commits: Seq[CommitInfo])( def getPullRequestComments(userName: String, repositoryName: String, issueId: Int, commits: Seq[CommitInfo])(implicit
implicit s: Session s: Session
): Seq[Comment] = { ): Seq[Comment] = {
(commits.flatMap(commit => getCommitComments(userName, repositoryName, commit.id, true)) ++ getComments( (commits.flatMap(commit => getCommitComments(userName, repositoryName, commit.id, true)) ++ getComments(
userName, userName,
@@ -552,8 +559,7 @@ trait PullRequestService {
case x: CommitComment if x.fileName.isEmpty => (Some(x.commentId), None, None, None) case x: CommitComment if x.fileName.isEmpty => (Some(x.commentId), None, None, None)
case x: CommitComment => (None, x.fileName, x.originalOldLine, x.originalNewLine) case x: CommitComment => (None, x.fileName, x.originalOldLine, x.originalNewLine)
case x => throw new MatchError(x) case x => throw new MatchError(x)
} }.toSeq
.toSeq
.map { .map {
// Normal comment // Normal comment
case ((Some(_), _, _, _), comments) => case ((Some(_), _, _, _), comments) =>
@@ -578,8 +584,8 @@ trait PullRequestService {
.sortWith(_.registeredDate before _.registeredDate) .sortWith(_.registeredDate before _.registeredDate)
} }
def markMergeAndClosePullRequest(userName: String, owner: String, repository: String, pull: PullRequest)( def markMergeAndClosePullRequest(userName: String, owner: String, repository: String, pull: PullRequest)(implicit
implicit s: Session s: Session
): Unit = { ): Unit = {
createComment(owner, repository, userName, pull.issueId, "Merged by user", "merge") createComment(owner, repository, userName, pull.issueId, "Merged by user", "merge")
createComment(owner, repository, userName, pull.issueId, "Close", "close") createComment(owner, repository, userName, pull.issueId, "Close", "close")
@@ -609,8 +615,7 @@ trait PullRequestService {
Using.resources( Using.resources(
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)), Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name)) Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
) { ) { case (oldGit, newGit) =>
case (oldGit, newGit) =>
if (originRepository.branchList.contains(originId)) { if (originRepository.branchList.contains(originId)) {
val forkedId2 = val forkedId2 =
forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.commitId }.getOrElse(forkedId) forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.commitId }.getOrElse(forkedId)
@@ -657,18 +662,18 @@ object PullRequestService {
commitIdTo: String commitIdTo: String
) { ) {
val hasConflict = conflictMessage.isDefined val hasConflict: Boolean = conflictMessage.isDefined
val statuses: List[CommitStatus] = 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, _)) .map(CommitStatus.pending(branchProtection.owner, branchProtection.repository, _))
val hasRequiredStatusProblem = needStatusCheck && branchProtection.contexts.exists( val hasRequiredStatusProblem: Boolean = needStatusCheck && branchProtection.contexts
context => statuses.find(_.context == context).map(_.state) != Some(CommitState.SUCCESS) .getOrElse(Nil)
) .exists(context => !statuses.find(_.context == context).map(_.state).contains(CommitState.SUCCESS))
val hasProblem = hasRequiredStatusProblem || hasConflict || (statuses.nonEmpty && CommitState.combine( val hasProblem: Boolean = hasRequiredStatusProblem || hasConflict || (statuses.nonEmpty && CommitState.combine(
statuses.map(_.state).toSet statuses.map(_.state).toSet
) != CommitState.SUCCESS) ) != CommitState.SUCCESS)
val canUpdate = branchIsOutOfDate && !hasConflict val canUpdate: Boolean = branchIsOutOfDate && !hasConflict
val canMerge = hasMergePermission && !hasConflict && !hasRequiredStatusProblem val canMerge: Boolean = hasMergePermission && !hasConflict && !hasRequiredStatusProblem
lazy val commitStateSummary: (CommitState, String) = { lazy val commitStateSummary: (CommitState, String) = {
val stateMap = statuses.groupBy(_.state) val stateMap = statuses.groupBy(_.state)
val state = CommitState.combine(stateMap.keySet) val state = CommitState.combine(stateMap.keySet)
@@ -676,8 +681,8 @@ object PullRequestService {
state -> summary state -> summary
} }
lazy val statusesAndRequired: List[(CommitStatus, Boolean)] = statuses.map { s => 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
} }
} }

View File

@@ -8,7 +8,7 @@ import gitbucket.core.model.Profile.dateColumnType
import gitbucket.core.util.JGitUtil import gitbucket.core.util.JGitUtil
trait ReleaseService { trait ReleaseService {
self: AccountService with RepositoryService => self: AccountService & RepositoryService =>
def createReleaseAsset( def createReleaseAsset(
owner: String, owner: String,
@@ -36,14 +36,14 @@ trait ReleaseService {
ReleaseAssets.filter(x => x.byTag(owner, repository, tag)).list ReleaseAssets.filter(x => x.byTag(owner, repository, tag)).list
} }
def getReleaseAssetsMap(owner: String, repository: String, releases: Seq[ReleaseTag])( def getReleaseAssetsMap(owner: String, repository: String, releases: Seq[ReleaseTag])(implicit
implicit s: Session s: Session
): Map[ReleaseTag, Seq[ReleaseAsset]] = { ): Map[ReleaseTag, Seq[ReleaseAsset]] = {
releases.map(rel => (rel -> getReleaseAssets(owner, repository, rel.tag))).toMap releases.map(rel => (rel -> getReleaseAssets(owner, repository, rel.tag))).toMap
} }
def getReleaseAsset(owner: String, repository: String, tag: String, fileId: String)( def getReleaseAsset(owner: String, repository: String, tag: String, fileId: String)(implicit
implicit s: Session s: Session
): Option[ReleaseAsset] = { ): Option[ReleaseAsset] = {
ReleaseAssets.filter(x => x.byPrimaryKey(owner, repository, tag, fileId)) firstOption ReleaseAssets.filter(x => x.byPrimaryKey(owner, repository, tag, fileId)) firstOption
} }
@@ -76,8 +76,8 @@ trait ReleaseService {
ReleaseTags.filter(x => x.byRepository(owner, repository)).sortBy(x => x.updatedDate).list ReleaseTags.filter(x => x.byRepository(owner, repository)).sortBy(x => x.updatedDate).list
} }
def getReleases(owner: String, repository: String, tags: Seq[JGitUtil.TagInfo])( def getReleases(owner: String, repository: String, tags: Seq[JGitUtil.TagInfo])(implicit
implicit s: Session s: Session
): Seq[ReleaseTag] = { ): Seq[ReleaseTag] = {
ReleaseTags ReleaseTags
.filter(x => x.byRepository(owner, repository)) .filter(x => x.byRepository(owner, repository))
@@ -89,8 +89,8 @@ trait ReleaseService {
ReleaseTags.filter(_.byTag(owner, repository, tag)).firstOption ReleaseTags.filter(_.byTag(owner, repository, tag)).firstOption
} }
def updateRelease(owner: String, repository: String, tag: String, title: String, content: Option[String])( def updateRelease(owner: String, repository: String, tag: String, title: String, content: Option[String])(implicit
implicit s: Session s: Session
): Int = { ): Int = {
ReleaseTags ReleaseTags
.filter(_.byPrimaryKey(owner, repository, tag)) .filter(_.byPrimaryKey(owner, repository, tag))

View File

@@ -1,7 +1,7 @@
package gitbucket.core.service package gitbucket.core.service
import gitbucket.core.api.JsonFormat import gitbucket.core.api.JsonFormat
import gitbucket.core.model.{Account, WebHook} 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.model.activity.{CloseIssueInfo, PushInfo}
import gitbucket.core.plugin.PluginRegistry import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service.SystemSettingsService.SystemSettings import gitbucket.core.service.SystemSettingsService.SystemSettings
@@ -11,18 +11,14 @@ import gitbucket.core.util.JGitUtil.CommitInfo
import gitbucket.core.util.{JGitUtil, LockUtil} import gitbucket.core.util.{JGitUtil, LockUtil}
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder} 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 org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack}
import scala.util.Using import scala.util.Using
trait RepositoryCommitFileService { trait RepositoryCommitFileService {
self: AccountService self: AccountService & ActivityService & IssuesService & PullRequestService & WebHookPullRequestService &
with ActivityService RepositoryService & ProtectedBranchService =>
with IssuesService
with PullRequestService
with WebHookPullRequestService
with RepositoryService =>
/** /**
* Create multiple files by callback function. * Create multiple files by callback function.
@@ -65,7 +61,8 @@ trait RepositoryCommitFileService {
path, path,
newFileName, newFileName,
oldFileName, oldFileName,
if (content.nonEmpty) { content.getBytes(charset) } else { Array.emptyByteArray }, if (content.nonEmpty) { content.getBytes(charset) }
else { Array.emptyByteArray },
message, message,
commit, commit,
loginAccount, loginAccount,
@@ -95,10 +92,10 @@ trait RepositoryCommitFileService {
)(implicit s: Session, c: JsonFormat.Context): Either[String, (ObjectId, Option[ObjectId])] = { )(implicit s: Session, c: JsonFormat.Context): Either[String, (ObjectId, Option[ObjectId])] = {
val newPath = newFileName.map { newFileName => 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 => 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) { _createFiles(repository, branch, message, pusherAccount, committerName, committerMailAddress, settings) {
@@ -142,7 +139,6 @@ trait RepositoryCommitFileService {
)( )(
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => R f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => R
)(implicit s: Session, c: JsonFormat.Context): Either[String, (ObjectId, R)] = { )(implicit s: Session, c: JsonFormat.Context): Either[String, (ObjectId, R)] = {
LockUtil.lock(s"${repository.owner}/${repository.name}") { LockUtil.lock(s"${repository.owner}/${repository.name}") {
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
val builder = DirCache.newInCore.builder() val builder = DirCache.newInCore.builder()
@@ -171,7 +167,14 @@ trait RepositoryCommitFileService {
// call pre-commit hook // call pre-commit hook
val error = PluginRegistry().getReceiveHooks.flatMap { hook => val error = PluginRegistry().getReceiveHooks.flatMap { hook =>
hook.preReceive(repository.owner, repository.name, receivePack, receiveCommand, pusherAccount.userName, false) hook.preReceive(
repository.owner,
repository.name,
receivePack,
receiveCommand,
pusherAccount.userName,
mergePullRequest = false
)
}.headOption }.headOption
error match { error match {
@@ -197,7 +200,8 @@ trait RepositoryCommitFileService {
// record activity // record activity
updateLastActivityDate(repository.owner, repository.name) updateLastActivityDate(repository.owner, repository.name)
val commitInfo = new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId)) val commitInfo = new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
val pushInfo = PushInfo(repository.owner, repository.name, pusherAccount.userName, branch, List(commitInfo)) val pushInfo =
PushInfo(repository.owner, repository.name, pusherAccount.userName, branch, List(commitInfo))
recordActivity(pushInfo) recordActivity(pushInfo)
// create issue comment by commit message // create issue comment by commit message
@@ -224,7 +228,14 @@ trait RepositoryCommitFileService {
// call post-commit hook // call post-commit hook
PluginRegistry().getReceiveHooks.foreach { 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)) val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))

View File

@@ -2,10 +2,9 @@ package gitbucket.core.service
import java.nio.file.Files import java.nio.file.Files
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import gitbucket.core.model.Profile.profile.blockingApi.*
import gitbucket.core.model.Profile.profile.blockingApi._
import gitbucket.core.model.activity.{CreateRepositoryInfo, ForkInfo} import gitbucket.core.model.activity.{CreateRepositoryInfo, ForkInfo}
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory.*
import gitbucket.core.util.{FileUtil, JGitUtil, LockUtil} import gitbucket.core.util.{FileUtil, JGitUtil, LockUtil}
import gitbucket.core.model.{Account, Role} import gitbucket.core.model.{Account, Role}
import gitbucket.core.plugin.PluginRegistry import gitbucket.core.plugin.PluginRegistry
@@ -18,6 +17,7 @@ import org.eclipse.jgit.lib.{Constants, FileMode}
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future import scala.concurrent.Future
import scala.jdk.CollectionConverters._
import scala.util.Using import scala.util.Using
object RepositoryCreationService { object RepositoryCreationService {
@@ -46,12 +46,7 @@ object RepositoryCreationService {
} }
trait RepositoryCreationService { trait RepositoryCreationService {
self: AccountService self: AccountService & RepositoryService & LabelsService & WikiService & ActivityService & PrioritiesService =>
with RepositoryService
with LabelsService
with WikiService
with ActivityService
with PrioritiesService =>
def canCreateRepository(repositoryOwner: String, loginAccount: Account)(implicit session: Session): Boolean = { def canCreateRepository(repositoryOwner: String, loginAccount: Account)(implicit session: Session): Boolean = {
repositoryOwner == loginAccount.userName || getGroupsByUserName(loginAccount.userName) repositoryOwner == loginAccount.userName || getGroupsByUserName(loginAccount.userName)
@@ -92,7 +87,7 @@ trait RepositoryCreationService {
RepositoryCreationService.startCreation(owner, name) RepositoryCreationService.startCreation(owner, name)
try { try {
Database() withTransaction { implicit session => Database() withTransaction { implicit session =>
//val ownerAccount = getAccountByUserName(owner).get // val ownerAccount = getAccountByUserName(owner).get
val loginUserName = loginAccount.userName val loginUserName = loginAccount.userName
val copyRepositoryDir = if (initOption == "COPY") { val copyRepositoryDir = if (initOption == "COPY") {
@@ -167,6 +162,16 @@ trait RepositoryCreationService {
try { try {
Using.resource(Git.open(dir)) { git => Using.resource(Git.open(dir)) { git =>
git.push().setRemote(gitdir.toURI.toString).setPushAll().setPushTags().call() git.push().setRemote(gitdir.toURI.toString).setPushAll().setPushTags().call()
// Adjust the default branch
val branches = git.branchList().call().asScala.map(_.getName.stripPrefix("refs/heads/"))
if (!branches.contains(defaultBranch)) {
val defaultBranch = Seq("master", "main").find(branches.contains).getOrElse(branches.head)
saveRepositoryDefaultBranch(owner, name, defaultBranch)
// Change repository HEAD
Using.resource(Git.open(getRepositoryDir(owner, name))) { git =>
git.getRepository.updateRef(Constants.HEAD, true).link(Constants.R_HEADS + defaultBranch)
}
}
} }
} finally { } finally {
FileUtils.deleteQuietly(dir) FileUtils.deleteQuietly(dir)
@@ -214,8 +219,7 @@ trait RepositoryCreationService {
// Set default collaborators for the private fork // Set default collaborators for the private fork
if (repository.repository.isPrivate) { if (repository.repository.isPrivate) {
// Copy collaborators from the source repository // Copy collaborators from the source repository
getCollaborators(repository.owner, repository.name).foreach { getCollaborators(repository.owner, repository.name).foreach { case (collaborator, _) =>
case (collaborator, _) =>
addCollaborator(accountName, repository.name, collaborator.collaboratorName, collaborator.role) addCollaborator(accountName, repository.name, collaborator.collaboratorName, collaborator.role)
} }
// Register an owner of the source repository as a collaborator // Register an owner of the source repository as a collaborator

View File

@@ -14,16 +14,15 @@ import scala.util.Using
trait RepositorySearchService { self: IssuesService => trait RepositorySearchService { self: IssuesService =>
import RepositorySearchService._ import RepositorySearchService._
def countIssues(owner: String, repository: String, query: String, pullRequest: Boolean)( def countIssues(owner: String, repository: String, query: String, pullRequest: Boolean)(implicit
implicit session: Session session: Session
): Int = ): Int =
searchIssuesByKeyword(owner, repository, query, pullRequest).length searchIssuesByKeyword(owner, repository, query, pullRequest).length
def searchIssues(owner: String, repository: String, query: String, pullRequest: Boolean)( def searchIssues(owner: String, repository: String, query: String, pullRequest: Boolean)(implicit
implicit session: Session session: Session
): List[IssueSearchResult] = ): List[IssueSearchResult] =
searchIssuesByKeyword(owner, repository, query, pullRequest).map { searchIssuesByKeyword(owner, repository, query, pullRequest).map { case (issue, commentCount, content) =>
case (issue, commentCount, content) =>
IssueSearchResult( IssueSearchResult(
issue.issueId, issue.issueId,
issue.isPullRequest, issue.isPullRequest,
@@ -48,8 +47,7 @@ trait RepositorySearchService { self: IssuesService =>
} else { } else {
val files = searchRepositoryFiles(git, query) val files = searchRepositoryFiles(git, query)
val commits = JGitUtil.getLatestCommitFromPaths(git, files.map(_._1), "HEAD") val commits = JGitUtil.getLatestCommitFromPaths(git, files.map(_._1), "HEAD")
files.map { files.map { case (path, text) =>
case (path, text) =>
val (highlightText, lineNumber) = getHighlightText(text, query) val (highlightText, lineNumber) = getHighlightText(text, query)
FileSearchResult(path, commits(path).getCommitterIdent.getWhen, highlightText, lineNumber) FileSearchResult(path, commits(path).getCommitterIdent.getWhen, highlightText, lineNumber)
} }
@@ -68,8 +66,7 @@ trait RepositorySearchService { self: IssuesService =>
} else { } else {
val files = searchRepositoryFiles(git, query) val files = searchRepositoryFiles(git, query)
val commits = JGitUtil.getLatestCommitFromPaths(git, files.map(_._1), "HEAD") val commits = JGitUtil.getLatestCommitFromPaths(git, files.map(_._1), "HEAD")
files.map { files.map { case (path, text) =>
case (path, text) =>
val (highlightText, lineNumber) = getHighlightText(text, query) val (highlightText, lineNumber) = getHighlightText(text, query)
FileSearchResult( FileSearchResult(
path.stripSuffix(".md"), path.stripSuffix(".md"),

View File

@@ -120,14 +120,14 @@ trait RepositoryService {
deleteRepositoryOnModel(oldUserName, oldRepositoryName) deleteRepositoryOnModel(oldUserName, oldRepositoryName)
RepositoryWebHooks.insertAll( RepositoryWebHooks.insertAll(
webHooks.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* webHooks.map(_.copy(userName = newUserName, repositoryName = newRepositoryName))*
) )
RepositoryWebHookEvents.insertAll( RepositoryWebHookEvents.insertAll(
webHookEvents.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* webHookEvents.map(_.copy(userName = newUserName, repositoryName = newRepositoryName))*
) )
Milestones.insertAll(milestones.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*) Milestones.insertAll(milestones.map(_.copy(userName = newUserName, repositoryName = newRepositoryName))*)
Priorities.insertAll(priorities.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*) Priorities.insertAll(priorities.map(_.copy(userName = newUserName, repositoryName = newRepositoryName))*)
IssueId.insertAll(issueId.map(_.copy(_1 = newUserName, _2 = newRepositoryName)): _*) IssueId.insertAll(issueId.map(_.copy(_1 = newUserName, _2 = newRepositoryName))*)
val newMilestones = Milestones.filter(_.byRepository(newUserName, newRepositoryName)).list val newMilestones = Milestones.filter(_.byRepository(newUserName, newRepositoryName)).list
val newPriorities = Priorities.filter(_.byRepository(newUserName, newRepositoryName)).list val newPriorities = Priorities.filter(_.byRepository(newUserName, newRepositoryName)).list
@@ -145,31 +145,31 @@ trait RepositoryService {
.priorityId .priorityId
} }
) )
}: _*) }*)
PullRequests.insertAll( PullRequests.insertAll(
pullRequests.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* pullRequests.map(_.copy(userName = newUserName, repositoryName = newRepositoryName))*
) )
IssueComments.insertAll( IssueComments.insertAll(
issueComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* issueComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName))*
) )
Labels.insertAll(labels.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*) Labels.insertAll(labels.map(_.copy(userName = newUserName, repositoryName = newRepositoryName))*)
CommitComments.insertAll( CommitComments.insertAll(
commitComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* commitComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName))*
) )
CommitStatuses.insertAll( CommitStatuses.insertAll(
commitStatuses.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* commitStatuses.map(_.copy(userName = newUserName, repositoryName = newRepositoryName))*
) )
ProtectedBranches.insertAll( ProtectedBranches.insertAll(
protectedBranches.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* protectedBranches.map(_.copy(userName = newUserName, repositoryName = newRepositoryName))*
) )
ProtectedBranchContexts.insertAll( ProtectedBranchContexts.insertAll(
protectedBranchContexts.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* protectedBranchContexts.map(_.copy(userName = newUserName, repositoryName = newRepositoryName))*
) )
DeployKeys.insertAll(deployKeys.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*) DeployKeys.insertAll(deployKeys.map(_.copy(userName = newUserName, repositoryName = newRepositoryName))*)
ReleaseTags.insertAll(releases.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*) ReleaseTags.insertAll(releases.map(_.copy(userName = newUserName, repositoryName = newRepositoryName))*)
ReleaseAssets.insertAll( ReleaseAssets.insertAll(
releaseAssets.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* releaseAssets.map(_.copy(userName = newUserName, repositoryName = newRepositoryName))*
) )
// Update source repository of pull requests // Update source repository of pull requests
@@ -187,19 +187,18 @@ trait RepositoryService {
val newLabelMap = val newLabelMap =
Labels.filter(_.byRepository(newUserName, newRepositoryName)).map(x => (x.labelName, x.labelId)).list.toMap Labels.filter(_.byRepository(newUserName, newRepositoryName)).map(x => (x.labelName, x.labelId)).list.toMap
IssueLabels.insertAll( IssueLabels.insertAll(
issueLabels.map( issueLabels.map(x =>
x =>
x.copy( x.copy(
labelId = newLabelMap(oldLabelMap(x.labelId)), labelId = newLabelMap(oldLabelMap(x.labelId)),
userName = newUserName, userName = newUserName,
repositoryName = newRepositoryName repositoryName = newRepositoryName
) )
): _* )*
) )
// TODO Drop transferred owner from collaborators? // TODO Drop transferred owner from collaborators?
Collaborators.insertAll( Collaborators.insertAll(
collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName))*
) )
// Move git repository // Move git repository
@@ -275,8 +274,7 @@ trait RepositoryService {
(x.userName, x.repositoryName) (x.userName, x.repositoryName)
} }
.list .list
.foreach { .foreach { case (userName, repositoryName) =>
case (userName, repositoryName) =>
Repositories Repositories
.filter(_.byRepository(userName, repositoryName)) .filter(_.byRepository(userName, repositoryName))
.map(x => (x.originUserName ?, x.originRepositoryName ?)) .map(x => (x.originUserName ?, x.originRepositoryName ?))
@@ -292,8 +290,7 @@ trait RepositoryService {
(x.userName, x.repositoryName) (x.userName, x.repositoryName)
} }
.list .list
.foreach { .foreach { case (userName, repositoryName) =>
case (userName, repositoryName) =>
Repositories Repositories
.filter(_.byRepository(userName, repositoryName)) .filter(_.byRepository(userName, repositoryName))
.map(x => (x.parentUserName ?, x.parentRepositoryName ?)) .map(x => (x.parentUserName ?, x.parentRepositoryName ?))
@@ -321,11 +318,9 @@ trait RepositoryService {
(Repositories (Repositories
.join(Accounts) .join(Accounts)
.on(_.userName === _.userName) .on(_.userName === _.userName)
.filter { .filter { case (t1, t2) =>
case (t1, t2) =>
t1.byRepository(userName, repositoryName) && t2.removed === false.bind t1.byRepository(userName, repositoryName) && t2.removed === false.bind
} firstOption) map { } firstOption) map { case (repository, account) =>
case (repository, account) =>
// for getting issue count and pull request count // for getting issue count and pull request count
val issues = Issues val issues = Issues
.filter { t => .filter { t =>
@@ -396,8 +391,8 @@ trait RepositoryService {
* Returns the list of repositories which are owned by the specified user. * Returns the list of repositories which are owned by the specified user.
* This list includes group repositories if the specified user is a member of the group. * This list includes group repositories if the specified user is a member of the group.
*/ */
def getUserRepositories(userName: String, withoutPhysicalInfo: Boolean = false)( def getUserRepositories(userName: String, withoutPhysicalInfo: Boolean = false)(implicit
implicit s: Session s: Session
): List[RepositoryInfo] = { ): List[RepositoryInfo] = {
Repositories Repositories
.filter { t1 => .filter { t1 =>
@@ -464,8 +459,7 @@ trait RepositoryService {
Repositories Repositories
.join(Accounts) .join(Accounts)
.on(_.userName === _.userName) .on(_.userName === _.userName)
.filter { .filter { case (t1, t2) =>
case (t1, t2) =>
(t2.removed === false.bind) && ((t1.isPrivate === false.bind && !limit.bind) || (t1.userName === x.userName) || (t2.removed === false.bind) && ((t1.isPrivate === false.bind && !limit.bind) || (t1.userName === x.userName) ||
(t1.userName in GroupMembers.filter(_.userName === x.userName.bind).map(_.groupName)) || (t1.userName in GroupMembers.filter(_.userName === x.userName.bind).map(_.groupName)) ||
(Collaborators.filter { t3 => (Collaborators.filter { t3 =>
@@ -486,14 +480,13 @@ trait RepositoryService {
repositoryUserName.map { userName => repositoryUserName.map { userName =>
t.userName === userName.bind t.userName === userName.bind
} getOrElse LiteralColumn(true) } getOrElse LiteralColumn(true)
} }.sortBy(_.lastActivityDate desc)
.sortBy(_.lastActivityDate desc)
.list .list
.map(createRepositoryInfo(_, withoutPhysicalInfo)) .map(createRepositoryInfo(_, withoutPhysicalInfo))
} }
private def createRepositoryInfo(repository: Repository, withoutPhysicalInfo: Boolean = false)( private def createRepositoryInfo(repository: Repository, withoutPhysicalInfo: Boolean = false)(implicit
implicit s: Session s: Session
): RepositoryInfo = { ): RepositoryInfo = {
new RepositoryInfo( new RepositoryInfo(
if (withoutPhysicalInfo) { if (withoutPhysicalInfo) {
@@ -586,8 +579,8 @@ trait RepositoryService {
) )
} }
def saveRepositoryDefaultBranch(userName: String, repositoryName: String, defaultBranch: String)( def saveRepositoryDefaultBranch(userName: String, repositoryName: String, defaultBranch: String)(implicit
implicit s: Session s: Session
): Unit = ): Unit =
Repositories Repositories
.filter(_.byRepository(userName, repositoryName)) .filter(_.byRepository(userName, repositoryName))
@@ -599,16 +592,16 @@ trait RepositoryService {
/** /**
* Add collaborator (user or group) to the repository. * Add collaborator (user or group) to the repository.
*/ */
def addCollaborator(userName: String, repositoryName: String, collaboratorName: String, role: String)( def addCollaborator(userName: String, repositoryName: String, collaboratorName: String, role: String)(implicit
implicit s: Session s: Session
): Unit = ): Unit =
Collaborators insert Collaborator(userName, repositoryName, collaboratorName, role) Collaborators insert Collaborator(userName, repositoryName, collaboratorName, role)
/** /**
* Remove specified collaborator from the repository. * Remove specified collaborator from the repository.
*/ */
def removeCollaborator(userName: String, repositoryName: String, collaboratorName: String)( def removeCollaborator(userName: String, repositoryName: String, collaboratorName: String)(implicit
implicit s: Session s: Session
): Unit = ): Unit =
Collaborators.filter(_.byPrimaryKey(userName, repositoryName, collaboratorName)).delete Collaborators.filter(_.byPrimaryKey(userName, repositoryName, collaboratorName)).delete
@@ -634,8 +627,8 @@ trait RepositoryService {
* Returns the list of all collaborator name and permission which is sorted with ascending order. * Returns the list of all collaborator name and permission which is sorted with ascending order.
* If a group is added as a collaborator, this method returns users who are belong to that group. * If a group is added as a collaborator, this method returns users who are belong to that group.
*/ */
def getCollaboratorUserNames(userName: String, repositoryName: String, filter: Seq[Role] = Nil)( def getCollaboratorUserNames(userName: String, repositoryName: String, filter: Seq[Role] = Nil)(implicit
implicit s: Session s: Session
): List[String] = { ): List[String] = {
val q1 = Collaborators val q1 = Collaborators
.join(Accounts) .join(Accounts)
@@ -661,23 +654,23 @@ trait RepositoryService {
def hasOwnerRole(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = { def hasOwnerRole(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = {
loginAccount match { loginAccount match {
case Some(a) if (a.isAdmin) => true case Some(a) if a.isAdmin => true
case Some(a) if (a.userName == owner) => true case Some(a) if a.userName == owner => true
case Some(a) if (getGroupMembers(owner).exists(_.userName == a.userName)) => 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 Some(a) if getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN)).contains(a.userName) => true
case _ => false case _ => false
} }
} }
def hasDeveloperRole(owner: String, repository: String, loginAccount: Option[Account])( def hasDeveloperRole(owner: String, repository: String, loginAccount: Option[Account])(implicit
implicit s: Session s: Session
): Boolean = { ): Boolean = {
loginAccount match { loginAccount match {
case Some(a) if (a.isAdmin) => true case Some(a) if a.isAdmin => true
case Some(a) if (a.userName == owner) => true case Some(a) if a.userName == owner => true
case Some(a) if (getGroupMembers(owner).exists(_.userName == a.userName)) => true case Some(a) if getGroupMembers(owner).exists(_.userName == a.userName) => true
case Some(a) 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 true
case _ => false case _ => false
} }
@@ -685,12 +678,12 @@ trait RepositoryService {
def hasGuestRole(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = { def hasGuestRole(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = {
loginAccount match { loginAccount match {
case Some(a) if (a.isAdmin) => true case Some(a) if a.isAdmin => true
case Some(a) if (a.userName == owner) => true case Some(a) if a.userName == owner => true
case Some(a) if (getGroupMembers(owner).exists(_.userName == a.userName)) => true case Some(a) if getGroupMembers(owner).exists(_.userName == a.userName) => true
case Some(a) case Some(a)
if (getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER, Role.GUEST)) if getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER, Role.GUEST))
.contains(a.userName)) => .contains(a.userName) =>
true true
case _ => false case _ => false
} }
@@ -701,17 +694,29 @@ trait RepositoryService {
true true
} else { } else {
loginAccount match { loginAccount match {
case Some(x) if (x.isAdmin) => true case Some(x) if x.isAdmin => true
case Some(x) if (repository.userName == x.userName) => 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 getGroupMembers(repository.userName).exists(_.userName == x.userName) => true
case Some(x) case Some(x) if getCollaboratorUserNames(repository.userName, repository.repositoryName).contains(x.userName) =>
if (getCollaboratorUserNames(repository.userName, repository.repositoryName).contains(x.userName)) =>
true true
case _ => false 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 = private def getForkedCount(userName: String, repositoryName: String)(implicit s: Session): Int =
Query(Repositories.filter { t => Query(Repositories.filter { t =>
(t.originUserName === userName.bind) && (t.originRepositoryName === repositoryName.bind) (t.originUserName === userName.bind) && (t.originRepositoryName === repositoryName.bind)
@@ -731,7 +736,7 @@ trait RepositoryService {
(t.originUserName === userName.bind) && (t.originRepositoryName === repositoryName.bind) (t.originUserName === userName.bind) && (t.originRepositoryName === repositoryName.bind)
} }
.sortBy(_.userName asc) .sortBy(_.userName asc)
.list //.map(t => t.userName -> t.repositoryName).list .list // .map(t => t.userName -> t.repositoryName).list
private val templateExtensions = Seq("md", "markdown") private val templateExtensions = Seq("md", "markdown")

View File

@@ -25,8 +25,8 @@ trait RequestCache
private implicit def context2Session(implicit context: Context): Session = private implicit def context2Session(implicit context: Context): Session =
request2Session(context.request) request2Session(context.request)
def getIssueFromCache(userName: String, repositoryName: String, issueId: String)( def getIssueFromCache(userName: String, repositoryName: String, issueId: String)(implicit
implicit context: Context context: Context
): Option[Issue] = { ): Option[Issue] = {
context.cache(s"issue.${userName}/${repositoryName}#${issueId}") { context.cache(s"issue.${userName}/${repositoryName}#${issueId}") {
super.getIssue(userName, repositoryName, issueId) super.getIssue(userName, repositoryName, issueId)
@@ -45,19 +45,18 @@ trait RequestCache
} }
} }
def getRepositoryInfoFromCache(userName: String, repositoryName: String)( def getRepositoryInfoFromCache(userName: String, repositoryName: String)(implicit
implicit context: Context context: Context
): Option[Repository] = { ): Option[Repository] = {
context.cache(s"repository.${userName}/${repositoryName}") { context.cache(s"repository.${userName}/${repositoryName}") {
Repositories Repositories
.join(Accounts) .join(Accounts)
.on(_.userName === _.userName) .on(_.userName === _.userName)
.filter { .filter { case (t1, t2) =>
case (t1, t2) =>
t1.byRepository(userName, repositoryName) && t2.removed === false.bind t1.byRepository(userName, repositoryName) && t2.removed === false.bind
} }
.map { .map { case (t1, t2) =>
case (t1, t2) => t1 t1
} }
.firstOption .firstOption
} }

Some files were not shown because too many files have changed in this diff Show More