Compare commits

...

327 Commits

Author SHA1 Message Date
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
Naoki Takezoe
176deb4930 Bump gist plugin to 4.23.0 to support GitBucket 4.40.0 (#3399) 2023-10-22 18:09:00 +09:00
Naoki Takezoe
6e26d090ed Fix executable war for Jetty 10 (#3397) 2023-10-22 17:20:04 +09:00
Naoki Takezoe
070f74e7dc Release GitBucket 4.40.0 (#3396) 2023-10-22 02:26:54 +09:00
Naoki Takezoe
bcd78b3e5b Support custom fields in search condition (#3286) 2023-10-22 02:00:24 +09:00
Scala Steward
be8f45ce49 Update tika-core to 2.9.1 2023-10-21 10:45:17 +09:00
Scala Steward
baa96408a8 Update apache-sshd to 2.11.0 2023-10-20 18:58:23 +09:00
Scala Steward
d2a7548f73 Update github-api to 1.317 2023-10-20 10:53:01 +09:00
Scala Steward
31e3fc64fc Update oauth2-oidc-sdk to 11.4 2023-10-19 07:42:27 +09:00
Scala Steward
01a9883847 Update typesafe:config to 1.4.3 2023-10-17 19:46:41 +09:00
kenji yoshida
3cf2bea01e add JDK 21 test 2023-10-13 08:03:01 +09:00
Scala Steward
7caf11ae83 Update jetty-http, jetty-io, jetty-security, ... to 10.0.17 2023-10-10 14:04:45 +09:00
Scala Steward
b22bc6fef0 Update oauth2-oidc-sdk to 11.2 2023-10-09 18:35:04 +09:00
kenji yoshida
88533c5807 update scala 3 build setting 2023-10-09 10:34:11 +09:00
Scala Steward
53d955f198 Update mockito-core to 5.6.0 2023-10-07 07:52:10 +09:00
Scala Steward
724550ec5f Update commons-net to 3.10.0 2023-10-06 15:26:10 +09:00
Scala Steward
1a63eff8f9 Update oauth2-oidc-sdk to 11.1 2023-10-06 15:25:42 +09:00
Scala Steward
cf1b245229 Update mysql, postgresql to 1.19.1 2023-10-03 08:09:12 +09:00
Scala Steward
c54a76c9b4 Update commons-io to 2.14.0 2023-09-30 08:56:10 +09:00
Scala Steward
1a7213e2f0 Update scalatra-forms-javax, ... to 3.0.0 2023-09-25 08:38:14 +09:00
Naoki Takezoe
a386cda024 Bump to Scalatra 3.0.0-RC1 (#3376) 2023-09-24 10:22:42 +09:00
Scala Steward
f414bb4c79 Update oauth2-oidc-sdk to 11.0 2023-09-21 08:29:55 +09:00
kenji yoshida
6a51c7414a fix warning. avoid deprecated URL constructor 2023-09-20 15:47:09 +09:00
Scala Steward
9718a67a74 Update sbt-assembly to 2.1.3 2023-09-18 20:56:27 +09:00
scala-steward-bot
179f86c904 Update sbt-twirl, twirl-api to 1.6.1 (#3372)
* Update sbt-twirl, twirl-api to 1.6.1

* Revert commit(s) 9cd04872

* Update sbt-twirl, twirl-api to 1.6.1
2023-09-17 23:46:50 +00:00
Scala Steward
e6ae6fc17d Update sbt-assembly to 2.1.2 2023-09-18 07:47:50 +09:00
dependabot[bot]
1f42b52cfd Bump actions/checkout from 3 to 4 (#3366)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-17 00:00:35 +09:00
scala-steward-bot
cdff342e3d Update sbt-twirl, twirl-api to 1.6.0 (#3369) 2023-09-16 23:59:19 +09:00
Scala Steward
daa8b6acf2 Update sbt, sbt-dependency-tree to 1.9.6 2023-09-16 07:31:13 +09:00
Scala Steward
2ae72c6db4 Update oauth2-oidc-sdk to 10.15 2023-09-15 10:03:01 +09:00
Scala Steward
9e3204d546 Update sbt, sbt-dependency-tree to 1.9.5 2023-09-14 17:45:42 +09:00
kenji yoshida
59e560b8e1 add -Xsource:3 2023-09-11 07:54:58 +09:00
xuwei-k
186269f8ff add explicit type 2023-09-10 20:04:23 +09:00
Scala Steward
e441c40429 Update commons-compress to 1.24.0 2023-09-09 09:05:43 +09:00
Scala Steward
e1fa85848c Update sbt-scoverage to 2.0.9 2023-09-09 08:00:39 +09:00
Scala Steward
8d66348634 Update scala-library to 2.13.12 2023-09-07 07:45:23 +09:00
Scala Steward
f16b7d2c9b Update org.eclipse.jgit.archive, ... to 6.7.0.202309050840-r 2023-09-06 22:25:07 +09:00
Scala Steward
0ae8cc2820 Update scala3-library to 3.3.1 2023-09-06 08:26:29 +09:00
Scala Steward
ca25e7fe90 Update sbt-scalafmt to 2.5.2 2023-09-03 13:33:56 +09:00
Scala Steward
2692e29716 Update org.eclipse.jgit.archive, ... to 6.6.1.202309021850-r 2023-09-03 10:45:43 +09:00
Scala Steward
b71ccfbcc5 Update oauth2-oidc-sdk to 10.14.2 2023-09-02 10:33:01 +09:00
Scala Steward
659a157cb7 Update jetty-http, jetty-io, jetty-security, ... to 10.0.16 2023-08-31 07:47:22 +09:00
Scala Steward
f22fab32cc Update testcontainers-scala to 0.41.0 2023-08-30 07:21:12 +09:00
Scala Steward
631e418733 Update sbt-scalafmt to 2.5.1 2023-08-30 07:20:56 +09:00
Scala Steward
0f57da14e4 Update oauth2-oidc-sdk to 10.14.1 2023-08-30 07:20:40 +09:00
Scala Steward
424790e686 Update tika-core to 2.9.0 2023-08-29 07:37:26 +09:00
Scala Steward
5dc43a7f75 Update sbt, sbt-dependency-tree to 1.9.4 2023-08-25 07:51:17 +09:00
Scala Steward
debb131623 Update mockito-core to 5.5.0 2023-08-22 18:16:15 +09:00
Scala Steward
e3e21e9590 Update github-api to 1.316 2023-08-22 06:57:37 +09:00
Scala Steward
42fa7ea74d Update mysql, postgresql to 1.19.0 2023-08-22 04:59:17 +09:00
Naoki Takezoe
8ada3dde04 Allow to create pull request from default branch of forked repositories (#3339) 2023-08-13 01:48:23 +09:00
xuwei-k
d43a666dfc use mapTo. prepare Scala 3 2023-08-11 10:22:16 +09:00
Naoki Takezoe
5b266229c3 Fix AccountController (#3337) 2023-08-11 03:27:44 +09:00
Naoki Takezoe
d8b0062f2b Show activities of all visible repositories (#3336) 2023-08-11 03:11:51 +09:00
kenji yoshida
7f0859c0a3 update blocking-slick 2023-08-10 07:59:43 +09:00
Scala Steward
38c7fbf589 Update logback-classic to 1.4.11 2023-08-10 07:39:26 +09:00
Scala Steward
41335964b7 Update logback-classic to 1.4.10 2023-08-10 03:13:16 +09:00
Scala Steward
c5464b2099 Update logback-classic to 1.4.9 2023-08-08 06:31:55 +09:00
Scala Steward
dc49b0dace Update oauth2-oidc-sdk to 10.13.2 2023-08-05 10:28:40 +09:00
Scala Steward
365f30b76b Update oauth2-oidc-sdk to 10.13 2023-08-03 06:43:45 +09:00
Scala Steward
ff1bc0fbca Update oauth2-oidc-sdk to 10.12 2023-07-31 20:20:18 +09:00
Scala Steward
41d55372b5 Update sbt-license-report to 1.6.1 2023-07-31 09:52:52 +09:00
Scala Steward
ba8e5fe3cb Update sbt-license-report to 1.6.0 2023-07-30 15:15:42 +09:00
Scala Steward
6e692f70b7 Update sbt, sbt-dependency-tree to 1.9.3 2023-07-25 09:36:29 +09:00
Scala Steward
aab979b78b Update scalatra, scalatra-forms, ... to 3.0.0-M5-javax 2023-07-23 13:26:14 +09:00
scala-steward-bot
9b7856464a Update markedj to 1.0.18 (#3323) 2023-07-22 21:40:27 +09:00
kenji yoshida
903008b417 update blocking-slick 2023-07-11 09:12:15 +09:00
Scala Steward
515cf86e4f Update sbt, sbt-dependency-tree to 1.9.2 2023-07-10 12:21:48 +09:00
Scala Steward
c0b9eb8789 Update oauth2-oidc-sdk to 10.11 2023-07-09 02:31:16 +09:00
Scala Steward
fefbf92f83 Update oauth2-oidc-sdk to 10.10.1 2023-06-30 07:45:44 +09:00
Scala Steward
006fae36c9 Update thumbnailator to 0.4.20 2023-06-29 07:09:36 +09:00
Scala Steward
281f7f37d8 Update sbt, sbt-dependency-tree to 1.9.1 2023-06-26 13:51:18 +09:00
Scala Steward
36c6dd979f Update oauth2-oidc-sdk to 10.9.2 2023-06-23 06:06:28 +09:00
Scala Steward
9f44015003 Update mockito-core to 5.4.0 2023-06-18 19:47:18 +09:00
kenji yoshida
e719533ed9 Update ISSUE_TEMPLATE.md 2023-06-18 12:55:08 +09:00
Scala Steward
773221d65b Update testcontainers-scala to 0.40.17 2023-06-18 07:46:09 +09:00
Scala Steward
6beacb6af3 Update logback-classic to 1.4.8 2023-06-14 07:10:28 +09:00
Scala Steward
f11b92d391 Update org.eclipse.jgit.archive, ... to 6.6.0.202305301015-r 2023-06-13 03:58:06 +09:00
Scala Steward
def222d035 Update commons-io to 2.13.0 2023-06-08 05:36:02 +09:00
Scala Steward
5dee7a9057 Update scalatra, scalatra-forms, ... to 3.0.0-M4 2023-06-08 05:35:20 +09:00
Scala Steward
c20cc1271c Update sbt-scoverage to 2.0.8 2023-06-03 19:48:05 +09:00
Scala Steward
843d429cb2 Update scala-library to 2.13.11 2023-06-03 03:34:17 +09:00
Scala Steward
372e17ea2b Update sbt to 1.9.0 2023-06-02 20:02:47 +09:00
Scala Steward
5490e82411 Update github-api to 1.315 2023-06-01 17:02:45 +09:00
Scala Steward
d30a5d2f92 Update mysql, postgresql to 1.18.3 2023-06-01 07:52:54 +09:00
Scala Steward
ef69763749 Update mysql, postgresql to 1.18.2 2023-05-31 01:06:52 +09:00
Scala Steward
59d1250e36 Update testcontainers-scala to 0.40.16 2023-05-27 07:19:22 +09:00
Scala Steward
33bf0b7b31 Update scala3-library to 3.3.0 2023-05-24 04:35:31 +09:00
Scala Steward
884cd498ce Update commons-io to 2.12.0 2023-05-17 07:02:27 +09:00
Scala Steward
e0b021cb3e Update tika-core to 2.8.0 2023-05-16 05:42:27 +09:00
Scala Steward
57edf101fa Update sbt-license-report to 1.5.0 2023-05-15 14:49:08 +09:00
Scala Steward
a2bf0d86d8 Update apache-sshd to 2.10.0 2023-05-15 06:49:29 +09:00
Scala Steward
32e2ea5061 Update sbt, sbt-dependency-tree to 1.8.3 2023-05-13 06:57:17 +09:00
Scala Steward
e28c0592f6 Update oauth2-oidc-sdk to 10.9.1 2023-05-12 07:07:12 +09:00
Scala Steward
60627060d5 Update mysql, postgresql to 1.18.1 2023-05-11 20:08:12 +09:00
Scala Steward
cea4d7e15f Update oauth2-oidc-sdk to 10.9 2023-05-06 19:47:44 +09:00
Naoki Takezoe
ca1670b21a Make it possible to edit empty custom field value (#3288) 2023-05-06 18:34:54 +09:00
Naoki Takezoe
c9c72faaf1 Improve git push performance (#3284) 2023-05-02 12:36:53 +09:00
kenji yoshida
ba42538775 Update validation.md 2023-05-02 07:29:42 +09:00
kenji yoshida
39141d8996 http => https 2023-05-01 19:27:33 +09:00
takezoe
e3dd099bbb Update version to 4.40.0-SNAPSHOT 2023-05-01 01:20:59 +09:00
Naoki Takezoe
6b37d1027f Make the default branch configurable (#3283) 2023-04-30 22:09:19 +09:00
scala-steward-bot
b521f43c84 Update org.eclipse.jgit.archive, ... to 6.5.0.202303070854-r (#3281)
* Update org.eclipse.jgit.archive, ... to 6.5.0.202303070854-r

* Revert commit(s) 61079bb8

* Update org.eclipse.jgit.archive, ... to 6.5.0.202303070854-r
2023-04-30 14:48:48 +09:00
Scala Steward
7c5a9df759 Update jetty-http, jetty-io, jetty-security, ... to 10.0.15 2023-04-30 13:49:29 +09:00
Scala Steward
04762a3e53 Update mockito-core to 5.3.1 2023-04-30 13:49:06 +09:00
kenji yoshida
d2ba2df2ba Update .scala-steward.conf 2023-04-30 13:15:24 +09:00
kenji yoshida
0e7f34f1d7 fix scala 3 build 2023-04-30 13:13:30 +09:00
xuwei-k
d576bbfaed update scala 3 build settings 2023-04-30 13:10:32 +09:00
kenji yoshida
2bfdca6992 Update build.yml 2023-04-29 16:10:55 +09:00
scala-steward-bot
d18fdb6399 Update logback-classic to 1.4.7 (#3273)
Co-authored-by: Naoki Takezoe <takezoe@gmail.com>
2023-04-29 12:04:02 +09:00
Naoki Takezoe
fe02605544 Upgrade to Scalatra 3.0.0-M3 and drop Java 8 support (#3279)
* Bump to Scalatra 3.0.0-M1
* Bump to Scalatra 3.0.0-M3
* Format build.sbt
* Drop Java 8 support
* Fix scala-xml related warnings
* Bump sbt-scoverage to 2.0.7

---------

Co-authored-by: kenji yoshida <6b656e6a69@gmail.com>
2023-04-29 11:53:30 +09:00
scala-steward-bot
f86ae20791 Update sbt-license-report to 1.4.0 (#3278)
Co-authored-by: kenji yoshida <6b656e6a69@gmail.com>
2023-04-28 22:45:53 +00:00
Naoki Takezoe
df69f88186 Release 4.39.0 (#3277) 2023-04-29 01:46:13 +09:00
Naoki Takezoe
8d1323f354 Delete records in ISSUE_ASSIGNEE when repository is deleted (#3276) 2023-04-24 02:26:21 +09:00
Naoki Takezoe
2f598b618b Fix issues in git refs APIs (#3275) 2023-04-23 22:52:31 +09:00
Scala Steward
baf0b0b92c Update oauth2-oidc-sdk to 10.8 2023-04-23 03:07:02 +09:00
Scala Steward
27a75250a6 Update logback-classic to 1.3.7 2023-04-20 06:32:21 +09:00
Scala Steward
15f60402a5 Update oauth2-oidc-sdk to 10.7.2 2023-04-17 17:49:56 +09:00
Scala Steward
41c6fc90b3 Update testcontainers-scala to 0.40.15 2023-04-17 00:50:22 +09:00
Scala Steward
34356b04a8 Update mysql, postgresql to 1.18.0 2023-04-05 02:14:51 +09:00
Scala Steward
2ca02b6539 Update oauth2-oidc-sdk to 10.7.1 2023-03-31 03:06:07 +09:00
Scala Steward
cd0c71dffb Update commons-compress to 1.23.0 2023-03-23 07:28:32 +09:00
Scala Steward
a59120fe19 Update testcontainers-scala to 0.40.14 2023-03-22 06:59:19 +09:00
Naoki Takezoe
fdc35f48ed Update the developer doc (#3257) 2023-03-19 11:48:07 +09:00
Naoki Takezoe
bae9b7ddc3 Ignore signed-commit verification error (#3256) 2023-03-18 21:58:15 +09:00
Scala Steward
3dd9b7e587 Update postgresql to 42.6.0 2023-03-18 07:48:38 +09:00
Scala Steward
44c905bdab Update logback-classic to 1.3.6 2023-03-17 13:08:58 +09:00
Scala Steward
5214040257 Update jetty-continuation, jetty-http, ... to 9.4.51.v20230217 2023-02-28 07:00:10 +09:00
Scala Steward
7ad9f901dd Update github-api to 1.314 2023-02-27 07:09:28 +09:00
Naoki Takezoe
f472d52954 Add --disable_news_feed option to disable News Feed (#3246) 2023-02-27 02:17:52 +09:00
Naoki Takezoe
1e752af41b Update directory.md 2023-02-26 11:25:50 +09:00
Naoki Takezoe
3ba46c3fc6 Update directory.md 2023-02-26 11:25:30 +09:00
Naoki Takezoe
bf83da476f Update directory.md 2023-02-26 11:21:26 +09:00
Naoki Takezoe
6b8c4cf8d0 Add --disable_cache option to disable cache (#3245) 2023-02-24 09:42:17 +09:00
Scala Steward
445329c07a Update postgresql to 42.5.4 2023-02-17 04:14:01 +09:00
Scala Steward
8f370e19c6 Update oauth2-oidc-sdk to 10.7 2023-02-16 21:44:11 +09:00
Scala Steward
736dbcfb58 Update oauth2-oidc-sdk to 10.6 2023-02-16 03:53:15 +09:00
Scala Steward
c1cb7f87e0 Update oauth2-oidc-sdk to 10.5.2 2023-02-14 07:22:34 +09:00
Scala Steward
3c14fcefc9 Update sbt-assembly to 2.1.1 2023-02-12 15:54:07 +09:00
Scala Steward
831badf8db Update tika-core to 2.7.0 2023-02-04 09:06:43 +09:00
Scala Steward
5f8a6e8d24 Update postgresql to 42.5.3 2023-02-04 07:54:40 +09:00
Scala Steward
a3981493f7 Update postgresql to 42.5.2 2023-02-01 07:33:53 +09:00
Scala Steward
6919cf5d4d Update oauth2-oidc-sdk to 10.5.1 2023-01-27 09:50:55 +09:00
kenji yoshida
f6d1e6bdd6 pin mockito 4.x
https://github.com/mockito/mockito/pull/2804
2023-01-14 18:11:18 +09:00
Scala Steward
a13ff89acd Update oauth2-oidc-sdk to 10.5 2023-01-13 07:20:28 +09:00
Naoki Takezoe
cd5c76279a Fix input JSON schema of add/replace labels to an issue API (#3226) 2023-01-13 02:41:48 +09:00
Scala Steward
debff5e4b8 Update thumbnailator to 0.4.19 2023-01-01 06:56:02 +09:00
Scala Steward
433e207ec5 Update mockito-core to 4.11.0 2022-12-29 01:38:44 +09:00
Naoki Takezoe
3775f6a907 Fix NoSuchElementException in delete issue comment API (#3220) 2022-12-26 09:48:05 +09:00
Naoki Takezoe
10d611c0eb Call OpenID connect logout endpoint when signed-out on GitBucket (#3219) 2022-12-26 09:41:29 +09:00
takezoe
963bc4d672 Change the log file path in local dev mode 2022-12-18 22:53:20 +09:00
Naoki Takezoe
e68a21ee30 Enum support in custom fields (#3195) 2022-12-18 22:46:11 +09:00
Naoki Takezoe
d5c083b70f Upgrade logback-classic to 1.3.5 (#3218) 2022-12-18 19:39:06 +09:00
Scala Steward
2deb9cf417 Update mockito-core to 4.10.0 2022-12-15 07:49:15 +09:00
Scala Steward
fca0cfcdc7 Update oauth2-oidc-sdk to 10.4 2022-12-13 22:09:35 +09:00
Scala Steward
1466e1bdb3 Update oauth2-oidc-sdk to 10.3.1 2022-12-13 20:58:47 +09:00
Naoki Takezoe
dd48bc443a Add option to keep session in the local dev mode (#3205) 2022-12-12 08:22:41 +09:00
Scala Steward
f455738e5f Update sbt-assembly to 2.1.0 2022-12-10 07:37:40 +09:00
Scala Steward
85193803cd Update jetty-continuation, jetty-http, ... to 9.4.50.v20221201 2022-12-08 08:58:51 +09:00
Scala Steward
4e90a6074a Update oauth2-oidc-sdk to 10.3 2022-12-07 08:02:34 +09:00
Scala Steward
ca94fa5184 Update sbt-pgp to 2.2.1 2022-12-06 14:45:21 +09:00
Scala Steward
f14a7c996f Update testcontainers-scala to 0.40.12 2022-12-05 04:53:03 +09:00
Scala Steward
989d22f4d8 Update httpclient to 4.5.14 2022-12-04 22:46:38 +09:00
Scala Steward
400a812343 Update commons-net to 3.9.0 2022-12-02 09:11:27 +09:00
Scala Steward
97284f1ced Update postgresql to 42.5.1 2022-11-24 03:45:39 +09:00
Scala Steward
5e6a0d7e16 Update mysql, postgresql to 1.17.6 2022-11-16 23:28:52 +09:00
Scala Steward
599e11245f Update apache-sshd to 2.9.2 2022-11-16 07:35:21 +09:00
Scala Steward
538d714c96 Update sbt-scalafmt to 2.5.0 2022-11-15 08:53:31 +09:00
Scala Steward
953915ba2a Update mockito-core to 4.9.0 2022-11-15 05:12:27 +09:00
Naoki Takezoe
1a2f5da055 Suppress "scanned from multiple locations" warnings in development (#3194) 2022-11-12 20:17:56 +09:00
Scala Steward
749a469d37 Update tika-core to 2.6.0 2022-11-08 03:15:07 +09:00
scala-steward-bot
c7d084321a Update mariadb-java-client to 2.7.6 (#3190) 2022-11-07 13:33:23 +09:00
Naoki Takezoe
00a61cd6cf Downgrade and pin MariaDB JDBC driver version (#3189) 2022-11-05 15:58:18 +09:00
Naoki Takezoe
d9c6c13c62 Merge 4.38.4 release notes into master (#3187) 2022-11-02 13:30:02 +09:00
Scala Steward
5260c5e889 Update commons-compress to 1.22 2022-11-01 06:47:55 +09:00
Scala Steward
1700f96c62 Update sbt-pgp to 2.2.0 2022-10-30 20:47:40 +09:00
Naoki Takezoe
5a0f9f8bbb Merge 4.38.3 release notes into master 2022-10-30 11:02:49 +09:00
takezoe
8fa22b4de2 Enhance .gitignore 2022-10-30 10:58:07 +09:00
Naoki Takezoe
d17cae16fd Revert "Fix IllegalStateException when returning unknown avatar image (#3158)" (#3179)
This reverts commit a0be02ce2f.
2022-10-30 10:18:49 +09:00
Naoki Takezoe
c4c48962cf Fix an issue that assignees are not saved in PR creation (#3178) 2022-10-30 09:54:33 +09:00
Naoki Takezoe
4140e92f0b Fix an issue that assignees are not saved in PR creation (#3178) 2022-10-30 09:54:18 +09:00
Scala Steward
887e560a1b Update oauth2-oidc-sdk to 10.1 2022-10-28 07:22:22 +09:00
pea-sys
e2d70181e8 png optimization (#3176) 2022-10-27 22:32:08 +09:00
Scala Steward
148c453dbc Update oauth2-oidc-sdk to 10.0 2022-10-24 20:52:36 +09:00
Scala Steward
f6ee9d311d Update thumbnailator to 0.4.18 2022-10-24 08:45:56 +09:00
Scala Steward
35209e43bb Update mockito-core to 4.8.1 2022-10-22 06:50:07 +09:00
Scala Steward
4a3ecf063d Update sbt-assembly to 2.0.0 2022-10-18 06:54:35 +09:00
Naoki Takezoe
4c79101624 Fix duplications in issue labels and assignees (#3168) 2022-10-17 01:00:22 +09:00
Naoki Takezoe
921b01661b Upgrade Scalatra to 2.8.4 (#3165) 2022-10-16 16:49:47 +09:00
Scala Steward
c63301d8e6 Update testcontainers-scala to 0.40.11 2022-10-11 12:22:12 +09:00
Naoki Takezoe
c9ed9d2237 Hide large diffs by default and show on demand (#3157) 2022-10-10 15:49:37 +09:00
Naoki Takezoe
ca55cbe456 Disable scalafmt on compile (#3160) 2022-10-10 03:15:59 +09:00
Scala Steward
d4828613ee Update scala-library to 2.13.10 2022-10-09 05:13:06 +09:00
Naoki Takezoe
a0be02ce2f Fix IllegalStateException when returning unknown avatar image (#3158) 2022-10-08 11:24:03 +09:00
Scala Steward
9b8016a4d5 Update mysql, postgresql to 1.17.5 2022-10-05 06:25:59 +09:00
Scala Steward
8fdd3bfd21 Update tika-core to 2.5.0 2022-10-04 06:11:07 +09:00
Scala Steward
695a061e3c Update sbt, sbt-dependency-tree to 1.7.2 2022-10-03 16:05:29 +09:00
Scala Steward
bd50d9218e Update mysql, postgresql to 1.17.4 2022-09-30 06:13:37 +09:00
Scala Steward
d8f13bc1ce Update json4s-jackson to 4.0.6 2022-09-29 17:14:45 +09:00
Scala Steward
ed84d1a3c9 Update github-api to 1.313 2022-09-27 18:50:29 +09:00
Scala Steward
3c765d879c Update mariadb-java-client to 3.0.8 2022-09-21 01:05:42 +09:00
Naoki Takezoe
4c76b6dd96 Release 4.38.2 (#3141) 2022-09-20 17:52:14 +09:00
Scala Steward
7e66917993 Update scala-library to 2.13.9 2022-09-20 07:35:35 +09:00
Scala Steward
eb0f985399 Update jetty-continuation, jetty-http, ... to 9.4.49.v20220914 2022-09-16 07:13:32 +09:00
Naoki Takezoe
3a2908c3a3 Resurrect assignee icons on the issue list (#3136) 2022-09-13 23:19:38 +09:00
Naoki Takezoe
f13c10859f Fix warnings in AccountService (#3135) 2022-09-13 15:42:43 +09:00
Scala Steward
8e8a314c91 Update oauth2-oidc-sdk to 9.43.1 2022-09-10 07:07:24 +09:00
Naoki Takezoe
dad0478b87 Remove unused import statement (#3134) 2022-09-10 03:06:21 +09:00
Naoki Takezoe
240b34016d Release 4.38.1 (#3133) 2022-09-10 02:43:06 +09:00
yurafuca
2c164d630c Fix comment diff in Chrome 105 (#3131) 2022-09-10 02:06:13 +09:00
Scala Steward
3d12a9038f Update mockito-core to 4.8.0 2022-09-08 06:51:44 +09:00
Scala Steward
71fc02b5cd Update apache-sshd to 2.9.1 2022-09-08 00:03:30 +09:00
Naoki Takezoe
74caaa3d94 Fix Markdown table CSS (#3126) 2022-09-06 00:11:53 +09:00
Naoki Takezoe
e024c12521 Fix HTML rendering of multiple asignees (#3125) 2022-09-05 22:39:36 +09:00
Scala Steward
1d0c364947 Update oauth2-oidc-sdk to 9.43 2022-09-05 18:53:55 +09:00
191 changed files with 5064 additions and 4150 deletions

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

@@ -0,0 +1,2 @@
# update scalafmt
3d5ca44d66c77a46770a65a895c9737c542690f6

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

@@ -4,8 +4,12 @@
- [ ] searched for similar already existing issue - [ ] searched for similar already existing issue
- [ ] read the documentation and [wiki](https://github.com/gitbucket/gitbucket/wiki) - [ ] 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)* *(if you have performed all the above, remove the paragraph and continue describing the issue with template below)*
-->
## Issue ## Issue
**Impacted version**: xxxx **Impacted version**: xxxx

View File

@@ -8,11 +8,11 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
strategy: strategy:
matrix: matrix:
java: [8, 11, 17] java: [11, 21]
steps: steps:
- uses: actions/checkout@v3 - 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,18 +22,18 @@ 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: Run tests - name: Run tests
run: sbt scalafmtSbtCheck scalafmtCheck test:scalafmtCheck test run: sbt scalafmtSbtCheck scalafmtCheckAll test
- name: Scala 3 - name: Scala 3
run: sbt '++ 3.1.2!' update # TODO run: sbt '++ 3.x' update # TODO
- 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.*

3
.gitignore vendored
View File

@@ -2,6 +2,9 @@
*.log *.log
.ensime .ensime
.ensime_cache .ensime_cache
.DS_Store
.java-version
.tmp
# sbt specific # sbt specific
dist/* dist/*

View File

@@ -3,7 +3,6 @@ updates.limit = 3
updates.includeScala = true updates.includeScala = true
updates.pin = [ updates.pin = [
{ groupId = "org.eclipse.jetty", version = "9." } { groupId = "org.eclipse.jetty", version = "10." }
{ groupId = "org.eclipse.jgit", version = "5." } { groupId = "org.mariadb.jdbc", version = "2." }
{ groupId = "com.zaxxer", version = "4." }
] ]

View File

@@ -1,12 +1,24 @@
version = "1.5.1" version = "3.8.1"
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,6 +1,44 @@
# 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.41.0 - 18 May 2024
- Simplify pull request UI
- Fix layout of branch selector
- Integrate keyword search with filtering in the search box
- Make max files and lines limit in showing diff configurable
- Adjust the default branch automatically when cloning external repo
- Performance improvement for listing branches
- Upgrade internal libraries
## 4.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
- Improve git push performance
## 4.39.0 - 29 Apr 2023
- Support enum type in custom fields of Issues and Pull requests
- Hide large diffs by default
- Add new options to make it possible to run GitBucket using multiple machines
- Fix many API issues
## 4.38.4 - 2 Nov 2022
- Downgrade MariaDB JDBC drive to avoid unknown error
## 4.38.3 - 30 Oct 2022
- Fix several issues around multiple assignees in issues and pull requests
- Fix IllegalStateException when returning unknown avatar image
## 4.38.2 - 20 Sep 2022
- Resurrect assignee icons on the issue list
## 4.38.1 - 10 Sep 2022
- Fix comment diff in Chrome 105
- Fix Markdown table CSS
- Fix HTML rendering of multiple asignees
## 4.38.0 - 3 Sep 2022 ## 4.38.0 - 3 Sep 2022
- Support multiple assignees for Issues and Pull requests - Support multiple assignees for Issues and Pull requests
- Custom fields for issues and pull requests - Custom fields for issues and pull requests

View File

@@ -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 11**. 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**.
@@ -59,17 +59,15 @@ Support
- 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 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.38.x What's New in 4.41.x
------------- -------------
## 4.38.0 - 3 Sep 2022 ## 4.41.0 - 18 May 2024
- Support multiple assignees for Issues and Pull requests - Simplify pull request UI
- Custom fields for issues and pull requests - Fix layout of branch selector
- Reset password by users - Integrate keyword search with filtering in the search box
- Allow to configure Jetty idle timeout in standalone mode - Make max files and lines limit in showing diff configurable
- Horizontal scroll for too wide tables in Markdown - Adjust the default branch automatically when cloning external repo
- Hide header content on signin and register page - Performance improvement for listing branches
- Fix the default charset of the online editor in the repository viewer - Upgrade internal libraries
- Fix the milestone count
- Some improvements and bugfixes for WebAPI and WebHook
See the [change log](CHANGELOG.md) for all of the updates. See the [change log](CHANGELOG.md) for all of the updates.

161
build.sbt
View File

@@ -1,12 +1,12 @@
import com.typesafe.sbt.license.{DepModuleInfo, LicenseInfo} 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.38.0" val GitBucketVersion = "4.41.0"
val ScalatraVersion = "2.8.2" val ScalatraVersion = "3.0.0"
val JettyVersion = "9.4.48.v20220622" val JettyVersion = "10.0.21"
val JgitVersion = "5.13.1.202206130422-r" val JgitVersion = "6.9.0.202403050737-r"
lazy val root = (project in file(".")) lazy val root = (project in file("."))
.enablePlugins(SbtTwirl, ScalatraPlugin) .enablePlugins(SbtTwirl, ScalatraPlugin)
@@ -15,71 +15,58 @@ sourcesInBase := false
organization := Organization organization := Organization
name := Name name := Name
version := GitBucketVersion version := GitBucketVersion
scalaVersion := "2.13.8" scalaVersion := "2.13.14"
scalafmtOnCompile := true crossScalaVersions += "3.4.2"
// 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" % ScalatraVersion cross CrossVersion.for3Use2_13, "org.scalatra" %% "scalatra-javax" % ScalatraVersion,
"org.scalatra" %% "scalatra-json" % ScalatraVersion cross CrossVersion.for3Use2_13, "org.scalatra" %% "scalatra-json-javax" % ScalatraVersion,
"org.scalatra" %% "scalatra-forms" % ScalatraVersion cross CrossVersion.for3Use2_13, "org.scalatra" %% "scalatra-forms-javax" % ScalatraVersion,
"org.json4s" %% "json4s-jackson" % "4.0.5" cross CrossVersion.for3Use2_13, "org.json4s" %% "json4s-jackson" % "4.1.0-M5",
"commons-io" % "commons-io" % "2.11.0", "commons-io" % "commons-io" % "2.16.1",
"io.github.gitbucket" % "solidbase" % "1.0.5", "io.github.gitbucket" % "solidbase" % "1.1.0",
"io.github.gitbucket" % "markedj" % "1.0.17", "io.github.gitbucket" % "markedj" % "1.0.20",
"org.apache.commons" % "commons-compress" % "1.21", "org.apache.commons" % "commons-compress" % "1.26.1",
"org.apache.commons" % "commons-email" % "1.5", "org.apache.commons" % "commons-email" % "1.6.0",
"commons-net" % "commons-net" % "3.8.0", "commons-net" % "commons-net" % "3.10.0",
"org.apache.httpcomponents" % "httpclient" % "4.5.13", "org.apache.httpcomponents" % "httpclient" % "4.5.14",
"org.apache.sshd" % "apache-sshd" % "2.9.0" exclude ("org.slf4j", "slf4j-jdk14") exclude ("org.apache.sshd", "sshd-mina") exclude ("org.apache.sshd", "sshd-netty"), "org.apache.sshd" % "apache-sshd" % "2.12.1" exclude ("org.slf4j", "slf4j-jdk14") exclude ("org.apache.sshd", "sshd-mina") exclude ("org.apache.sshd", "sshd-netty"),
"org.apache.tika" % "tika-core" % "2.4.1", "org.apache.tika" % "tika-core" % "2.9.2",
"com.github.takezoe" %% "blocking-slick-32" % "0.0.12" cross CrossVersion.for3Use2_13, "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" % "1.4.199",
"org.mariadb.jdbc" % "mariadb-java-client" % "3.0.7", "org.mariadb.jdbc" % "mariadb-java-client" % "2.7.12",
"org.postgresql" % "postgresql" % "42.5.0", "org.postgresql" % "postgresql" % "42.7.3",
"ch.qos.logback" % "logback-classic" % "1.2.11", "ch.qos.logback" % "logback-classic" % "1.5.6",
"com.zaxxer" % "HikariCP" % "4.0.3" exclude ("org.slf4j", "slf4j-api"), "com.zaxxer" % "HikariCP" % "5.1.0" exclude ("org.slf4j", "slf4j-api"),
"com.typesafe" % "config" % "1.4.2", "com.typesafe" % "config" % "1.4.3",
"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.12",
"org.cache2k" % "cache2k-all" % "1.6.0.Final", "org.cache2k" % "cache2k-all" % "1.6.0.Final",
"net.coobird" % "thumbnailator" % "0.4.17", "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" % "9.42", "com.nimbusds" % "oauth2-oidc-sdk" % "11.12",
"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" % ScalatraVersion % "test" cross CrossVersion.for3Use2_13, "org.scalatra" %% "scalatra-scalatest-javax" % ScalatraVersion % "test",
"org.mockito" % "mockito-core" % "4.7.0" % "test", "org.mockito" % "mockito-core" % "5.12.0" % "test",
"com.dimafeng" %% "testcontainers-scala" % "0.40.10" % "test", "com.dimafeng" %% "testcontainers-scala" % "0.41.3" % "test",
"org.testcontainers" % "mysql" % "1.17.3" % "test", "org.testcontainers" % "mysql" % "1.19.8" % "test",
"org.testcontainers" % "postgresql" % "1.17.3" % "test", "org.testcontainers" % "postgresql" % "1.19.8" % "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" % "0.3.0",
"org.kohsuke" % "github-api" % "1.308" % "test" "org.kohsuke" % "github-api" % "1.321" % "test"
) )
libraryDependencies ~= {
_.map {
case x if x.name == "twirl-api" =>
x cross CrossVersion.for3Use2_13
case x =>
x
}
}
// Compiler settings // Compiler settings
scalacOptions := Seq( scalacOptions := Seq(
"-deprecation", "-deprecation",
@@ -89,7 +76,15 @@ scalacOptions := Seq(
"-Wunused:imports", "-Wunused:imports",
"-Wconf:cat=unused&src=twirl/.*:s,cat=unused&src=scala/gitbucket/core/model/[^/]+\\.scala:s" "-Wconf:cat=unused&src=twirl/.*:s,cat=unused&src=scala/gitbucket/core/model/[^/]+\\.scala:s"
) )
compile / javacOptions ++= Seq("-target", "8", "-source", "8") scalacOptions ++= {
scalaBinaryVersion.value match {
case "2.13" =>
Seq("-Xsource:3")
case _ =>
Nil
}
}
compile / javacOptions ++= Seq("-target", "11", "-source", "11")
Jetty / javaOptions += "-Dlogback.configurationFile=/logback-dev.xml" Jetty / javaOptions += "-Dlogback.configurationFile=/logback-dev.xml"
// Test settings // Test settings
@@ -114,8 +109,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")
} }
} }
@@ -123,15 +118,14 @@ signedArtifacts := {
val ExecutableConfig = config("executable").hide val ExecutableConfig = config("executable").hide
Keys.ivyConfigurations += ExecutableConfig Keys.ivyConfigurations += ExecutableConfig
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"org.eclipse.jetty" % "jetty-security" % JettyVersion % "executable", "org.eclipse.jetty" % "jetty-security" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "executable", "org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-continuation" % JettyVersion % "executable", "org.eclipse.jetty" % "jetty-server" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-server" % JettyVersion % "executable", "org.eclipse.jetty" % "jetty-xml" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-xml" % JettyVersion % "executable", "org.eclipse.jetty" % "jetty-http" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-http" % JettyVersion % "executable", "org.eclipse.jetty" % "jetty-servlet" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-servlet" % JettyVersion % "executable", "org.eclipse.jetty" % "jetty-io" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-io" % JettyVersion % "executable", "org.eclipse.jetty" % "jetty-util" % JettyVersion % "executable"
"org.eclipse.jetty" % "jetty-util" % JettyVersion % "executable"
) )
// Run package task before test to generate target/webapp for integration test // Run package task before test to generate target/webapp for integration test
@@ -159,8 +153,7 @@ executableKey := {
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 (jar, temp, (name: String) =>
(name startsWith "javax/") || (name startsWith "javax/") || (name startsWith "org/") || (name startsWith "META-INF/services/"))
(name startsWith "org/"))
} }
// include original war file // include original war file
@@ -185,7 +178,7 @@ executableKey := {
val url = "https://github.com/" + val url = "https://github.com/" +
s"gitbucket/gitbucket-${pluginId}-plugin/releases/download/${pluginVersion}/gitbucket-${pluginId}-plugin-${pluginVersion}.jar" s"gitbucket/gitbucket-${pluginId}-plugin/releases/download/${pluginVersion}/gitbucket-${pluginId}-plugin-${pluginVersion}.jar"
log info s"Download: ${url}" log info s"Download: ${url}"
IO transfer (new java.net.URL(url).openStream, pluginsDir / url.substring(url.lastIndexOf("/") + 1)) IO transfer (new java.net.URI(url).toURL.openStream, pluginsDir / url.substring(url.lastIndexOf("/") + 1))
case _ => () case _ => ()
} }
} }
@@ -206,10 +199,9 @@ 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)
} }
// done // done
@@ -286,6 +278,9 @@ Test / testOptions ++= {
} }
Jetty / javaOptions ++= Seq( Jetty / javaOptions ++= Seq(
"-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",
// "-Ddev-features=keep-session"
) )

View File

@@ -1,7 +1,7 @@
How to build and run from the source tree How to build and run from the source tree
======== ========
First of all, Install [sbt](http://www.scala-sbt.org/index.html). First of all, Install [sbt](https://www.scala-sbt.org/index.html).
```shell ```shell
$ brew install sbt $ brew install sbt
@@ -18,7 +18,21 @@ $ sbt ~jetty:start
Then access `http://localhost:8080/` in your browser. The default administrator account is `root` and password is `root`. Then access `http://localhost:8080/` in your browser. The default administrator account is `root` and password is `root`.
Source code modifications are detected and a reloading happens automatically. You can modify the logging configuration by editing `src/main/resources/logback-dev.xml`. Source code modifications are detected and a reloading happens automatically.
You can modify the logging configuration by editing `src/main/resources/logback-dev.xml`.
Note that HttpSession is cleared when auto-reloading happened.
This is a bit annoying when developing features that requires sign-in.
You can keep HttpSession even if GitBucket is restarted by enabling this configuration in `build.sbt`:
https://github.com/gitbucket/gitbucket/blob/d5c083b70f7f3748d080166252e9a3dcaf579648/build.sbt#L292
Or by launching GitBucket with the following command:
```shell
sbt '; set Jetty/javaOptions += "-Ddev-features=keep-session" ; ~jetty:start'
```
Note that this feature serializes HttpSession on the local disk and assigns all requests to the same session
which means you cannot test multi users behavior in this mode.
Build war file Build war file
-------- --------
@@ -37,7 +51,8 @@ To build an executable war file, run
$ sbt executable $ sbt executable
``` ```
at the top of the source tree. It generates executable `gitbucket.war` into `target/executable`. We release this war file as release artifact. at the top of the source tree. It generates executable `gitbucket.war` into `target/executable`.
We release this war file as release artifact.
Run tests spec Run tests spec
--------- ---------

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(

View File

@@ -5,13 +5,17 @@ GitBucket persists all data into __HOME/.gitbucket__ in default (In 1.9 or befor
This directory has following structure: This directory has following structure:
``` ```
* /HOME/gitbucket * /HOME/.gitbucket
* gitbucket.conf
* database.conf
* activity.log
* data.mv.db, data.trace.db (H2 data files if the default embed H2 is used)
* /repositories * /repositories
* /USER_NAME * /USER_NAME
* /REPO_NAME.git (substance of repository. GitServlet sees this directory) * /REPO_NAME.git (substance of repository. GitServlet sees this directory)
* /REPO_NAME.wiki.git (wiki repository) * /REPO_NAME.wiki.git (wiki repository)
* /REPO_NAME * /REPO_NAME
* /issues (files which are attached to issue) * /issues (files attached to issue)
* /lfs (LFS managed files) * /lfs (LFS managed files)
* /data * /data
* /USER_NAME * /USER_NAME
@@ -20,6 +24,8 @@ This directory has following structure:
* /plugins * /plugins
* plugin.jar * plugin.jar
* /.installed (copied available plugins from the parent directory automatically) * /.installed (copied available plugins from the parent directory automatically)
* /sessions
* HTTP session data created (if '--save_sessions' option is used in the standalone mode)
* /tmp * /tmp
* /_upload * /_upload
* /SESSION_ID (removed at session timeout) * /SESSION_ID (removed at session timeout)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 KiB

After

Width:  |  Height:  |  Size: 112 KiB

View File

@@ -1,6 +1,6 @@
Mapping and Validation Mapping and Validation
======== ========
GitBucket uses [scalatra-forms](http://scalatra.org/guides/2.6/formats/forms.html) to validate request parameters and map them to the scala object. This is inspired by Play2 form mapping / validation. GitBucket uses [scalatra-forms](https://scalatra.org/guides/2.8/formats/forms.html) to validate request parameters and map them to the scala object. This is inspired by Play2 form mapping / validation.
At first, define the mapping as following: At first, define the mapping as following:

View File

@@ -1 +1 @@
sbt.version=1.7.1 sbt.version=1.10.0

View File

@@ -1,11 +1,11 @@
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature") scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2")
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.1") addSbtPlugin("org.playframework.twirl" % "sbt-twirl" % "2.0.5")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "1.2.0") addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.2.0")
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.1.2") addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1")
addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0") addSbtPlugin("com.github.sbt" % "sbt-license-report" % "1.6.1")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.0") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.12")
addDependencyTreePlugin addDependencyTreePlugin

View File

@@ -65,9 +65,15 @@ public class JettyLauncher {
boolean saveSessions = false; boolean saveSessions = false;
for(String arg: args) { for(String arg: args) {
if(arg.equals("--save_sessions")) { if (arg.equals("--save_sessions")) {
saveSessions = true; saveSessions = true;
} }
if (arg.equals("--disable_news_feed")) {
System.setProperty("gitbucket.disableNewsFeed", "true");
}
if (arg.equals("--disable_cache")) {
System.setProperty("gitbucket.disableCache", "true");
}
if(arg.startsWith("--") && arg.contains("=")) { if(arg.startsWith("--") && arg.contains("=")) {
String[] dim = arg.split("=", 2); String[] dim = arg.split("=", 2);
if(dim.length == 2) { if(dim.length == 2) {
@@ -149,7 +155,7 @@ public class JettyLauncher {
} }
if (connectorsSet.contains(Connectors.HTTPS)) { if (connectorsSet.contains(Connectors.HTTPS)) {
final SslContextFactory sslContextFactory = new SslContextFactory.Server(); final SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath(requireNonNull(keyStorePath, sslContextFactory.setKeyStorePath(requireNonNull(keyStorePath,
"You must specify a path to an SSL keystore via the --key_store_path command line argument" + "You must specify a path to an SSL keystore via the --key_store_path command line argument" +

View File

@@ -1,4 +1,4 @@
notifications:1.11.0 notifications:1.11.0
gist:4.22.0 gist:4.23.0
emoji:4.6.0 emoji:4.6.0
pages:1.10.0 pages:1.10.0

View File

@@ -7,7 +7,7 @@
</appender> </appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender"> <appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>gitbucket.log</file> <file>.tmp/gitbucket.log</file>
<encoder> <encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder> </encoder>
@@ -23,4 +23,4 @@
<logger name="scala.slick.jdbc.JdbcBackend.statement" level="DEBUG" /> <logger name="scala.slick.jdbc.JdbcBackend.statement" level="DEBUG" />
--> -->
</configuration> </configuration>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<changeSet>
<addColumn tableName="CUSTOM_FIELD">
<column name="CONSTRAINTS" type="varchar(200)" nullable="true"/>
</addColumn>
</changeSet>

View File

@@ -76,21 +76,20 @@ 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"), repositoryName = rs.getString("REPOSITORY_NAME"),
repositoryName = rs.getString("REPOSITORY_NAME"), activityUserName = rs.getString("ACTIVITY_USER_NAME"),
activityUserName = rs.getString("ACTIVITY_USER_NAME"), activityType = rs.getString("ACTIVITY_TYPE"),
activityType = rs.getString("ACTIVITY_TYPE"), message = rs.getString("MESSAGE"),
message = rs.getString("MESSAGE"), additionalInfo = {
additionalInfo = { val additionalInfo = rs.getString("ADDITIONAL_INFO")
val additionalInfo = rs.getString("ADDITIONAL_INFO") if (rs.wasNull()) None else Some(additionalInfo)
if (rs.wasNull()) None else Some(additionalInfo) },
}, activityDate = rs.getTimestamp("ACTIVITY_DATE")
activityDate = rs.getTimestamp("ACTIVITY_DATE") )
)
} }
Using.resource(new FileOutputStream(ActivityLog, true)) { out => Using.resource(new FileOutputStream(ActivityLog, true)) { out =>
list.foreach { activity => list.foreach { activity =>
@@ -111,5 +110,12 @@ object GitBucketCoreModule
new Version("4.37.0", new LiquibaseMigration("update/gitbucket-core_4.37.xml")), new Version("4.37.0", new LiquibaseMigration("update/gitbucket-core_4.37.xml")),
new Version("4.37.1"), new Version("4.37.1"),
new Version("4.37.2"), new Version("4.37.2"),
new Version("4.38.0", new LiquibaseMigration("update/gitbucket-core_4.38.xml")) new Version("4.38.0", new LiquibaseMigration("update/gitbucket-core_4.38.xml")),
new Version("4.38.1"),
new Version("4.38.2"),
new Version("4.38.3"),
new Version("4.38.4"),
new Version("4.39.0", new LiquibaseMigration("update/gitbucket-core_4.39.xml")),
new Version("4.40.0"),
new Version("4.41.0")
) )

View File

@@ -0,0 +1,6 @@
package gitbucket.core.api
/**
* https://developer.github.com/v3/issues/labels/#add-labels-to-an-issue
*/
case class AddLabelsToAnIssue(labels: Seq[String])

View File

@@ -66,16 +66,17 @@ object ApiBranchProtection {
} }
} }
implicit val enforcementLevelSerializer: CustomSerializer[EnforcementLevel] = new CustomSerializer[EnforcementLevel]( implicit val enforcementLevelSerializer: CustomSerializer[EnforcementLevel] =
format => 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,16 +22,15 @@ 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, fileInfo.path,
fileInfo.path, fileInfo.id.getName,
fileInfo.id.getName, Some(Base64.getEncoder.encodeToString(arr)),
Some(Base64.getEncoder.encodeToString(arr)), Some("base64")
Some("base64") )(repositoryName)
)(repositoryName)
) )
.getOrElse(ApiContents("file", fileInfo.name, fileInfo.path, fileInfo.commitId, None, None)(repositoryName)) .getOrElse(ApiContents("file", fileInfo.name, fileInfo.path, fileInfo.commitId, None, None)(repositoryName))
} }

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

@@ -61,7 +61,7 @@ object ApiRepository {
watchers = 0, watchers = 0,
forks = 0, forks = 0,
`private` = false, `private` = false,
default_branch = "master", default_branch = "main",
owner = owner, owner = owner,
has_issues = true has_issues = true
) )

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) =>
{ Try(Date.from(Instant.parse(s))).getOrElse(throw new MappingException("Can't convert " + s + " to Date"))
case JString(s) => },
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]() +
@@ -48,29 +47,32 @@ object JsonFormat {
ApiBranchProtection.enforcementLevelSerializer ApiBranchProtection.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

@@ -185,7 +185,6 @@ trait AccountControllerBase extends AccountManagementControllerBase {
initOption: String, initOption: String,
sourceUrl: Option[String] sourceUrl: Option[String]
) )
case class ForkRepositoryForm(owner: String, name: String)
val newRepositoryForm = mapping( val newRepositoryForm = mapping(
"owner" -> trim(label("Owner", text(required, maxlength(100), identifier, existsAccount))), "owner" -> trim(label("Owner", text(required, maxlength(100), identifier, existsAccount))),
@@ -196,11 +195,6 @@ 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)
val forkRepositoryForm = mapping(
"owner" -> trim(label("Repository owner", text(required))),
"name" -> trim(label("Repository name", text(required)))
)(ForkRepositoryForm.apply)
case class AccountForm(accountName: String) case class AccountForm(accountName: String)
val accountForm = mapping( val accountForm = mapping(
@@ -221,9 +215,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
"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
@@ -268,7 +260,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
gitbucket.core.account.html.activity( gitbucket.core.account.html.activity(
account, account,
if (account.isGroupAccount) Nil else getGroupsByUserName(userName), if (account.isGroupAccount) Nil else getGroupsByUserName(userName),
getActivitiesByUser(userName, true), getActivitiesByUser(userName, publicOnly = true),
extraMailAddresses extraMailAddresses
) )
@@ -346,22 +338,21 @@ 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), fullName = form.fullName,
fullName = form.fullName, mailAddress = form.mailAddress,
mailAddress = form.mailAddress, description = form.description,
description = form.description, url = form.url
url = form.url
)
) )
)
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()
}) })
@@ -369,12 +360,11 @@ 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, 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 =>
// deleteRepository(userName, repositoryName) // deleteRepository(userName, repositoryName)
@@ -382,10 +372,10 @@ trait AccountControllerBase extends AccountManagementControllerBase {
// FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName)) // FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName)) // FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
// } // }
suspendAccount(account) suspendAccount(account)
session.invalidate session.invalidate
redirect("/") redirect("/")
} }
} getOrElse NotFound() } getOrElse NotFound()
}) })
@@ -412,7 +402,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
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()
}) })
@@ -531,9 +521,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, false)
html.edithook(webhook, events, account, false)
} }
} getOrElse NotFound() } getOrElse NotFound()
}) })
@@ -590,11 +579,10 @@ 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
) )
) )
.recover(toErrorMap), .recover(toErrorMap),
@@ -602,12 +590,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),
@@ -794,82 +781,83 @@ 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) { // redirect to the repository if repository already exists
// redirect to the repository if repository already exists redirect(s"/${form.owner}/${form.name}")
redirect(s"/${form.owner}/${form.name}") } else if (!canCreateRepository(form.owner, loginAccount)) {
} else if (!canCreateRepository(form.owner, loginAccount)) { // Permission error
// Permission error Forbidden()
Forbidden() } else {
} else { // create repository asynchronously
// create repository asynchronously createRepository(
createRepository( loginAccount,
loginAccount, form.owner,
form.owner, form.name,
form.name, form.description,
form.description, form.isPrivate,
form.isPrivate, form.initOption,
form.initOption, form.sourceUrl,
form.sourceUrl context.settings.defaultBranch
) )
// redirect to the repository // redirect to the repository
redirect(s"/${form.owner}/${form.name}") redirect(s"/${form.owner}/${form.name}")
}
} }
} else Forbidden() }
} else Forbidden()
} }
}) })
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 groups = getGroupsByUserName(loginUserName) val loginUserName = loginAccount.userName
groups match { val groups = getGroupsByUserName(loginUserName)
case _: List[String] => groups match {
val managerPermissions = groups.map { group => case _: List[String] =>
val members = getGroupMembers(group) val managerPermissions = groups.map { group =>
context.loginAccount.exists( val members = getGroupMembers(group)
x => context.loginAccount.exists(x =>
members.exists { member => members.exists { member =>
member.userName == x.userName && member.isManager member.userName == x.userName && member.isManager
} }
)
}
helper.html.forkrepository(
repository,
(groups zip managerPermissions).sortBy(_._1)
) )
case _ => redirect(s"/${loginUserName}") }
} helper.html.forkrepository(
} else BadRequest() repository,
(groups zip managerPermissions).sortBy(_._1)
)
case _ => redirect(s"/${loginUserName}")
}
} else BadRequest()
} }
}) })
post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) => post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) =>
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 accountName = form.accountName val loginUserName = loginAccount.userName
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()
} else { } else {
// 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()
} }
}) })
@@ -896,9 +884,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 (
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean } value.split(",").exists {
}) None _.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
}
) None
else Some("Must select one manager at least.") else Some("Must select one manager at least.")
} }
} }

View File

@@ -1,7 +1,6 @@
package gitbucket.core.controller package gitbucket.core.controller
import java.io.{File, FileInputStream} 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}
@@ -14,9 +13,9 @@ import org.scalatra._
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}
import is.tagomor.woothee.Classifier import is.tagomor.woothee.Classifier
import scala.util.Try import scala.util.Try
@@ -29,6 +28,9 @@ 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
import org.json4s.jackson.Serialization
import java.nio.charset.StandardCharsets
/** /**
* Provides generic features for controller implementations. * Provides generic features for controller implementations.
@@ -93,8 +95,16 @@ abstract class ControllerBase
} }
} }
private def LoginAccount: Option[Account] = private def LoginAccount: Option[Account] = {
request.getAs[Account](Keys.Session.LoginAccount).orElse(session.getAs[Account](Keys.Session.LoginAccount)) request
.getAs[Account](Keys.Session.LoginAccount)
.orElse(session.getAs[Account](Keys.Session.LoginAccount))
.orElse {
if (isDevFeatureEnabled(DevFeatures.KeepSession)) {
getLoginAccountFromLocalFile()
} else None
}
}
def ajaxGet(path: String)(action: => Any): Route = def ajaxGet(path: String)(action: => Any): Route =
super.get(path) { super.get(path) {
@@ -277,6 +287,47 @@ abstract class ControllerBase
} }
} }
} }
protected object DevFeatures {
val KeepSession = "keep-session"
}
private val loginAccountFile = new File(".tmp/login_account.json")
protected def isDevFeatureEnabled(feature: String): Boolean = {
Option(System.getProperty("dev-features")).getOrElse("").split(",").map(_.trim).contains(feature)
}
protected def getLoginAccountFromLocalFile(): Option[Account] = {
if (isDevFeatureEnabled(DevFeatures.KeepSession)) {
if (loginAccountFile.exists()) {
Using.resource(new FileInputStream(loginAccountFile)) { in =>
val json = IOUtils.toString(in, StandardCharsets.UTF_8)
val account = parse(json).extract[Account]
session.setAttribute(Keys.Session.LoginAccount, account)
Some(parse(json).extract[Account])
}
} else None
} else None
}
protected def saveLoginAccountToLocalFile(account: Account): Unit = {
if (isDevFeatureEnabled(DevFeatures.KeepSession)) {
if (!loginAccountFile.getParentFile.exists()) {
loginAccountFile.getParentFile.mkdirs()
}
Using.resource(new FileOutputStream(loginAccountFile)) { in =>
in.write(Serialization.write(account).getBytes(StandardCharsets.UTF_8))
}
}
}
protected def deleteLoginAccountFromLocalFile(): Unit = {
if (isDevFeatureEnabled(DevFeatures.KeepSession)) {
loginAccountFile.delete()
}
}
} }
/** /**
@@ -372,10 +423,11 @@ 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, true)
@@ -395,10 +447,11 @@ 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) => Some(value) == params.optionValue("mailAddress") || 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, true)

View File

@@ -6,6 +6,7 @@ import gitbucket.core.service._
import gitbucket.core.util.{Keys, UsersAuthenticator} import gitbucket.core.util.{Keys, UsersAuthenticator}
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.service.IssuesService._ import gitbucket.core.service.IssuesService._
import gitbucket.core.service.ActivityService._
class DashboardController class DashboardController
extends DashboardControllerBase extends DashboardControllerBase
@@ -42,7 +43,7 @@ trait DashboardControllerBase extends ControllerBase {
withoutPhysicalInfo = true, withoutPhysicalInfo = true,
limit = context.settings.basicBehavior.limitVisibleRepositories limit = context.settings.basicBehavior.limitVisibleRepositories
) )
html.repos(getGroupNames(loginAccount.userName), repos, repos) html.repos(getGroupNames(loginAccount.userName), repos, repos, isNewsFeedEnabled)
} }
}) })
@@ -111,13 +112,13 @@ trait DashboardControllerBase extends ControllerBase {
val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName) val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName)
val userRepos = getUserRepositories(userName, true).map(repo => repo.owner -> repo.name) val userRepos = getUserRepositories(userName, true).map(repo => repo.owner -> repo.name)
val page = IssueSearchCondition.page(request) val page = IssueSearchCondition.page(request)
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))
@@ -130,7 +131,8 @@ trait DashboardControllerBase extends ControllerBase {
None, None,
withoutPhysicalInfo = true, withoutPhysicalInfo = true,
limit = context.settings.basicBehavior.limitVisibleRepositories limit = context.settings.basicBehavior.limitVisibleRepositories
) ),
isNewsFeedEnabled
) )
} }
@@ -147,7 +149,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 =>
@@ -158,8 +160,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))
@@ -172,7 +174,8 @@ trait DashboardControllerBase extends ControllerBase {
None, None,
withoutPhysicalInfo = true, withoutPhysicalInfo = true,
limit = context.settings.basicBehavior.limitVisibleRepositories limit = context.settings.basicBehavior.limitVisibleRepositories
) ),
isNewsFeedEnabled
) )
} }

View File

@@ -75,59 +75,57 @@ 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")
// Check whether logged-in user is collaborator // Check whether logged-in user is collaborator
onlyWikiEditable(owner, repository, loginAccount) { onlyWikiEditable(owner, repository, loginAccount) {
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}")
if (headId != null) { if (headId != null) {
JGitUtil.processTree(git, headId) { (path, tree) => JGitUtil.processTree(git, headId) { (path, tree) =>
if (path != fileName) { if (path != fileName) {
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
}
}
} }
}
val bytes = IOUtils.toByteArray(file.getInputStream)
builder.add(
JGitUtil.createDirCacheEntry(
fileName,
FileMode.REGULAR_FILE,
inserter.insert(Constants.OBJ_BLOB, bytes)
)
)
builder.finish()
val newHeadId = JGitUtil.createNewCommit(
git,
inserter,
headId,
builder.getDirCache.writeTree(inserter),
Constants.HEAD,
loginAccount.fullName,
loginAccount.mailAddress,
s"Uploaded ${fileName}"
)
fileName
} }
val bytes = IOUtils.toByteArray(file.getInputStream)
builder.add(
JGitUtil.createDirCacheEntry(
fileName,
FileMode.REGULAR_FILE,
inserter.insert(Constants.OBJ_BLOB, bytes)
)
)
builder.finish()
val newHeadId = JGitUtil.createNewCommit(
git,
inserter,
headId,
builder.getDirCache.writeTree(inserter),
Constants.HEAD,
loginAccount.fullName,
loginAccount.mailAddress,
s"Uploaded ${fileName}"
)
fileName
} }
}, }
_ => true },
) _ => true
} )
}
} getOrElse BadRequest() } getOrElse BadRequest()
} }
@@ -135,20 +133,19 @@ 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 execute(
execute( { (file, fileId) =>
{ (file, fileId) => FileUtils.writeByteArrayToFile(
FileUtils.writeByteArrayToFile( new File(getReleaseFilesDir(owner, repository), FileUtil.checkFilename(tag + "/" + fileId)),
new File(getReleaseFilesDir(owner, repository), FileUtil.checkFilename(tag + "/" + fileId)), file.get()
file.get() )
) },
}, _ => true
_ => true )
)
} }
.getOrElse(BadRequest()) .getOrElse(BadRequest())
} }
@@ -158,9 +155,12 @@ class FileUploadController
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(
request2Session(request).conn.importAsSQL(file.getInputStream) { (file, fileId) =>
}, _ => true) request2Session(request).conn.importAsSQL(file.getInputStream)
},
_ => true
)
} }
redirect("/admin/data") redirect("/admin/data")
} }

View File

@@ -1,7 +1,8 @@
package gitbucket.core.controller package gitbucket.core.controller
import java.net.URI import com.nimbusds.jwt.JWT
import java.net.URI
import com.nimbusds.oauth2.sdk.id.State 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
@@ -13,6 +14,8 @@ 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._
class IndexController class IndexController
extends IndexControllerBase extends IndexControllerBase
with RepositoryService with RepositoryService
@@ -57,30 +60,41 @@ trait IndexControllerBase extends ControllerBase {
// //
// case class SearchForm(query: String, owner: String, repository: String) // case class SearchForm(query: String, owner: String, repository: String)
case class OidcContext(state: State, nonce: Nonce, redirectBackURI: String) case class OidcAuthContext(state: State, nonce: Nonce, redirectBackURI: String)
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)
gitbucket.core.html.index( if (!isNewsFeedEnabled) {
getRecentActivitiesByOwners(visibleOwnerSet), redirect("/dashboard/repos")
getVisibleRepositories( } else {
val repos = getVisibleRepositories(
Some(account), Some(account),
None, None,
withoutPhysicalInfo = true, withoutPhysicalInfo = true,
limit = context.settings.basicBehavior.limitVisibleRepositories limit = false
),
showBannerToCreatePersonalAccessToken = hasAccountFederation(account.userName) && !hasAccessToken(
account.userName
) )
)
gitbucket.core.html.index(
activities = getRecentActivitiesByRepos(repos.map(x => (x.owner, x.name)).toSet),
recentRepositories = if (context.settings.basicBehavior.limitVisibleRepositories) {
repos.filter(x => x.owner == account.userName)
} else repos,
showBannerToCreatePersonalAccessToken = hasAccountFederation(account.userName) && !hasAccessToken(
account.userName
),
enableNewsFeed = isNewsFeedEnabled
)
}
} }
.getOrElse { .getOrElse {
gitbucket.core.html.index( gitbucket.core.html.index(
getRecentPublicActivities(), activities = getRecentPublicActivities(),
getVisibleRepositories(None, withoutPhysicalInfo = true), recentRepositories = getVisibleRepositories(None, withoutPhysicalInfo = true),
showBannerToCreatePersonalAccessToken = false showBannerToCreatePersonalAccessToken = false,
enableNewsFeed = isNewsFeedEnabled
) )
} }
} }
@@ -120,8 +134,8 @@ trait IndexControllerBase extends ControllerBase {
case _ => "/" case _ => "/"
} }
session.setAttribute( session.setAttribute(
Keys.Session.OidcContext, Keys.Session.OidcAuthContext,
OidcContext(authenticationRequest.getState, authenticationRequest.getNonce, redirectBackURI) OidcAuthContext(authenticationRequest.getState, authenticationRequest.getNonce, redirectBackURI)
) )
redirect(authenticationRequest.toURI.toString) redirect(authenticationRequest.toURI.toString)
} getOrElse { } getOrElse {
@@ -135,9 +149,10 @@ trait IndexControllerBase extends ControllerBase {
get("/signin/oidc") { get("/signin/oidc") {
context.settings.oidc.map { oidc => context.settings.oidc.map { oidc =>
val redirectURI = new URI(s"$baseUrl/signin/oidc") val redirectURI = new URI(s"$baseUrl/signin/oidc")
session.get(Keys.Session.OidcContext) match { session.get(Keys.Session.OidcAuthContext) match {
case Some(context: OidcContext) => case Some(context: OidcAuthContext) =>
authenticate(params.toMap, redirectURI, context.state, context.nonce, oidc).map { account => authenticate(params.toMap, redirectURI, context.state, context.nonce, oidc).map { case (jwt, account) =>
session.setAttribute(Keys.Session.OidcSessionContext, OidcSessionContext(jwt))
signin(account, context.redirectBackURI) signin(account, context.redirectBackURI)
} orElse { } orElse {
flash.update("error", "Sorry, authentication failed. Please try again.") flash.update("error", "Sorry, authentication failed. Please try again.")
@@ -155,7 +170,18 @@ trait IndexControllerBase extends ControllerBase {
} }
get("/signout") { get("/signout") {
session.invalidate context.settings.oidc.foreach { oidc =>
session.get(Keys.Session.OidcSessionContext).foreach { case context: OidcSessionContext =>
val redirectURI = new URI(baseUrl)
val authenticationRequest = createOIDLogoutRequest(oidc.issuer, oidc.clientID, redirectURI, context.token)
session.invalidate()
redirect(authenticationRequest.toURI.toString)
}
}
session.invalidate()
if (isDevFeatureEnabled(DevFeatures.KeepSession)) {
deleteLoginAccountFromLocalFile()
}
redirect("/") redirect("/")
} }
@@ -178,6 +204,9 @@ trait IndexControllerBase extends ControllerBase {
*/ */
private def signin(account: Account, redirectUrl: String = "/") = { private def signin(account: Account, redirectUrl: String = "/") = {
session.setAttribute(Keys.Session.LoginAccount, account) session.setAttribute(Keys.Session.LoginAccount, account)
if (isDevFeatureEnabled(DevFeatures.KeepSession)) {
saveLoginAccountToLocalFile(account)
}
updateLastLoginDate(account.userName) updateLastLoginDate(account.userName)
if (LDAPUtil.isDummyMailAddress(account)) { if (LDAPUtil.isDummyMailAddress(account)) {
@@ -201,7 +230,7 @@ trait IndexControllerBase extends ControllerBase {
org.json4s.jackson.Serialization.write( org.json4s.jackson.Serialization.write(
Map( Map(
"options" -> ( "options" -> (
getAllUsers(false) getAllUsers(includeRemoved = false)
.withFilter { t => .withFilter { t =>
(user, group) match { (user, group) match {
case (true, true) => true case (true, true) => true
@@ -213,9 +242,9 @@ trait IndexControllerBase extends ControllerBase {
.map { t => .map { t =>
Map( Map(
"label" -> s"${avatar(t.userName, 16)}<b>@${StringUtil.escapeHtml( "label" -> s"${avatar(t.userName, 16)}<b>@${StringUtil.escapeHtml(
StringUtil.cutTail(t.userName, 25, "...") StringUtil.cutTail(t.userName, 25, "...")
)}</b> ${StringUtil )}</b> ${StringUtil
.escapeHtml(StringUtil.cutTail(t.fullName, 25, "..."))}", .escapeHtml(StringUtil.cutTail(t.fullName, 25, "..."))}",
"value" -> t.userName "value" -> t.userName
) )
} }
@@ -234,22 +263,23 @@ trait IndexControllerBase extends ControllerBase {
} getOrElse "" } getOrElse ""
}) })
// TODO Move to RepositoryViewrController? // TODO Move to RepositoryViewerController?
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 =
val i = params.getOrElse("page", "1").toInt try {
if (i <= 0) 1 else i val i = params.getOrElse("page", "1").toInt
} catch { if (i <= 0) 1 else i
case _: NumberFormatException => 1 } catch {
} case _: NumberFormatException => 1
}
target.toLowerCase match { target.toLowerCase match {
case "issues" => case "issues" =>
gitbucket.core.search.html.issues( gitbucket.core.search.html.issues(
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, false) else Nil, if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, pullRequest = false) else Nil,
false, pullRequest = false,
query, query,
page, page,
repository repository
@@ -257,8 +287,8 @@ trait IndexControllerBase extends ControllerBase {
case "pulls" => case "pulls" =>
gitbucket.core.search.html.issues( gitbucket.core.search.html.issues(
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, true) else Nil, if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, pullRequest = true) else Nil,
true, pullRequest = true,
query, query,
page, page,
repository repository
@@ -293,15 +323,15 @@ trait IndexControllerBase extends ControllerBase {
) )
val repositories = { val repositories = {
context.settings.basicBehavior.limitVisibleRepositories match { if (context.settings.basicBehavior.limitVisibleRepositories) {
case true => getVisibleRepositories(
getVisibleRepositories( context.loginAccount,
context.loginAccount, None,
None, withoutPhysicalInfo = true,
withoutPhysicalInfo = true, limit = false
limit = false )
) } else {
case false => visibleRepositories visibleRepositories
} }
}.filter { repository => }.filter { repository =>
repository.name.toLowerCase.indexOf(query) >= 0 || repository.owner.toLowerCase.indexOf(query) >= 0 repository.name.toLowerCase.indexOf(query) >= 0 || repository.owner.toLowerCase.indexOf(query) >= 0

View File

@@ -89,36 +89,44 @@ trait IssuesControllerBase extends ControllerBase {
get("/:owner/:repository/issues")(referrersOnly { repository => get("/:owner/:repository/issues")(referrersOnly { repository =>
val q = request.getParameter("q") val q = request.getParameter("q")
if (Option(q).exists(_.contains("is:pr"))) { Option(q) match {
redirect(s"/${repository.owner}/${repository.name}/pulls?q=${StringUtil.urlEncode(q)}") case Some(filter) if filter.contains("is:pr") =>
} else { redirect(s"/${repository.owner}/${repository.name}/pulls?q=${StringUtil.urlEncode(q)}")
searchIssues(repository) 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=issues")
} else {
searchIssues(repository, condition, IssueSearchCondition.page(request))
}
case None =>
searchIssues(repository, IssueSearchCondition(request), IssueSearchCondition.page(request))
} }
}) })
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, getComments(repository.owner, repository.name, issueId.toInt),
getComments(repository.owner, repository.name, issueId.toInt), getIssueLabels(repository.owner, repository.name, issueId.toInt),
getIssueLabels(repository.owner, repository.name, issueId.toInt), getIssueAssignees(repository.owner, repository.name, issueId.toInt),
getIssueAssignees(repository.owner, repository.name, issueId.toInt), getAssignableUserNames(repository.owner, repository.name),
getAssignableUserNames(repository.owner, repository.name), getMilestonesWithIssueCount(repository.owner, repository.name),
getMilestonesWithIssueCount(repository.owner, repository.name), getPriorities(repository.owner, repository.name),
getPriorities(repository.owner, repository.name), getLabels(repository.owner, repository.name),
getLabels(repository.owner, repository.name), getCustomFieldsWithValue(repository.owner, repository.name, issueId.toInt).filter(_._1.enableForIssues),
getCustomFieldsWithValue(repository.owner, repository.name, issueId.toInt).filter(_._1.enableForIssues), isIssueEditable(repository),
isIssueEditable(repository), isIssueManageable(repository),
isIssueManageable(repository), isIssueCommentManageable(repository),
isIssueCommentManageable(repository), repository
repository )
) }
}
} getOrElse NotFound() } getOrElse NotFound()
}) })
@@ -139,130 +147,120 @@ 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, form.title,
form.title, form.content,
form.content, form.assigneeUserNames.toSeq.flatMap(_.split(",")),
form.assigneeUserNames.toSeq.flatMap(_.split(",")), form.milestoneId,
form.milestoneId, form.priorityId,
form.priorityId, form.labelNames.toSeq.flatMap(_.split(",")),
form.labelNames.toSeq.flatMap(_.split(",")), loginAccount
loginAccount )
)
// 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, repository.name,
repository.name, key.replaceFirst("^custom-field-", "").toInt
key.replaceFirst("^custom-field-", "").toInt ).foreach { field =>
).foreach { field => CustomFieldBehavior.validate(field, value, messages) match {
CustomFieldBehavior.validate(field, value, messages) match { case None =>
case None => insertOrUpdateCustomFieldValue(field, repository.owner, repository.name, issue.issueId, value)
insertOrUpdateCustomFieldValue(field, repository.owner, repository.name, issue.issueId, value) case Some(_) => halt(400)
case Some(_) => halt(400)
}
}
} }
}
} }
}
redirect(s"/${issue.userName}/${issue.repositoryName}/issues/${issue.issueId}") redirect(s"/${issue.userName}/${issue.repositoryName}/issues/${issue.issueId}")
} else Unauthorized() } else Unauthorized()
} }
}) })
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 { if (isEditableContent(repository.owner, repository.name, issue.openedUserName, loginAccount)) {
issue => if (issue.title != title) {
if (isEditableContent(repository.owner, repository.name, issue.openedUserName, loginAccount)) { // update issue
if (issue.title != title) { updateIssue(repository.owner, repository.name, issue.issueId, title, issue.content)
// update issue // extract references and create refer comment
updateIssue(repository.owner, repository.name, issue.issueId, title, issue.content) createReferComment(repository.owner, repository.name, issue.copy(title = title), title, loginAccount)
// extract references and create refer comment createComment(
createReferComment(repository.owner, repository.name, issue.copy(title = title), title, loginAccount) repository.owner,
createComment( repository.name,
repository.owner, loginAccount.userName,
repository.name, issue.issueId,
loginAccount.userName, issue.title + "\r\n" + title,
issue.issueId, "change_title"
issue.title + "\r\n" + title, )
"change_title" }
) redirect(s"/${repository.owner}/${repository.name}/issues/_data/${issue.issueId}")
} } else Unauthorized()
redirect(s"/${repository.owner}/${repository.name}/issues/_data/${issue.issueId}") } getOrElse NotFound()
} else Unauthorized()
} getOrElse NotFound()
} }
}) })
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 updateIssue(repository.owner, repository.name, issue.issueId, issue.title, content)
updateIssue(repository.owner, repository.name, issue.issueId, issue.title, content) // extract references and create refer comment
// extract references and create refer comment createReferComment(repository.owner, repository.name, issue, content.getOrElse(""), loginAccount)
createReferComment(repository.owner, repository.name, issue, content.getOrElse(""), loginAccount)
redirect(s"/${repository.owner}/${repository.name}/issues/_data/${issue.issueId}") redirect(s"/${repository.owner}/${repository.name}/issues/_data/${issue.issueId}")
} else Unauthorized() } else Unauthorized()
} getOrElse NotFound() } getOrElse NotFound()
} }
}) })
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) => post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
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 { case (issue, id) =>
handleComment(issue, Some(form.content), repository, actionOpt) map { redirect(
case (issue, id) => s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}"
redirect( )
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}" }
) } getOrElse NotFound()
}
} getOrElse NotFound()
} }
}) })
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 { case (issue, id) =>
handleComment(issue, form.content, repository, actionOpt) map { redirect(
case (issue, id) => s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}"
redirect( )
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}" }
) } getOrElse NotFound()
}
} getOrElse NotFound()
} }
}) })
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) redirect(s"/${repository.owner}/${repository.name}/issue_comments/_data/${comment.commentId}")
redirect(s"/${repository.owner}/${repository.name}/issue_comments/_data/${comment.commentId}") } else Unauthorized()
} else Unauthorized() } getOrElse NotFound()
} getOrElse NotFound()
} }
}) })
@@ -277,65 +275,61 @@ 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 { if (isEditableContent(x.userName, x.repositoryName, x.openedUserName, loginAccount)) {
x => params.get("dataType") collect {
if (isEditableContent(x.userName, x.repositoryName, x.openedUserName, loginAccount)) { case t if t == "html" => html.editissue(x.content, x.issueId, repository)
params.get("dataType") collect { } getOrElse {
case t if t == "html" => html.editissue(x.content, x.issueId, repository) contentType = formats("json")
} getOrElse { org.json4s.jackson.Serialization.write(
contentType = formats("json") Map(
org.json4s.jackson.Serialization.write( "title" -> x.title,
Map( "content" -> Markdown.toHtml(
"title" -> x.title, markdown = x.content getOrElse "No description given.",
"content" -> Markdown.toHtml( repository = repository,
markdown = x.content getOrElse "No description given.", branch = repository.repository.defaultBranch,
repository = repository, enableWikiLink = false,
branch = repository.repository.defaultBranch, enableRefsLink = true,
enableWikiLink = false, enableAnchor = true,
enableRefsLink = true, enableLineBreaks = true,
enableAnchor = true, enableTaskList = true,
enableLineBreaks = true, hasWritePermission = true
enableTaskList = true,
hasWritePermission = true
)
)
) )
} )
} else Unauthorized() )
} getOrElse NotFound() }
} else Unauthorized()
} getOrElse NotFound()
} }
}) })
ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository => ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository =>
context.withLoginAccount { context.withLoginAccount { loginAccount =>
loginAccount => getComment(repository.owner, repository.name, params("id")) map { x =>
getComment(repository.owner, repository.name, params("id")) map { if (isEditableContent(x.userName, x.repositoryName, x.commentedUserName, loginAccount)) {
x => params.get("dataType") collect {
if (isEditableContent(x.userName, x.repositoryName, x.commentedUserName, loginAccount)) { case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
params.get("dataType") collect { } getOrElse {
case t if t == "html" => html.editcomment(x.content, x.commentId, repository) contentType = formats("json")
} getOrElse { org.json4s.jackson.Serialization.write(
contentType = formats("json") Map(
org.json4s.jackson.Serialization.write( "content" -> view.Markdown.toHtml(
Map( markdown = x.content,
"content" -> view.Markdown.toHtml( repository = repository,
markdown = x.content, branch = repository.repository.defaultBranch,
repository = repository, enableWikiLink = false,
branch = repository.repository.defaultBranch, enableRefsLink = true,
enableWikiLink = false, enableAnchor = true,
enableRefsLink = true, enableLineBreaks = true,
enableAnchor = true, enableTaskList = true,
enableLineBreaks = true, hasWritePermission = true
enableTaskList = true,
hasWritePermission = true
)
)
) )
} )
} else Unauthorized() )
} getOrElse NotFound() }
} else Unauthorized()
} getOrElse NotFound()
} }
}) })
@@ -374,9 +368,8 @@ trait IssuesControllerBase extends ControllerBase {
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()
}) })
@@ -457,7 +450,7 @@ 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, true)
@@ -507,9 +500,9 @@ trait IssuesControllerBase extends ControllerBase {
.map { t => .map { t =>
Map( Map(
"label" -> s"""${if (t.isPullRequest) "<i class='octicon octicon-git-pull-request'></i>" "label" -> s"""${if (t.isPullRequest) "<i class='octicon octicon-git-pull-request'></i>"
else "<i class='octicon octicon-issue-opened'></i>"}<b> #${StringUtil else "<i class='octicon octicon-issue-opened'></i>"}<b> #${StringUtil
.escapeHtml(t.issueId.toString)} ${StringUtil .escapeHtml(t.issueId.toString)} ${StringUtil
.escapeHtml(StringUtil.cutTail(t.title, 50, "..."))}</b>""", .escapeHtml(StringUtil.cutTail(t.title, 50, "..."))}</b>""",
"value" -> t.issueId.toString "value" -> t.issueId.toString
) )
} }
@@ -531,10 +524,7 @@ trait IssuesControllerBase extends ControllerBase {
} }
} }
private def searchIssues(repository: RepositoryService.RepositoryInfo) = { private def searchIssues(repository: RepositoryService.RepositoryInfo, condition: IssueSearchCondition, page: Int) = {
val page = IssueSearchCondition.page(request)
// retrieve search condition
val condition = IssueSearchCondition(request)
// search issues // search issues
val issues = val issues =
searchIssue( searchIssue(
@@ -565,8 +555,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 +564,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

@@ -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("/register") && !context.currentPath.endsWith("/info/refs") && !context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
!context.currentPath.startsWith("/plugin-assets") && !context.currentPath.startsWith("/register") && !context.currentPath.endsWith("/info/refs") &&
!PluginRegistry().getAnonymousAccessiblePaths().exists { path => !context.currentPath.startsWith("/plugin-assets") &&
context.currentPath.startsWith(path) !PluginRegistry().getAnonymousAccessiblePaths().exists { path =>
}) { context.currentPath.startsWith(path)
}
) {
Unauthorized() Unauthorized()
} else { } else {
pass() pass()

View File

@@ -45,7 +45,7 @@ trait PrioritiesControllerBase extends ControllerBase {
get("/:owner/:repository/issues/priorities")(referrersOnly { repository => get("/:owner/:repository/issues/priorities")(referrersOnly { repository =>
html.list( html.list(
getPriorities(repository.owner, repository.name), getPriorities(repository.owner, repository.name),
countIssueGroupByPriorities(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty), countIssueGroupByPriorities(repository.owner, repository.name, IssuesService.IssueSearchCondition()),
repository, repository,
hasDeveloperRole(repository.owner, repository.name, context.loginAccount) hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
) )
@@ -60,7 +60,7 @@ trait PrioritiesControllerBase extends ControllerBase {
createPriority(repository.owner, repository.name, form.priorityName, form.description, form.color.substring(1)) createPriority(repository.owner, repository.name, form.priorityName, form.description, form.color.substring(1))
html.priority( html.priority(
getPriority(repository.owner, repository.name, priorityId).get, getPriority(repository.owner, repository.name, priorityId).get,
countIssueGroupByPriorities(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty), countIssueGroupByPriorities(repository.owner, repository.name, IssuesService.IssueSearchCondition()),
repository, repository,
hasDeveloperRole(repository.owner, repository.name, context.loginAccount) hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
) )
@@ -84,7 +84,7 @@ trait PrioritiesControllerBase extends ControllerBase {
) )
html.priority( html.priority(
getPriority(repository.owner, repository.name, params("priorityId").toInt).get, getPriority(repository.owner, repository.name, params("priorityId").toInt).get,
countIssueGroupByPriorities(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty), countIssueGroupByPriorities(repository.owner, repository.name, IssuesService.IssueSearchCondition()),
repository, repository,
hasDeveloperRole(repository.owner, repository.name, context.loginAccount) hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
) )

View File

@@ -69,7 +69,7 @@ trait PullRequestsControllerBase extends ControllerBase {
"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)),
"assignedUserName" -> trim(optional(text())), "assigneeUserNames" -> trim(optional(text())),
"milestoneId" -> trim(optional(number())), "milestoneId" -> trim(optional(number())),
"priorityId" -> trim(optional(number())), "priorityId" -> trim(optional(number())),
"labelNames" -> trim(optional(text())) "labelNames" -> trim(optional(text()))
@@ -92,7 +92,7 @@ trait PullRequestsControllerBase extends ControllerBase {
commitIdFrom: String, commitIdFrom: String,
commitIdTo: String, commitIdTo: String,
isDraft: Boolean, isDraft: Boolean,
assignedUserNames: Option[String], assigneeUserNames: Option[String],
milestoneId: Option[Int], milestoneId: Option[Int],
priorityId: Option[Int], priorityId: Option[Int],
labelNames: Option[String] labelNames: Option[String]
@@ -102,176 +102,160 @@ trait PullRequestsControllerBase extends ControllerBase {
get("/:owner/:repository/pulls")(referrersOnly { repository => get("/:owner/:repository/pulls")(referrersOnly { repository =>
val q = request.getParameter("q") val q = request.getParameter("q")
if (Option(q).exists(_.contains("is:issue"))) { Option(q) match {
redirect(s"/${repository.owner}/${repository.name}/issues?q=" + StringUtil.urlEncode(q)) case Some(filter) if filter.contains("is:issue") =>
} else { redirect(s"/${repository.owner}/${repository.name}/issues?q=${StringUtil.urlEncode(q)}")
searchPullRequests(None, repository) 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))
}
case None =>
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 { val (commits, diffs) =
case (issue, pullreq) => getRequestCompareInfo(
val (commits, diffs) = repository.owner,
getRequestCompareInfo( repository.name,
repository.owner, pullreq.commitIdFrom,
repository.name, repository.owner,
pullreq.commitIdFrom, repository.name,
repository.owner, pullreq.commitIdTo,
repository.name, context.settings
pullreq.commitIdTo )
)
html.conversation( html.conversation(
issue, issue,
pullreq, pullreq,
commits.flatten, commits.flatten,
getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten), getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
diffs.size, diffs.size,
getIssueLabels(repository.owner, repository.name, issueId), getIssueLabels(repository.owner, repository.name, issueId),
getIssueAssignees(repository.owner, repository.name, issueId), getIssueAssignees(repository.owner, repository.name, issueId),
getAssignableUserNames(repository.owner, repository.name), getAssignableUserNames(repository.owner, repository.name),
getMilestonesWithIssueCount(repository.owner, repository.name), getMilestonesWithIssueCount(repository.owner, repository.name),
getPriorities(repository.owner, repository.name), getPriorities(repository.owner, repository.name),
getLabels(repository.owner, repository.name), getLabels(repository.owner, repository.name),
getCustomFieldsWithValue(repository.owner, repository.name, issueId).filter(_._1.enableForPullRequests), getCustomFieldsWithValue(repository.owner, repository.name, issueId).filter(_._1.enableForPullRequests),
isEditable(repository), isEditable(repository),
isManageable(repository), isManageable(repository),
hasDeveloperRole(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount), hasDeveloperRole(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount),
repository, repository,
getRepository(pullreq.requestUserName, pullreq.requestRepositoryName), getRepository(pullreq.requestUserName, pullreq.requestRepositoryName),
flash.iterator.map(f => f._1 -> f._2.toString).toMap flash.iterator.map(f => f._1 -> f._2.toString).toMap
) )
}
// html.pullreq(
// issue,
// pullreq,
// comments,
// getIssueLabels(owner, name, issueId),
// getAssignableUserNames(owner, name),
// getMilestonesWithIssueCount(owner, name),
// getPriorities(owner, name),
// getLabels(owner, name),
// commits,
// diffs,
// isEditable(repository),
// isManageable(repository),
// hasDeveloperRole(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount),
// repository,
// getRepository(pullreq.requestUserName, pullreq.requestRepositoryName),
// flash.toMap.map(f => f._1 -> f._2.toString)
// )
}
} getOrElse NotFound() } getOrElse NotFound()
}) })
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 { val (commits, diffs) =
case (issue, pullreq) => getRequestCompareInfo(
val (commits, diffs) = repository.owner,
getRequestCompareInfo( repository.name,
repository.owner, pullreq.commitIdFrom,
repository.name, repository.owner,
pullreq.commitIdFrom, repository.name,
repository.owner, pullreq.commitIdTo,
repository.name, context.settings
pullreq.commitIdTo )
)
val commitsWithStatus = commits.map { day => val commitsWithStatus = commits.map { day =>
day.map { commit => day.map { commit =>
(commit, getCommitStatusWithSummary(repository.owner, repository.name, commit.id)) (commit, getCommitStatusWithSummary(repository.owner, repository.name, commit.id))
} }
}
html.commits(
issue,
pullreq,
commitsWithStatus,
getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
diffs.size,
isManageable(repository),
repository
)
} }
html.commits(
issue,
pullreq,
commitsWithStatus,
getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
diffs.size,
isManageable(repository),
repository
)
}
} getOrElse NotFound() } getOrElse NotFound()
}) })
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 { val (commits, diffs) =
case (issue, pullreq) => getRequestCompareInfo(
val (commits, diffs) = repository.owner,
getRequestCompareInfo( repository.name,
repository.owner, pullreq.commitIdFrom,
repository.name, repository.owner,
pullreq.commitIdFrom, repository.name,
repository.owner, pullreq.commitIdTo,
repository.name, context.settings
pullreq.commitIdTo )
)
html.files( html.files(
issue, issue,
pullreq, pullreq,
diffs, diffs,
commits.flatten, commits.flatten,
getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten), getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
isManageable(repository), isManageable(repository),
repository repository
) )
} }
} getOrElse NotFound() } getOrElse NotFound()
}) })
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 { val conflictMessage = LockUtil.lock(s"${repository.owner}/${repository.name}") {
case (issue, pullreq) => checkConflict(repository.owner, repository.name, pullreq.branch, issueId)
val conflictMessage = LockUtil.lock(s"${repository.owner}/${repository.name}") { }
checkConflict(repository.owner, repository.name, pullreq.branch, issueId) val hasMergePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
} val branchProtection = getProtectedBranchInfo(repository.owner, repository.name, pullreq.branch)
val hasMergePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount) val mergeStatus = PullRequestService.MergeStatus(
val branchProtection = getProtectedBranchInfo(repository.owner, repository.name, pullreq.branch) conflictMessage = conflictMessage,
val mergeStatus = PullRequestService.MergeStatus( commitStatuses = getCommitStatuses(repository.owner, repository.name, pullreq.commitIdTo),
conflictMessage = conflictMessage, branchProtection = branchProtection,
commitStatuses = getCommitStatuses(repository.owner, repository.name, pullreq.commitIdTo), branchIsOutOfDate =
branchProtection = branchProtection, !JGitUtil.getShaByRef(repository.owner, repository.name, pullreq.branch).contains(pullreq.commitIdFrom),
branchIsOutOfDate = JGitUtil.getShaByRef(repository.owner, repository.name, pullreq.branch) != Some( needStatusCheck = context.loginAccount.forall { u =>
pullreq.commitIdFrom branchProtection.needStatusCheck(u.userName)
), },
needStatusCheck = context.loginAccount.forall { u => hasUpdatePermission = hasDeveloperRole(
branchProtection.needStatusCheck(u.userName) pullreq.requestUserName,
}, pullreq.requestRepositoryName,
hasUpdatePermission = hasDeveloperRole( context.loginAccount
) &&
context.loginAccount.exists { u =>
!getProtectedBranchInfo(
pullreq.requestUserName, pullreq.requestUserName,
pullreq.requestRepositoryName, pullreq.requestRepositoryName,
context.loginAccount pullreq.requestBranch
) && ).needStatusCheck(u.userName)
context.loginAccount.exists { u => },
!getProtectedBranchInfo( hasMergePermission = hasMergePermission,
pullreq.requestUserName, commitIdTo = pullreq.commitIdTo
pullreq.requestRepositoryName, )
pullreq.requestBranch html.mergeguide(
).needStatusCheck(u.userName) mergeStatus,
}, issue,
hasMergePermission = hasMergePermission, pullreq,
commitIdTo = pullreq.commitIdTo repository,
) getRepository(pullreq.requestUserName, pullreq.requestRepositoryName).get
html.mergeguide( )
mergeStatus, }
issue,
pullreq,
repository,
getRepository(pullreq.requestUserName, pullreq.requestRepositoryName).get
)
}
} getOrElse NotFound() } getOrElse NotFound()
}) })
@@ -330,7 +314,9 @@ trait PullRequestsControllerBase extends ControllerBase {
} 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}"
@@ -396,79 +382,86 @@ trait PullRequestsControllerBase extends ControllerBase {
}) })
get("/:owner/:repository/compare")(referrersOnly { forkedRepository => get("/:owner/:repository/compare")(referrersOnly { forkedRepository =>
val headBranch: Option[String] = 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)) ) { (oldGit, newGit) =>
) { (oldGit, newGit) => val newBranch = headBranch.getOrElse(JGitUtil.getDefaultBranch(newGit, forkedRepository).get._2)
val newBranch = headBranch.getOrElse(JGitUtil.getDefaultBranch(newGit, forkedRepository).get._2) val oldBranch = originRepository.branchList
val oldBranch = originRepository.branchList .find(_ == newBranch)
.find(_ == newBranch) .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}")
} }
} }
}
} }
}) })
private def getOriginRepositoryName(
originOwner: String,
forkedOwner: String,
forkedRepository: RepositoryInfo
): Option[String] = {
if (originOwner == forkedOwner) {
// Self repository
Some(forkedRepository.name)
} else if (forkedRepository.repository.originUserName.isEmpty) {
// when ForkedRepository is the original repository
getForkedRepositories(forkedRepository.owner, forkedRepository.name)
.find(_.userName == originOwner)
.map(_.repositoryName)
} else if (Some(originOwner) == forkedRepository.repository.originUserName) {
// Original repository
forkedRepository.repository.originRepositoryName
} else {
// Sibling repository
getUserRepositories(originOwner)
.find { x =>
x.repository.originUserName == forkedRepository.repository.originUserName &&
x.repository.originRepositoryName == forkedRepository.repository.originRepositoryName
}
.map(_.repository.repositoryName)
}
}
get("/:owner/:repository/compare/*...*")(referrersOnly { forkedRepository => get("/:owner/:repository/compare/*...*")(referrersOnly { forkedRepository =>
val Seq(origin, forked) = multiParams("splat") val Seq(origin, forked) = multiParams("splat")
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 <- if (originOwner == forkedOwner) { (for (
// Self repository originRepositoryName <- getOriginRepositoryName(originOwner, forkedOwner, forkedRepository);
Some(forkedRepository.name) originRepository <- getRepository(originOwner, originRepositoryName)
} else if (forkedRepository.repository.originUserName.isEmpty) { ) yield {
// when ForkedRepository is the original repository
getForkedRepositories(forkedRepository.owner, forkedRepository.name)
.find(_.userName == originOwner)
.map(_.repositoryName)
} else if (Some(originOwner) == forkedRepository.repository.originUserName) {
// Original repository
forkedRepository.repository.originRepositoryName
} else {
// Sibling repository
getUserRepositories(originOwner)
.find { x =>
x.repository.originUserName == forkedRepository.repository.originUserName &&
x.repository.originRepositoryName == forkedRepository.repository.originRepositoryName
}
.map(_.repository.repositoryName)
};
originRepository <- getRepository(originOwner, originRepositoryName)) yield {
val (oldId, newId) = val (oldId, newId) =
getPullRequestCommitFromTo(originRepository, forkedRepository, originId, forkedId) getPullRequestCommitFromTo(originRepository, forkedRepository, originId, forkedId)
(oldId, newId) match { (oldId, newId) match {
case (Some(oldId), Some(newId)) => { case (Some(oldId), Some(newId)) =>
val (commits, diffs) = getRequestCompareInfo( val (commits, diffs) = getRequestCompareInfo(
originRepository.owner, originRepository.owner,
originRepository.name, originRepository.name,
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) {
@@ -512,7 +505,6 @@ trait PullRequestsControllerBase extends ControllerBase {
getLabels(originRepository.owner, originRepository.name), getLabels(originRepository.owner, originRepository.name),
getCustomFields(originRepository.owner, originRepository.name).filter(_.enableForPullRequests) getCustomFields(originRepository.owner, originRepository.name).filter(_.enableForPullRequests)
) )
}
case (oldId, newId) => case (oldId, newId) =>
redirect( redirect(
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/" + s"/${forkedRepository.owner}/${forkedRepository.name}/compare/" +
@@ -524,92 +516,141 @@ trait PullRequestsControllerBase extends ControllerBase {
}) getOrElse NotFound() }) getOrElse NotFound()
}) })
ajaxGet("/:owner/:repository/diff/:id")(referrersOnly { repository =>
(for {
commitId <- params.get("id")
path <- params.get("path")
diff <- getSingleDiff(repository.owner, repository.name, commitId, path)
} yield {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map(
"oldContent" -> diff.oldContent,
"newContent" -> diff.newContent
)
)
}) getOrElse NotFound()
})
ajaxGet("/:owner/:repository/diff/*...*")(referrersOnly { forkedRepository =>
val Seq(origin, forked) = multiParams("splat")
val (originOwner, originId) = parseCompareIdentifier(origin, forkedRepository.owner)
val (forkedOwner, forkedId) = parseCompareIdentifier(forked, forkedRepository.owner)
(for {
path <- params.get("path")
originRepositoryName <- getOriginRepositoryName(originOwner, forkedOwner, forkedRepository)
originRepository <- getRepository(originOwner, originRepositoryName)
(oldId, newId) = getPullRequestCommitFromTo(originRepository, forkedRepository, originId, forkedId)
oldId <- oldId
newId <- newId
diff <- getSingleDiff(
originRepository.owner,
originRepository.name,
oldId.getName,
forkedRepository.owner,
forkedRepository.name,
newId.getName,
path
)
} yield {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map(
"oldContent" -> diff.oldContent,
"newContent" -> diff.newContent
)
)
}) getOrElse NotFound()
})
ajaxGet("/:owner/:repository/compare/*...*/mergecheck")(readableUsersOnly { forkedRepository => ajaxGet("/:owner/:repository/compare/*...*/mergecheck")(readableUsersOnly { forkedRepository =>
val Seq(origin, forked) = multiParams("splat") val Seq(origin, forked) = multiParams("splat")
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 (
Some(forkedRepository.name) originRepositoryName <-
} else { if (originOwner == forkedOwner) {
forkedRepository.repository.originRepositoryName.orElse { Some(forkedRepository.name)
getForkedRepositories(forkedRepository.owner, forkedRepository.name) } else {
.find(_.userName == originOwner) forkedRepository.repository.originRepositoryName.orElse {
.map(_.repositoryName) getForkedRepositories(forkedRepository.owner, forkedRepository.name)
} .find(_.userName == originOwner)
}; .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}") { checkConflict(
checkConflict( originRepository.owner,
originRepository.owner, originRepository.name,
originRepository.name, originBranch,
originBranch, forkedRepository.owner,
forkedRepository.owner, forkedRepository.name,
forkedRepository.name, forkedBranch
forkedBranch )
) }
} html.mergecheck(conflict.isDefined)
html.mergecheck(conflict.isDefined)
} }
}) getOrElse NotFound() }) getOrElse NotFound()
}) })
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(
owner = repository.owner, owner = repository.owner,
repository = repository.name, repository = repository.name,
loginUser = loginAccount.userName, loginUser = loginAccount.userName,
title = form.title, title = form.title,
content = form.content, content = form.content,
milestoneId = if (manageable) form.milestoneId else None, milestoneId = if (manageable) form.milestoneId else None,
priorityId = if (manageable) form.priorityId else None, priorityId = if (manageable) form.priorityId else None,
isPullRequest = true isPullRequest = true
) )
createPullRequest( createPullRequest(
originRepository = repository, originRepository = repository,
issueId = issueId, issueId = issueId,
originBranch = form.targetBranch, originBranch = form.targetBranch,
requestUserName = form.requestUserName, requestUserName = form.requestUserName,
requestRepositoryName = form.requestRepositoryName, requestRepositoryName = form.requestRepositoryName,
requestBranch = form.requestBranch, requestBranch = form.requestBranch,
commitIdFrom = form.commitIdFrom, commitIdFrom = form.commitIdFrom,
commitIdTo = form.commitIdTo, commitIdTo = form.commitIdTo,
isDraft = form.isDraft, isDraft = form.isDraft,
loginAccount = loginAccount, loginAccount = loginAccount,
settings = context.settings settings = context.settings
) )
if (manageable) { if (manageable) {
// insert assignees // insert assignees
form.assignedUserNames.foreach { value => form.assigneeUserNames.foreach { value =>
value.split(",").foreach { userName => value.split(",").foreach { userName =>
registerIssueAssignee(repository.owner, repository.name, issueId, userName) registerIssueAssignee(repository.owner, repository.name, issueId, userName)
}
} }
// insert labels }
form.labelNames.foreach { value => // insert labels
val labels = getLabels(repository.owner, repository.name) form.labelNames.foreach { value =>
value.split(",").foreach { labelName => val labels = getLabels(repository.owner, repository.name)
labels.find(_.labelName == labelName).map { label => value.split(",").foreach { labelName =>
registerIssueLabel(repository.owner, repository.name, issueId, label.labelId) labels.find(_.labelName == labelName).map { label =>
} registerIssueLabel(repository.owner, repository.name, issueId, label.labelId)
} }
} }
} }
}
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}") redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
} }
}) })
@@ -619,24 +660,23 @@ 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, defaultBranch = repository.repository.defaultBranch,
defaultBranch = repository.repository.defaultBranch, origin = repository.repository.originUserName.isEmpty
origin = repository.repository.originUserName.isEmpty )
) .filter { x =>
.filter { x => x.mergeInfo.map(_.ahead).getOrElse(0) > 0 && x.mergeInfo.map(_.behind).getOrElse(0) == 0 &&
x.mergeInfo.map(_.ahead).getOrElse(0) > 0 && x.mergeInfo.map(_.behind).getOrElse(0) == 0 && x.commitTime.getTime > thresholdTime &&
x.commitTime.getTime > thresholdTime && mailAddresses.contains(x.committerEmailAddress)
mailAddresses.contains(x.committerEmailAddress) }
} .sortBy { br =>
.sortBy { br => (br.mergeInfo.isEmpty, br.commitTime)
(br.mergeInfo.isEmpty, br.commitTime) }
} .map(_.name)
.map(_.name) .reverse
.reverse
} }
val targetRepository = (for { val targetRepository = (for {
@@ -656,10 +696,11 @@ trait PullRequestsControllerBase extends ControllerBase {
html.proposals(proposedBranches, targetRepository, repository) html.proposals(proposedBranches, targetRepository, repository)
}) })
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) = { private def searchPullRequests(
val page = IssueSearchCondition.page(request) repository: RepositoryService.RepositoryInfo,
// retrieve search condition condition: IssueSearchCondition,
val condition = IssueSearchCondition(request) page: Int
) = {
// search issues // search issues
val issues = searchIssue( val issues = searchIssue(
condition, condition,

View File

@@ -106,33 +106,31 @@ 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
createRelease(repository.owner, repository.name, form.name, form.content, tagName, loginAccount) createRelease(repository.owner, repository.name, form.name, form.content, tagName, loginAccount)
// Insert into RELEASE_ASSET // Insert into RELEASE_ASSET
val files = params.toMap.collect { val files = params.toMap.collect {
case (name, value) if name.startsWith("file:") => case (name, value) if name.startsWith("file:") =>
val Array(_, fileId) = name.split(":") val Array(_, fileId) = name.split(":")
(fileId, value) (fileId, value)
} }
files.foreach { 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), FileUtil.checkFilename(tagName + "/" + fileId)
FileUtil.checkFilename(tagName + "/" + fileId) ).length
).length createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount) }
}
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}")
} }
}) })
@@ -171,48 +169,45 @@ 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)
// Delete and Insert RELEASE_ASSET // Delete and Insert RELEASE_ASSET
val assets = getReleaseAssets(repository.owner, repository.name, tagName) val assets = getReleaseAssets(repository.owner, repository.name, tagName)
deleteReleaseAssets(repository.owner, repository.name, tagName) deleteReleaseAssets(repository.owner, repository.name, tagName)
val files = params.toMap.collect { val files = params.toMap.collect {
case (name, value) if name.startsWith("file:") => case (name, value) if name.startsWith("file:") =>
val Array(_, fileId) = name.split(":") val Array(_, fileId) = name.split(":")
(fileId, value) (fileId, value)
}
files.foreach {
case (fileId, fileName) =>
val size =
new File(
getReleaseFilesDir(repository.owner, repository.name),
FileUtil.checkFilename(tagName + "/" + fileId)
).length
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
}
assets.foreach { asset =>
if (!files.exists { case (fileId, _) => fileId == asset.fileName }) {
val file = new File(
getReleaseFilesDir(repository.owner, repository.name),
FileUtil.checkFilename(release.tag + "/" + asset.fileName)
)
FileUtils.forceDelete(file)
}
}
redirect(s"/${release.userName}/${release.repositoryName}/releases/${tagName}")
} }
.getOrElse(NotFound()) files.foreach { case (fileId, fileName) =>
val size =
new File(
getReleaseFilesDir(repository.owner, repository.name),
FileUtil.checkFilename(tagName + "/" + fileId)
).length
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
}
assets.foreach { asset =>
if (!files.exists { case (fileId, _) => fileId == asset.fileName }) {
val file = new File(
getReleaseFilesDir(repository.owner, repository.name),
FileUtil.checkFilename(release.tag + "/" + asset.fileName)
)
FileUtils.forceDelete(file)
}
}
redirect(s"/${release.userName}/${release.repositoryName}/releases/${tagName}")
}
.getOrElse(NotFound())
} }
}) })
@@ -237,9 +232,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 => (
(release, assets(release)) tag,
}) releases.find(_.tag == tag.name).map { release =>
(release, assets(release))
}
)
} }
tagsWithReleases tagsWithReleases
} }

View File

@@ -126,6 +126,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
case class CustomFieldForm( case class CustomFieldForm(
fieldName: String, fieldName: String,
fieldType: String, fieldType: String,
constraints: Option[String],
enableForIssues: Boolean, enableForIssues: Boolean,
enableForPullRequests: Boolean enableForPullRequests: Boolean
) )
@@ -133,6 +134,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
val customFieldForm = mapping( 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()))),
"enableForIssues" -> trim(label("Enable for issues", boolean(required))), "enableForIssues" -> trim(label("Enable for issues", boolean(required))),
"enableForPullRequests" -> trim(label("Enable for pull requests", boolean(required))), "enableForPullRequests" -> trim(label("Enable for pull requests", boolean(required))),
)(CustomFieldForm.apply) )(CustomFieldForm.apply)
@@ -284,93 +286,90 @@ 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
val url = params("url") val url = params("url")
val token = Some(params("token")) val token = Some(params("token"))
val ctype = WebHookContentType.valueOf(params("ctype")) val ctype = WebHookContentType.valueOf(params("ctype"))
val dummyWebHookInfo = RepositoryWebHook( val dummyWebHookInfo = RepositoryWebHook(
userName = repository.owner, userName = repository.owner,
repositoryName = repository.name, repositoryName = repository.name,
url = url, url = url,
ctype = ctype, ctype = ctype,
token = token token = token
)
val dummyPayload = {
val ownerAccount = getAccountByUserName(repository.owner).get
val commits =
if (JGitUtil.isEmpty(git)) List.empty
else
git.log
.add(git.getRepository.resolve(repository.repository.defaultBranch))
.setMaxCount(4)
.call
.iterator
.asScala
.map(new CommitInfo(_))
.toList
val pushedCommit = commits.drop(1)
WebHookPushPayload(
git = git,
sender = ownerAccount,
refName = "refs/heads/" + repository.repository.defaultBranch,
repositoryInfo = repository,
commits = pushedCommit,
repositoryOwner = ownerAccount,
oldId = commits.lastOption.map(_.id).map(ObjectId.fromString).getOrElse(ObjectId.zeroId()),
newId = commits.headOption.map(_.id).map(ObjectId.fromString).getOrElse(ObjectId.zeroId())
) )
val dummyPayload = { }
val ownerAccount = getAccountByUserName(repository.owner).get
val commits =
if (JGitUtil.isEmpty(git)) List.empty
else
git.log
.add(git.getRepository.resolve(repository.repository.defaultBranch))
.setMaxCount(4)
.call
.iterator
.asScala
.map(new CommitInfo(_))
.toList
val pushedCommit = commits.drop(1)
WebHookPushPayload( val (webHook, json, reqFuture, resFuture) =
git = git, callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload, context.settings).head
sender = ownerAccount,
refName = "refs/heads/" + repository.repository.defaultBranch,
repositoryInfo = repository,
commits = pushedCommit,
repositoryOwner = ownerAccount,
oldId = commits.lastOption.map(_.id).map(ObjectId.fromString).getOrElse(ObjectId.zeroId()),
newId = commits.headOption.map(_.id).map(ObjectId.fromString).getOrElse(ObjectId.zeroId())
)
}
val (webHook, json, reqFuture, resFuture) = val toErrorMap: PartialFunction[Throwable, Map[String, String]] = {
callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload, context.settings).head case e: java.net.UnknownHostException => Map("error" -> ("Unknown host " + e.getMessage))
case e: java.lang.IllegalArgumentException => Map("error" -> ("invalid url"))
case e: org.apache.http.client.ClientProtocolException => Map("error" -> ("invalid url"))
case NonFatal(e) => Map("error" -> (s"${e.getClass} ${e.getMessage}"))
}
val toErrorMap: PartialFunction[Throwable, Map[String, String]] = { contentType = formats("json")
case e: java.net.UnknownHostException => Map("error" -> ("Unknown host " + e.getMessage)) org.json4s.jackson.Serialization.write(
case e: java.lang.IllegalArgumentException => Map("error" -> ("invalid url")) Map(
case e: org.apache.http.client.ClientProtocolException => Map("error" -> ("invalid url")) "url" -> url,
case NonFatal(e) => Map("error" -> (s"${e.getClass} ${e.getMessage}")) "request" -> Await.result(
} reqFuture
.map(req =>
contentType = formats("json") Map(
org.json4s.jackson.Serialization.write( "headers" -> _headers(req.getAllHeaders),
Map( "payload" -> json
"url" -> url,
"request" -> Await.result(
reqFuture
.map(
req =>
Map(
"headers" -> _headers(req.getAllHeaders),
"payload" -> json
)
) )
.recover(toErrorMap), )
20 seconds .recover(toErrorMap),
), 20 seconds
"response" -> Await.result( ),
resFuture "response" -> Await.result(
.map( resFuture
res => .map(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), )
20 seconds .recover(toErrorMap),
) 20 seconds
) )
) )
)
} }
}) })
@@ -378,9 +377,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, false)
html.edithook(webhook, events, repository, false)
} getOrElse NotFound() } getOrElse NotFound()
}) })
@@ -404,23 +402,22 @@ 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 renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName) // Record activity log
// Record activity log val renameInfo = RenameRepositoryInfo(
val renameInfo = RenameRepositoryInfo( repository.owner,
repository.owner, form.repositoryName,
form.repositoryName, loginAccount.userName,
loginAccount.userName, repository.name
repository.name )
) recordActivity(renameInfo)
recordActivity(renameInfo) }
} redirect(s"/${repository.owner}/${form.repositoryName}")
redirect(s"/${repository.owner}/${form.repositoryName}") } else Forbidden()
} else Forbidden()
} }
}) })
@@ -428,24 +425,23 @@ 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) { // Update database and move git repository
// Update database and move git repository renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
renameRepository(repository.owner, repository.name, form.newOwner, repository.name) // Record activity log
// Record activity log val renameInfo = RenameRepositoryInfo(
val renameInfo = RenameRepositoryInfo( form.newOwner,
form.newOwner, repository.name,
repository.name, loginAccount.userName,
loginAccount.userName, repository.owner
repository.owner )
) recordActivity(renameInfo)
recordActivity(renameInfo) }
} redirect(s"/${form.newOwner}/${repository.name}")
redirect(s"/${form.newOwner}/${repository.name}") } else Forbidden()
} else Forbidden()
} }
}) })
@@ -511,6 +507,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
repository.name, repository.name,
form.fieldName, form.fieldName,
form.fieldType, form.fieldType,
if (form.fieldType == "enum") form.constraints else None,
form.enableForIssues, form.enableForIssues,
form.enableForPullRequests form.enableForPullRequests
) )
@@ -533,6 +530,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
params("fieldId").toInt, params("fieldId").toInt,
form.fieldName, form.fieldName,
form.fieldType, form.fieldType,
if (form.fieldType == "enum") form.constraints else None,
form.enableForIssues, form.enableForIssues,
form.enableForPullRequests form.enableForPullRequests
) )

View File

@@ -55,16 +55,14 @@ 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),
"useSMTP" -> trim(label("SMTP", boolean())), "useSMTP" -> trim(label("SMTP", boolean())),
@@ -123,8 +121,11 @@ 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))),
)(RepositoryViewerSettings.apply) "maxDiffFiles" -> trim(label("Max diff files", number(required))),
"maxDiffLines" -> trim(label("Max diff lines", number(required)))
)(RepositoryViewerSettings.apply),
"defaultBranch" -> trim(label("Default branch", text(required)))
)(SystemSettings.apply).verifying { settings => )(SystemSettings.apply).verifying { settings =>
Vector( Vector(
if (settings.ssh.enabled && settings.baseUrl.isEmpty) { if (settings.ssh.enabled && settings.baseUrl.isEmpty) {
@@ -250,28 +251,27 @@ 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")
val pkColumns = ListBuffer[String]() val pkColumns = ListBuffer[String]()
Using.resource(meta.getPrimaryKeys(null, null, tableName)) { rs => Using.resource(meta.getPrimaryKeys(null, null, tableName)) { rs =>
while (rs.next()) { while (rs.next()) {
pkColumns += rs.getString("COLUMN_NAME").toUpperCase pkColumns += rs.getString("COLUMN_NAME").toUpperCase
}
} }
val columns = ListBuffer[Column]()
Using.resource(meta.getColumns(null, "%", tableName, "%")) { rs =>
while (rs.next()) {
val columnName = rs.getString("COLUMN_NAME").toUpperCase
columns += Column(columnName, pkColumns.contains(columnName))
}
}
tables += Table(tableName.toUpperCase, columns.toSeq)
} }
val columns = ListBuffer[Column]()
Using.resource(meta.getColumns(null, "%", tableName, "%")) { rs =>
while (rs.next()) {
val columnName = rs.getString("COLUMN_NAME").toUpperCase
columns += Column(columnName, pkColumns.contains(columnName))
}
}
tables += Table(tableName.toUpperCase, columns.toSeq)
}
} }
html.dbviewer(tables.toSeq) html.dbviewer(tables.toSeq)
}) })
@@ -284,28 +284,26 @@ 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()) { rs =>
Using.resource(stmt.executeQuery()) { val meta = rs.getMetaData
rs => val columns = for (i <- 1 to meta.getColumnCount) yield {
val meta = rs.getMetaData meta.getColumnName(i)
val columns = for (i <- 1 to meta.getColumnCount) yield {
meta.getColumnName(i)
}
val result = ListBuffer[Map[String, String]]()
while (rs.next()) {
val row = columns.map { columnName =>
columnName -> Option(rs.getObject(columnName)).map(_.toString).getOrElse("<NULL>")
}.toMap
result += row
}
Ok(Serialization.write(Map("type" -> "query", "columns" -> columns, "rows" -> result)))
} }
} else { val result = ListBuffer[Map[String, String]]()
val rows = stmt.executeUpdate() while (rs.next()) {
Ok(Serialization.write(Map("type" -> "update", "rows" -> rows))) val row = columns.map { columnName =>
columnName -> Option(rs.getObject(columnName)).map(_.toString).getOrElse("<NULL>")
}.toMap
result += row
}
Ok(Serialization.write(Map("type" -> "query", "columns" -> columns, "rows" -> result)))
} }
} else {
val rows = stmt.executeUpdate()
Ok(Serialization.write(Map("type" -> "update", "rows" -> rows)))
}
} }
} catch { } catch {
case e: Exception => case e: Exception =>
@@ -322,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
@@ -419,44 +419,43 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
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, 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") } else {
} else { if (form.isRemoved) {
if (form.isRemoved) { // Remove repositories
// Remove repositories // getRepositoryNamesOfUser(userName).foreach { repositoryName =>
// getRepositoryNamesOfUser(userName).foreach { repositoryName => // deleteRepository(userName, repositoryName)
// deleteRepository(userName, repositoryName) // FileUtils.deleteDirectory(getRepositoryDir(userName, repositoryName))
// FileUtils.deleteDirectory(getRepositoryDir(userName, repositoryName)) // FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
// FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName)) // FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName)) // }
// } // Remove from GROUP_MEMBER and COLLABORATOR
// Remove from GROUP_MEMBER and COLLABORATOR removeUserRelatedData(userName)
removeUserRelatedData(userName)
}
updateAccount(
account.copy(
password = form.password.map(pbkdf2_sha256).getOrElse(account.password),
fullName = form.fullName,
mailAddress = form.mailAddress,
isAdmin = form.isAdmin,
description = form.description,
url = form.url,
isRemoved = form.isRemoved
)
)
updateImage(userName, form.fileId, form.clearImage)
updateAccountExtraMailAddresses(userName, form.extraMailAddresses.filter(_ != ""))
// call hooks
if (form.isRemoved) PluginRegistry().getAccountHooks.foreach(_.deleted(userName))
redirect("/admin/users")
} }
updateAccount(
account.copy(
password = form.password.map(pbkdf2_sha256).getOrElse(account.password),
fullName = form.fullName,
mailAddress = form.mailAddress,
isAdmin = form.isAdmin,
description = form.description,
url = form.url,
isRemoved = form.isRemoved
)
)
updateImage(userName, form.fileId, form.clearImage)
updateAccountExtraMailAddresses(userName, form.extraMailAddresses.filter(_ != ""))
// call hooks
if (form.isRemoved) PluginRegistry().getAccountHooks.foreach(_.deleted(userName))
redirect("/admin/users")
}
} getOrElse NotFound() } getOrElse NotFound()
}) })
@@ -497,13 +496,12 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
} }
.toList .toList
getAccountByUserName(groupName, true).map { getAccountByUserName(groupName, 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) {
// Remove from GROUP_MEMBER // Remove from GROUP_MEMBER
updateGroupMembers(form.groupName, Nil) updateGroupMembers(form.groupName, Nil)
// // Remove repositories // // Remove repositories
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName => // getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
// deleteRepository(groupName, repositoryName) // deleteRepository(groupName, repositoryName)
@@ -511,9 +509,9 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
// FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName)) // FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName))
// FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName)) // FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName))
// } // }
} else { } else {
// Update GROUP_MEMBER // Update GROUP_MEMBER
updateGroupMembers(form.groupName, members) updateGroupMembers(form.groupName, members)
// // Update COLLABORATOR for group repositories // // Update COLLABORATOR for group repositories
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName => // getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
// removeCollaborators(form.groupName, repositoryName) // removeCollaborators(form.groupName, repositoryName)
@@ -521,10 +519,10 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
// addCollaborator(form.groupName, repositoryName, userName) // addCollaborator(form.groupName, repositoryName, userName)
// } // }
// } // }
} }
updateImage(form.groupName, form.fileId, form.clearImage) updateImage(form.groupName, form.fileId, form.clearImage)
redirect("/admin/users") redirect("/admin/users")
} getOrElse NotFound() } getOrElse NotFound()
}) })
@@ -551,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
@@ -564,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 (
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean } value.split(",").exists {
}) None _.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
}
) None
else Some("Must select one manager at least.") else Some("Must select one manager at least.")
} }
} }

View File

@@ -84,9 +84,8 @@ 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

@@ -46,7 +46,7 @@ trait WikiControllerBase extends ControllerBase {
) )
val newForm = mapping( 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()))),
"currentPageName" -> trim(label("Current page name", text())), "currentPageName" -> trim(label("Current page name", text())),
@@ -54,7 +54,7 @@ trait WikiControllerBase extends ControllerBase {
)(WikiPageEditForm.apply) )(WikiPageEditForm.apply)
val editForm = mapping( 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()))),
"currentPageName" -> trim(label("Current page name", text(required))), "currentPageName" -> trim(label("Current page name", text(required))),
@@ -62,46 +62,56 @@ trait WikiControllerBase extends ControllerBase {
)(WikiPageEditForm.apply) )(WikiPageEditForm.apply)
get("/:owner/:repository/wiki")(referrersOnly { repository => get("/:owner/:repository/wiki")(referrersOnly { repository =>
getWikiPage(repository.owner, repository.name, "Home").map { page => val branch = getWikiBranch(repository.owner, repository.name)
getWikiPage(repository.owner, repository.name, "Home", branch).map { page =>
html.page( html.page(
"Home", "Home",
page, page,
getWikiPageList(repository.owner, repository.name), getWikiPageList(repository.owner, repository.name, branch),
repository, repository,
isEditable(repository), isEditable(repository),
getWikiPage(repository.owner, repository.name, "_Sidebar"), getWikiPage(repository.owner, repository.name, "_Sidebar", branch),
getWikiPage(repository.owner, repository.name, "_Footer") getWikiPage(repository.owner, repository.name, "_Footer", branch)
) )
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/Home/_edit") } getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/Home/_edit")
}) })
get("/:owner/:repository/wiki/:page")(referrersOnly { repository => get("/:owner/:repository/wiki/:page")(referrersOnly { repository =>
val pageName = StringUtil.urlDecode(params("page")) val pageName = StringUtil.urlDecode(params("page"))
val branch = getWikiBranch(repository.owner, repository.name)
getWikiPage(repository.owner, repository.name, pageName).map { page => getWikiPage(repository.owner, repository.name, pageName, branch).map { page =>
html.page( html.page(
pageName, pageName,
page, page,
getWikiPageList(repository.owner, repository.name), getWikiPageList(repository.owner, repository.name, branch),
repository, repository,
isEditable(repository), isEditable(repository),
getWikiPage(repository.owner, repository.name, "_Sidebar"), getWikiPage(repository.owner, repository.name, "_Sidebar", branch),
getWikiPage(repository.owner, repository.name, "_Footer") getWikiPage(repository.owner, repository.name, "_Footer", branch)
) )
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_edit") } getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_edit")
}) })
get("/:owner/:repository/wiki/:page/_history")(referrersOnly { repository => get("/:owner/:repository/wiki/:page/_history")(referrersOnly { repository =>
val pageName = StringUtil.urlDecode(params("page")) val pageName = StringUtil.urlDecode(params("page"))
val branch = getWikiBranch(repository.owner, repository.name)
Using.resource(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git => Using.resource(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git =>
JGitUtil.getCommitLog(git, "master", path = pageName + ".md") match { JGitUtil.getCommitLog(git, branch, path = pageName + ".md") match {
case Right((logs, hasNext)) => html.history(Some(pageName), logs, repository, isEditable(repository)) case Right((logs, hasNext)) => html.history(Some(pageName), logs, repository, isEditable(repository))
case Left(_) => NotFound() case Left(_) => NotFound()
} }
} }
}) })
private def getWikiBranch(owner: String, repository: String): String = {
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
git.getRepository.getBranch
}
}
get("/:owner/:repository/wiki/:page/_compare/:commitId")(referrersOnly { repository => get("/:owner/:repository/wiki/:page/_compare/:commitId")(referrersOnly { 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("\\.\\.\\.")
@@ -111,7 +121,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")
@@ -127,7 +147,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")
@@ -136,78 +164,78 @@ 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("\\.\\.\\.") val branch = getWikiBranch(repository.owner, repository.name)
if (revertWikiPage(repository.owner, repository.name, from, to, loginAccount, Some(pageName))) { if (revertWikiPage(repository.owner, repository.name, from, to, loginAccount, Some(pageName), branch)) {
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}") redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}")
} else { } else {
flash.update("info", "This patch was not able to be reversed.") flash.update("info", "This patch was not able to be reversed.")
redirect( redirect(
s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}" s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}"
) )
} }
} else Unauthorized() } else Unauthorized()
} }
}) })
get("/:owner/:repository/wiki/_revert/:commitId")(readableUsersOnly { repository => get("/:owner/:repository/wiki/_revert/:commitId")(readableUsersOnly { repository =>
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)
if (revertWikiPage(repository.owner, repository.name, from, to, loginAccount, None)) { if (revertWikiPage(repository.owner, repository.name, from, to, loginAccount, None, branch)) {
redirect(s"/${repository.owner}/${repository.name}/wiki") redirect(s"/${repository.owner}/${repository.name}/wiki")
} else { } else {
flash.update("info", "This patch was not able to be reversed.") flash.update("info", "This patch was not able to be reversed.")
redirect(s"/${repository.owner}/${repository.name}/wiki/_compare/${from}...${to}") redirect(s"/${repository.owner}/${repository.name}/wiki/_compare/${from}...${to}")
} }
} else Unauthorized() } else Unauthorized()
} }
}) })
get("/:owner/:repository/wiki/:page/_edit")(readableUsersOnly { repository => get("/:owner/:repository/wiki/:page/_edit")(readableUsersOnly { repository =>
if (isEditable(repository)) { if (isEditable(repository)) {
val pageName = StringUtil.urlDecode(params("page")) val pageName = StringUtil.urlDecode(params("page"))
html.edit(pageName, getWikiPage(repository.owner, repository.name, pageName), repository) val branch = getWikiBranch(repository.owner, repository.name)
html.edit(pageName, getWikiPage(repository.owner, repository.name, pageName, branch), repository)
} else Unauthorized() } else Unauthorized()
}) })
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, repository.name,
repository.name, form.currentPageName,
form.currentPageName, form.pageName,
form.pageName, appendNewLine(convertLineSeparator(form.content, "LF"), "LF"),
appendNewLine(convertLineSeparator(form.content, "LF"), "LF"), loginAccount,
loginAccount, form.message.getOrElse(""),
form.message.getOrElse(""), Some(form.id)
Some(form.id) ).foreach { commitId =>
).foreach { updateLastActivityDate(repository.owner, repository.name)
commitId => val wikiEditInfo =
updateLastActivityDate(repository.owner, repository.name) EditWikiPageInfo(repository.owner, repository.name, loginAccount.userName, form.pageName, commitId)
val wikiEditInfo = recordActivity(wikiEditInfo)
EditWikiPageInfo(repository.owner, repository.name, loginAccount.userName, form.pageName, commitId) callWebHookOf(repository.owner, repository.name, WebHook.Gollum, context.settings) {
recordActivity(wikiEditInfo) getAccountByUserName(repository.owner).map { repositoryUser =>
callWebHookOf(repository.owner, repository.name, WebHook.Gollum, context.settings) { WebHookGollumPayload("edited", form.pageName, commitId, repository, repositoryUser, loginAccount)
getAccountByUserName(repository.owner).map { repositoryUser => }
WebHookGollumPayload("edited", form.pageName, commitId, repository, repositoryUser, loginAccount)
}
}
} }
if (notReservedPageName(form.pageName)) { }
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}") if (notReservedPageName(form.pageName)) {
} else { redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
redirect(s"/${repository.owner}/${repository.name}/wiki") } else {
} redirect(s"/${repository.owner}/${repository.name}/wiki")
} else Unauthorized() }
} else Unauthorized()
} }
}) })
@@ -218,69 +246,67 @@ 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, repository.name,
repository.name, form.currentPageName,
form.currentPageName, form.pageName,
form.pageName, form.content,
form.content, loginAccount,
loginAccount, form.message.getOrElse(""),
form.message.getOrElse(""), None
None ).foreach { commitId =>
).foreach { updateLastActivityDate(repository.owner, repository.name)
commitId => val createWikiPageInfo =
updateLastActivityDate(repository.owner, repository.name) CreateWikiPageInfo(repository.owner, repository.name, loginAccount.userName, form.pageName)
val createWikiPageInfo = recordActivity(createWikiPageInfo)
CreateWikiPageInfo(repository.owner, repository.name, loginAccount.userName, form.pageName) callWebHookOf(repository.owner, repository.name, WebHook.Gollum, context.settings) {
recordActivity(createWikiPageInfo) getAccountByUserName(repository.owner).map { repositoryUser =>
callWebHookOf(repository.owner, repository.name, WebHook.Gollum, context.settings) { WebHookGollumPayload("created", form.pageName, commitId, repository, repositoryUser, loginAccount)
getAccountByUserName(repository.owner).map { repositoryUser => }
WebHookGollumPayload("created", form.pageName, commitId, repository, repositoryUser, loginAccount)
}
}
} }
}
if (notReservedPageName(form.pageName)) { if (notReservedPageName(form.pageName)) {
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}") redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
} else { } else {
redirect(s"/${repository.owner}/${repository.name}/wiki") redirect(s"/${repository.owner}/${repository.name}/wiki")
} }
} else Unauthorized() } else Unauthorized()
} }
}) })
get("/:owner/:repository/wiki/:page/_delete")(readableUsersOnly { repository => get("/:owner/:repository/wiki/:page/_delete")(readableUsersOnly { repository =>
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( repository.owner,
repository.owner, repository.name,
repository.name, 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, repository.name,
repository.name, loginAccount.userName,
loginAccount.userName, pageName
pageName )
) recordActivity(deleteWikiInfo)
recordActivity(deleteWikiInfo) updateLastActivityDate(repository.owner, repository.name)
updateLastActivityDate(repository.owner, repository.name)
redirect(s"/${repository.owner}/${repository.name}/wiki") redirect(s"/${repository.owner}/${repository.name}/wiki")
} else Unauthorized() } else Unauthorized()
} }
}) })
get("/:owner/:repository/wiki/_pages")(referrersOnly { repository => get("/:owner/:repository/wiki/_pages")(referrersOnly { repository =>
html.pages(getWikiPageList(repository.owner, repository.name), repository, isEditable(repository)) val branch = getWikiBranch(repository.owner, repository.name)
html.pages(getWikiPageList(repository.owner, repository.name, branch), repository, isEditable(repository))
}) })
get("/:owner/:repository/wiki/_history")(referrersOnly { repository => get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
@@ -309,13 +335,18 @@ trait WikiControllerBase extends ControllerBase {
value: String, value: String,
params: Map[String, Seq[String]], params: Map[String, Seq[String]],
messages: Messages messages: Messages
): Option[String] = ): Option[String] = {
getWikiPageList(params.value("owner"), params.value("repository")) val owner = params.value("owner")
val repository = params.value("repository")
val branch = getWikiBranch(owner, repository)
getWikiPageList(owner, repository, branch)
.find(_ == value) .find(_ == value)
.map(_ => "Page already exists.") .map(_ => "Page already exists.")
}
} }
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.")
@@ -326,7 +357,7 @@ trait WikiControllerBase extends ControllerBase {
} }
} }
private def notReservedPageName(value: String) = !(Array[String]("_Sidebar", "_Footer") contains value) private def notReservedPageName(value: String): Boolean = !(Array[String]("_Sidebar", "_Footer") contains value)
private def conflictForNew: Constraint = new Constraint() { private def conflictForNew: 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] = {
@@ -344,7 +375,13 @@ trait WikiControllerBase extends ControllerBase {
} }
} }
private def targetWikiPage = getWikiPage(params("owner"), params("repository"), params("pageName")) private def targetWikiPage: Option[WikiService.WikiPageInfo] = {
val owner = params("owner")
val repository = params("repository")
val pageName = params("pageName")
val branch = getWikiBranch(owner, repository)
getWikiPage(owner, repository, pageName, branch)
}
private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = { private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = {
repository.repository.options.wikiOption match { repository.repository.options.wikiOption match {

View File

@@ -57,23 +57,26 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
* iii. Create a reference * iii. Create a reference
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#create-a-reference * https://docs.github.com/en/free-pro-team@latest/rest/reference/git#create-a-reference
*/ */
post("/api/v3/repos/:owner/:repository/git/refs")(referrersOnly { 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.owner))) { git => val ref = git.getRepository.findRef(data.ref)
val ref = git.getRepository.findRef(data.ref) if (ref == null) {
if (ref == null) { val update = git.getRepository.updateRef(data.ref)
val update = git.getRepository.updateRef(data.ref) update.setNewObjectId(ObjectId.fromString(data.sha))
update.setNewObjectId(ObjectId.fromString(data.sha)) val result = update.update()
val result = update.update() result match {
result match { case Result.NEW =>
case Result.NEW => JsonFormat(ApiRef.fromRef(RepositoryName(repository.owner, repository.name), ref)) JsonFormat(
case _ => UnprocessableEntity(result.name()) ApiRef
} .fromRef(RepositoryName(repository.owner, repository.name), git.getRepository.findRef(data.ref))
} else { )
UnprocessableEntity("Ref already exists.") case _ => UnprocessableEntity(result.name())
} }
} else {
UnprocessableEntity("Ref already exists.")
} }
}
} getOrElse BadRequest() } getOrElse BadRequest()
}) })
@@ -83,24 +86,23 @@ 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.owner))) { git => val ref = git.getRepository.findRef(refName)
val ref = git.getRepository.findRef(refName) if (ref == null) {
if (ref == null) { UnprocessableEntity("Ref does not exist.")
UnprocessableEntity("Ref does not exist.") } else {
} else { val update = git.getRepository.updateRef(ref.getName)
val update = git.getRepository.updateRef(ref.getName) update.setNewObjectId(ObjectId.fromString(data.sha))
update.setNewObjectId(ObjectId.fromString(data.sha)) update.setForceUpdate(data.force)
update.setForceUpdate(data.force) val result = update.update()
val result = update.update() result match {
result match { case Result.FORCED | Result.FAST_FORWARD | Result.NO_CHANGE =>
case Result.FORCED | Result.FAST_FORWARD | Result.NO_CHANGE => JsonFormat(ApiRef.fromRef(RepositoryName(repository), git.getRepository.findRef(refName)))
JsonFormat(ApiRef.fromRef(RepositoryName(repository), update.getRef)) case _ => UnprocessableEntity(result.name())
case _ => UnprocessableEntity(result.name())
}
} }
} }
}
} getOrElse BadRequest() } getOrElse BadRequest()
}) })

View File

@@ -23,9 +23,8 @@ 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()
}) })
@@ -85,7 +84,7 @@ trait ApiIssueCommentControllerBase extends ControllerBase {
* iv. Delete a comment * iv. Delete a comment
* https://docs.github.com/en/rest/reference/issues#delete-an-issue-comment * https://docs.github.com/en/rest/reference/issues#delete-an-issue-comment
*/ */
delete("/api/v3/repos/:owner/:repo/issues/comments/:id")(readableUsersOnly { repository => delete("/api/v3/repos/:owner/:repository/issues/comments/:id")(readableUsersOnly { repository =>
val maybeDeleteResponse: Option[Either[ActionResult, Option[Int]]] = val maybeDeleteResponse: Option[Either[ActionResult, Option[Int]]] =
for { for {
commentId <- params("id").toIntOpt commentId <- params("id").toIntOpt

View File

@@ -29,7 +29,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,17 +39,16 @@ 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), user = ApiUser(issueUser),
user = ApiUser(issueUser), assignees = assigneeUsers.map(ApiUser(_)),
assignees = assigneeUsers.map(ApiUser(_)), labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
labels = getIssueLabels(repository.owner, repository.name, issue.issueId) .map(ApiLabel(_, RepositoryName(repository))),
.map(ApiLabel(_, RepositoryName(repository))), issue.milestoneId.flatMap { getApiMilestone(repository, _) }
issue.milestoneId.flatMap { getApiMilestone(repository, _) } )
)
}) })
}) })
@@ -126,7 +125,7 @@ trait ApiIssueControllerBase extends ControllerBase {
*/ */
/* /*
* vii. Unlock an issue * vii. Unlock an issue
* https://developer.github.com/v3/issues/#unlock-an-issue * https://developer.github.com/v3/issues/#unlock-an-issue
*/ */
} }

View File

@@ -1,5 +1,5 @@
package gitbucket.core.controller.api package gitbucket.core.controller.api
import gitbucket.core.api.{ApiError, ApiLabel, CreateALabel, JsonFormat} import gitbucket.core.api.{AddLabelsToAnIssue, ApiError, ApiLabel, CreateALabel, JsonFormat}
import gitbucket.core.controller.ControllerBase import gitbucket.core.controller.ControllerBase
import gitbucket.core.service._ import gitbucket.core.service._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
@@ -69,25 +69,24 @@ 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( ApiLabel(
ApiLabel( getLabel(repository.owner, repository.name, label.labelId).get,
getLabel(repository.owner, repository.name, label.labelId).get, RepositoryName(repository)
RepositoryName(repository)
)
) )
} else { )
// TODO ApiError should support errors field to enhance compatibility of GitHub API } else {
UnprocessableEntity( // TODO ApiError should support errors field to enhance compatibility of GitHub API
ApiError( UnprocessableEntity(
"Validation Failed", ApiError(
Some("https://developer.github.com/v3/issues/labels/#create-a-label") "Validation Failed",
) Some("https://developer.github.com/v3/issues/labels/#create-a-label")
) )
} )
}
} getOrElse NotFound() } getOrElse NotFound()
} }
}) getOrElse NotFound() }) getOrElse NotFound()
@@ -121,10 +120,10 @@ trait ApiIssueLabelControllerBase extends ControllerBase {
*/ */
post("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository => post("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
JsonFormat(for { JsonFormat(for {
data <- extractFromJsonBody[Seq[String]] data <- extractFromJsonBody[AddLabelsToAnIssue]
issueId <- params("id").toIntOpt issueId <- params("id").toIntOpt
} yield { } yield {
data.map { labelName => data.labels.map { labelName =>
val label = getLabel(repository.owner, repository.name, labelName).getOrElse( val label = getLabel(repository.owner, repository.name, labelName).getOrElse(
getLabel( getLabel(
repository.owner, repository.owner,
@@ -160,11 +159,11 @@ trait ApiIssueLabelControllerBase extends ControllerBase {
*/ */
put("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository => put("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
JsonFormat(for { JsonFormat(for {
data <- extractFromJsonBody[Seq[String]] data <- extractFromJsonBody[AddLabelsToAnIssue]
issueId <- params("id").toIntOpt issueId <- params("id").toIntOpt
} yield { } yield {
deleteAllIssueLabels(repository.owner, repository.name, issueId, true) deleteAllIssueLabels(repository.owner, repository.name, issueId, true)
data.map { labelName => data.labels.map { labelName =>
val label = getLabel(repository.owner, repository.name, labelName).getOrElse( val label = getLabel(repository.owner, repository.name, labelName).getOrElse(
getLabel( getLabel(
repository.owner, repository.owner,
@@ -189,7 +188,7 @@ trait ApiIssueLabelControllerBase extends ControllerBase {
}) })
/* /*
* xi Get labels for every issue in a milestone * xi Get labels for every issue in a milestone
* https://developer.github.com/v3/issues/labels/#get-labels-for-every-issue-in-a-milestone * https://developer.github.com/v3/issues/labels/#get-labels-for-every-issue-in-a-milestone
*/ */
} }

View File

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

@@ -71,6 +71,6 @@ trait ApiOrganizationControllerBase extends ControllerBase {
*/ */
/* /*
* should implement delete an organization API? * should implement delete an organization API?
*/ */
} }

View File

@@ -48,19 +48,18 @@ 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, headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
headRepo = ApiRepository(headRepo, ApiUser(headOwner)), baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
baseRepo = ApiRepository(repository, ApiUser(baseOwner)), user = ApiUser(issueUser),
user = ApiUser(issueUser), labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
labels = getIssueLabels(repository.owner, repository.name, issue.issueId) .map(ApiLabel(_, RepositoryName(repository))),
.map(ApiLabel(_, RepositoryName(repository))), assignees = assignees.map(ApiUser.apply),
assignees = assignees.map(ApiUser.apply), mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId) )
)
}) })
}) })
@@ -89,38 +88,37 @@ 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( owner = repository.owner,
owner = repository.owner, repository = repository.name,
repository = repository.name, loginUser = context.loginAccount.get.userName,
loginUser = context.loginAccount.get.userName, title = createPullReq.title,
title = createPullReq.title, content = createPullReq.body,
content = createPullReq.body, milestoneId = None,
milestoneId = None, priorityId = None,
priorityId = None, isPullRequest = true
isPullRequest = true )
)
createPullRequest( createPullRequest(
originRepository = repository, originRepository = repository,
issueId = issueId, issueId = issueId,
originBranch = createPullReq.base, originBranch = createPullReq.base,
requestUserName = reqOwner, requestUserName = reqOwner,
requestRepositoryName = repository.name, requestRepositoryName = repository.name,
requestBranch = reqBranch, requestBranch = reqBranch,
commitIdFrom = commitIdFrom.getName, commitIdFrom = commitIdFrom.getName,
commitIdTo = commitIdTo.getName, commitIdTo = commitIdTo.getName,
isDraft = createPullReq.draft.getOrElse(false), isDraft = createPullReq.draft.getOrElse(false),
loginAccount = context.loginAccount.get, loginAccount = context.loginAccount.get,
settings = context.settings settings = context.settings
) )
getApiPullRequest(repository, issueId).map(JsonFormat(_)) getApiPullRequest(repository, issueId).map(JsonFormat(_))
case _ => case _ =>
None None
} }
} }
.getOrElse { .getOrElse {
NotFound() NotFound()
@@ -128,28 +126,27 @@ 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) createPullRequest(
createPullRequest( originRepository = repository,
originRepository = repository, issueId = createPullReqAlt.issue,
issueId = createPullReqAlt.issue, originBranch = createPullReqAlt.base,
originBranch = createPullReqAlt.base, requestUserName = reqOwner,
requestUserName = reqOwner, requestRepositoryName = repository.name,
requestRepositoryName = repository.name, requestBranch = reqBranch,
requestBranch = reqBranch, commitIdFrom = commitIdFrom.getName,
commitIdFrom = commitIdFrom.getName, commitIdTo = commitIdTo.getName,
commitIdTo = commitIdTo.getName, isDraft = false,
isDraft = false, loginAccount = context.loginAccount.get,
loginAccount = context.loginAccount.get, settings = context.settings
settings = context.settings )
) getApiPullRequest(repository, createPullReqAlt.issue).map(JsonFormat(_))
getApiPullRequest(repository, createPullReqAlt.issue).map(JsonFormat(_)) case _ =>
case _ => None
None }
}
} }
.getOrElse { .getOrElse {
NotFound() NotFound()
@@ -190,26 +187,24 @@ 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 { Using.resource(Git.open(getRepositoryDir(owner, name))) { git =>
case (issue, pullreq) => val oldId = git.getRepository.resolve(pullreq.commitIdFrom)
Using.resource(Git.open(getRepositoryDir(owner, name))) { git => val newId = git.getRepository.resolve(pullreq.commitIdTo)
val oldId = git.getRepository.resolve(pullreq.commitIdFrom) val repoFullName = RepositoryName(repository)
val newId = git.getRepository.resolve(pullreq.commitIdTo) val commits = git.log
val repoFullName = RepositoryName(repository) .addRange(oldId, newId)
val commits = git.log .call
.addRange(oldId, newId) .iterator
.call .asScala
.iterator .map { c =>
.asScala ApiCommitListItem(new CommitInfo(c), repoFullName)
.map { c =>
ApiCommitListItem(new CommitInfo(c), repoFullName)
}
.toList
JsonFormat(commits)
} }
.toList
JsonFormat(commits)
} }
}
} getOrElse NotFound() } getOrElse NotFound()
}) })
/* /*
@@ -240,8 +235,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 +268,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

@@ -119,40 +119,39 @@ 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) .map { release =>
.map { release => val fileId = FileUtil.generateFileId
val fileId = FileUtil.generateFileId val buf = new Array[Byte](request.inputStream.available())
val buf = new Array[Byte](request.inputStream.available()) request.inputStream.read(buf)
request.inputStream.read(buf) FileUtils.writeByteArrayToFile(
FileUtils.writeByteArrayToFile( new File(
new File( getReleaseFilesDir(repository.owner, repository.name),
getReleaseFilesDir(repository.owner, repository.name), FileUtil.checkFilename(tag + "/" + fileId)
FileUtil.checkFilename(tag + "/" + fileId) ),
), buf
buf )
) createReleaseAsset(
createReleaseAsset( repository.owner,
repository.owner, repository.name,
repository.name, tag,
tag, fileId,
fileId, name,
name, request.contentLength.getOrElse(0),
request.contentLength.getOrElse(0), context.loginAccount.get
context.loginAccount.get )
) getReleaseAsset(repository.owner, repository.name, tag, fileId)
getReleaseAsset(repository.owner, repository.name, tag, fileId) .map { asset =>
.map { asset => JsonFormat(ApiReleaseAsset(asset, RepositoryName(repository)))
JsonFormat(ApiReleaseAsset(asset, RepositoryName(repository))) }
} .getOrElse {
.getOrElse { ApiError("Unknown error")
ApiError("Unknown error") }
} }
} .getOrElse(NotFound())
.getOrElse(NotFound())
}) })
/** /**
@@ -176,7 +175,7 @@ trait ApiReleaseControllerBase extends ControllerBase {
*/ */
/* /*
* xii. Delete a release asset * xii. Delete a release asset
* https://developer.github.com/v3/repos/releases/#edit-a-release-asset * https://developer.github.com/v3/repos/releases/#edit-a-release-asset
*/ */
} }

View File

@@ -1,11 +1,11 @@
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
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.scalatra.NoContent import org.scalatra.NoContent
@@ -30,10 +30,9 @@ 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 = git,
defaultBranch = repository.repository.defaultBranch, 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,21 +46,19 @@ 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 <- getBranchesNoMergeInfo(
br <- getBranches( git,
git, repository.repository.defaultBranch
repository.repository.defaultBranch, ).find(_.name == branch)
repository.repository.originUserName.isEmpty } yield {
).find(_.name == branch) val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
} yield { JsonFormat(
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch) ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtection(protection))(RepositoryName(repository))
JsonFormat( )
ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtection(protection))(RepositoryName(repository)) }) getOrElse NotFound()
)
}) getOrElse NotFound()
} }
}) })
@@ -274,31 +271,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[ApiBranchProtection.EnablingAndDisabling].map(_.protection) br <- getBranchesNoMergeInfo(
br <- getBranches( git,
git, repository.repository.defaultBranch
repository.repository.defaultBranch, ).find(_.name == branch)
repository.repository.originUserName.isEmpty } yield {
).find(_.name == branch) if (protection.enabled) {
} yield { enableBranchProtection(
if (protection.enabled) { repository.owner,
enableBranchProtection( repository.name,
repository.owner, branch,
repository.name, protection.status.enforcement_level == ApiBranchProtection.Everyone,
branch, protection.status.contexts
protection.status.enforcement_level == ApiBranchProtection.Everyone, )
protection.status.contexts } else {
) disableBranchProtection(repository.owner, repository.name, branch)
} else { }
disableBranchProtection(repository.owner, repository.name, branch) JsonFormat(ApiBranch(branch, ApiBranchCommit(br.commitId), protection)(RepositoryName(repository)))
} }) getOrElse NotFound()
JsonFormat(ApiBranch(branch, ApiBranchCommit(br.commitId), protection)(RepositoryName(repository)))
}) getOrElse NotFound()
} }
}) })
} }

View File

@@ -4,28 +4,21 @@ 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 with CommitsService with ProtectedBranchService with ReferrerAuthenticator =>
@@ -43,56 +36,53 @@ 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)) { revWalk =>
Using.resource(new RevWalk(repo)) { val objectId = repo.resolve(sha)
revWalk => revWalk.markStart(revWalk.parseCommit(objectId))
val objectId = repo.resolve(sha) if (path.nonEmpty) {
revWalk.markStart(revWalk.parseCommit(objectId)) revWalk.setTreeFilter(
if (path.nonEmpty) { AndTreeFilter.create(PathFilterGroup.createFromStrings(path.get), TreeFilter.ANY_DIFF)
revWalk.setTreeFilter( )
AndTreeFilter.create(PathFilterGroup.createFromStrings(path.get), TreeFilter.ANY_DIFF)
)
}
val revfilters = new ListBuffer[(RevFilter)]()
if (author.nonEmpty) {
revfilters += AuthorRevFilter.create(author.get)
}
if (since.nonEmpty) {
revfilters += CommitTimeRevFilter.after(
Date.from(LocalDateTime.parse(since.get, ISO_DATE_TIME).toInstant(ZoneOffset.UTC))
)
}
if (until.nonEmpty) {
revfilters += CommitTimeRevFilter.before(
Date.from(LocalDateTime.parse(until.get, ISO_DATE_TIME).toInstant(ZoneOffset.UTC))
)
}
if (page > 1) {
revfilters += SkipRevFilter.create(page * per_page - 2)
}
revfilters += MaxCountRevFilter.create(per_page);
revWalk.setRevFilter(
if (revfilters.size > 1) {
AndRevFilter.create(revfilters.toArray)
} else {
revfilters(0)
}
)
JsonFormat(revWalk.asScala.map {
commit =>
val commitInfo = new CommitInfo(commit)
ApiCommits(
repositoryName = RepositoryName(repository),
commitInfo = commitInfo,
diffs = JGitUtil.getDiffs(git, commitInfo.parents.headOption, commitInfo.id, false, true),
author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress),
committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress),
commentCount = getCommitComment(repository.owner, repository.name, commitInfo.id).size
)
})
} }
val revfilters = new ListBuffer[(RevFilter)]()
if (author.nonEmpty) {
revfilters += AuthorRevFilter.create(author.get)
}
if (since.nonEmpty) {
revfilters += CommitTimeRevFilter.after(
Date.from(LocalDateTime.parse(since.get, ISO_DATE_TIME).toInstant(ZoneOffset.UTC))
)
}
if (until.nonEmpty) {
revfilters += CommitTimeRevFilter.before(
Date.from(LocalDateTime.parse(until.get, ISO_DATE_TIME).toInstant(ZoneOffset.UTC))
)
}
if (page > 1) {
revfilters += SkipRevFilter.create(page * per_page - 2)
}
revfilters += MaxCountRevFilter.create(per_page);
revWalk.setRevFilter(
if (revfilters.size > 1) {
AndRevFilter.create(revfilters.toArray)
} else {
revfilters(0)
}
)
JsonFormat(revWalk.asScala.map { commit =>
val commitInfo = new CommitInfo(commit)
ApiCommits(
repositoryName = RepositoryName(repository),
commitInfo = commitInfo,
diffs = JGitUtil.getDiffs(git, commitInfo.parents.headOption, commitInfo.id, false, true),
author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress),
committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress),
commentCount = getCommitComment(repository.owner, repository.name, commitInfo.id).size
)
})
}
} }
}) })
@@ -105,24 +95,23 @@ 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 => new CommitInfo(revWalk.parseCommit(objectId))
new CommitInfo(revWalk.parseCommit(objectId)) }
}
JsonFormat( JsonFormat(
ApiCommits( ApiCommits(
repositoryName = RepositoryName(repository), repositoryName = RepositoryName(repository),
commitInfo = commitInfo, commitInfo = commitInfo,
diffs = JGitUtil.getDiffs(git, commitInfo.parents.headOption, commitInfo.id, false, true), diffs = JGitUtil.getDiffs(git, commitInfo.parents.headOption, commitInfo.id, false, true),
author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress), author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress),
committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress), committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress),
commentCount = getCommitComment(repository.owner, repository.name, sha).size commentCount = getCommitComment(repository.owner, repository.name, sha).size
)
) )
)
} }
}) })
@@ -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, branch).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

@@ -19,17 +19,16 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
* 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. .find { file =>
.find { file => !file.isDirectory && RepositoryService.readmeFiles.contains(file.name.toLowerCase)
!file.isDirectory && RepositoryService.readmeFiles.contains(file.name.toLowerCase) } match {
} match { case Some(x) => getContents(repository = repository, path = x.name, refStr = refStr, ignoreCase = true)
case Some(x) => getContents(repository = repository, path = x.name, refStr = refStr, ignoreCase = true) case _ => NotFound()
case _ => NotFound() }
}
} }
}) })
@@ -134,67 +133,65 @@ 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 { val branch = data.branch.getOrElse(repository.repository.defaultBranch)
val branch = data.branch.getOrElse(repository.repository.defaultBranch) val commit = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
val commit = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch)) revCommit.name
revCommit.name }
} 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")))) { git =>
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { val fileInfo = getFileInfo(git, commit, path, false)
git =>
val fileInfo = getFileInfo(git, commit, path, false)
fileInfo match { fileInfo match {
case Some(f) if !data.sha.contains(f.id.getName) => case Some(f) if !data.sha.contains(f.id.getName) =>
ApiError( ApiError(
"The blob SHA is not matched.", "The blob SHA is not matched.",
Some("https://docs.github.com/en/rest/reference/repos#create-or-update-file-contents") Some("https://docs.github.com/en/rest/reference/repos#create-or-update-file-contents")
)
case _ =>
commitFile(
repository,
branch,
path,
Some(paths.last),
data.sha.map(_ => paths.last),
StringUtil.base64Decode(data.content),
data.message,
commit,
loginAccount,
data.committer.map(_.name).getOrElse(loginAccount.fullName),
data.committer.map(_.email).getOrElse(loginAccount.mailAddress),
context.settings
) match {
case Left(error) =>
ApiError(s"Failed to commit a file: ${error}", None)
case Right((_, None)) =>
ApiError("Failed to commit a file.", None)
case Right((commitId, Some(blobId))) =>
Map(
"content" -> ApiContents(
"file",
paths.last,
path,
blobId.name,
Some(data.content),
Some("base64")
)(RepositoryName(repository)),
"commit" -> ApiCommit(
git,
RepositoryName(repository),
new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
)
) )
case _ =>
commitFile(
repository,
branch,
path,
Some(paths.last),
data.sha.map(_ => paths.last),
StringUtil.base64Decode(data.content),
data.message,
commit,
loginAccount,
data.committer.map(_.name).getOrElse(loginAccount.fullName),
data.committer.map(_.email).getOrElse(loginAccount.mailAddress),
context.settings
) match {
case Left(error) =>
ApiError(s"Failed to commit a file: ${error}", None)
case Right((_, None)) =>
ApiError("Failed to commit a file.", None)
case Right((commitId, Some(blobId))) =>
Map(
"content" -> ApiContents(
"file",
paths.last,
path,
blobId.name,
Some(data.content),
Some("base64")
)(RepositoryName(repository)),
"commit" -> ApiCommit(
git,
RepositoryName(repository),
new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
)
)
}
} }
} }
}) }
})
} }
}) })
@@ -205,9 +202,9 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
*/ */
/* /*
* vi. Download a repository archive (tar/zip) * vi. Download a repository archive (tar/zip)
* https://docs.github.com/en/rest/reference/repos#download-a-repository-archive-tar * https://docs.github.com/en/rest/reference/repos#download-a-repository-archive-tar
* https://docs.github.com/en/rest/reference/repos#download-a-repository-archive-zip * https://docs.github.com/en/rest/reference/repos#download-a-repository-archive-zip
*/ */
} }

View File

@@ -93,7 +93,8 @@ trait ApiRepositoryControllerBase extends ControllerBase {
data.name, data.name,
data.description, data.description,
data.`private`, data.`private`,
data.auto_init data.auto_init,
context.settings.defaultBranch
) )
Await.result(f, Duration.Inf) Await.result(f, Duration.Inf)
@@ -130,7 +131,8 @@ trait ApiRepositoryControllerBase extends ControllerBase {
data.name, data.name,
data.description, data.description,
data.`private`, data.`private`,
data.auto_init data.auto_init,
context.settings.defaultBranch
) )
Await.result(f, Duration.Inf) Await.result(f, Duration.Inf)
val repository = Database() withTransaction { session => val repository = Database() withTransaction { session =>

View File

@@ -47,9 +47,8 @@ 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

@@ -113,8 +113,8 @@ trait ApiRepositoryWebhookControllerBase extends ControllerBase {
*/ */
/* /*
* vi. Test the push repository webhook * vi. Test the push repository webhook
* https://docs.github.com/en/rest/reference/repos#test-the-push-repository-webhook * https://docs.github.com/en/rest/reference/repos#test-the-push-repository-webhook
*/ */
} }

View File

@@ -10,7 +10,7 @@ trait AccessTokenComponent { self: Profile =>
val userName = column[String]("USER_NAME") val userName = column[String]("USER_NAME")
val tokenHash = column[String]("TOKEN_HASH") val tokenHash = column[String]("TOKEN_HASH")
val note = column[String]("NOTE") val note = column[String]("NOTE")
def * = (accessTokenId, userName, tokenHash, note).<>(AccessToken.tupled, AccessToken.unapply) def * = (accessTokenId, userName, tokenHash, note).mapTo[AccessToken]
} }
} }
case class AccessToken( case class AccessToken(

View File

@@ -35,7 +35,7 @@ trait AccountComponent { self: Profile =>
groupAccount, groupAccount,
removed, removed,
description.? description.?
).<>(Account.tupled, Account.unapply) ).mapTo[Account]
} }
} }

View File

@@ -9,7 +9,7 @@ trait AccountExtraMailAddressComponent { self: Profile =>
val userName = column[String]("USER_NAME", O PrimaryKey) val userName = column[String]("USER_NAME", O PrimaryKey)
val extraMailAddress = column[String]("EXTRA_MAIL_ADDRESS", O PrimaryKey) val extraMailAddress = column[String]("EXTRA_MAIL_ADDRESS", O PrimaryKey)
def * = def * =
(userName, extraMailAddress).<>(AccountExtraMailAddress.tupled, AccountExtraMailAddress.unapply) (userName, extraMailAddress).mapTo[AccountExtraMailAddress]
} }
} }

View File

@@ -9,7 +9,7 @@ trait AccountFederationComponent { self: Profile =>
val issuer = column[String]("ISSUER") val issuer = column[String]("ISSUER")
val subject = column[String]("SUBJECT") val subject = column[String]("SUBJECT")
val userName = column[String]("USER_NAME") val userName = column[String]("USER_NAME")
def * = (issuer, subject, userName).<>(AccountFederation.tupled, AccountFederation.unapply) def * = (issuer, subject, userName).mapTo[AccountFederation]
def byPrimaryKey(issuer: String, subject: String): Rep[Boolean] = def byPrimaryKey(issuer: String, subject: String): Rep[Boolean] =
(this.issuer === issuer.bind) && (this.subject === subject.bind) (this.issuer === issuer.bind) && (this.subject === subject.bind)

View File

@@ -9,7 +9,7 @@ trait AccountPreferenceComponent { self: Profile =>
val userName = column[String]("USER_NAME", O PrimaryKey) val userName = column[String]("USER_NAME", O PrimaryKey)
val highlighterTheme = column[String]("HIGHLIGHTER_THEME") val highlighterTheme = column[String]("HIGHLIGHTER_THEME")
def * = def * =
(userName, highlighterTheme).<>(AccountPreference.tupled, AccountPreference.unapply) (userName, highlighterTheme).mapTo[AccountPreference]
def byPrimaryKey(userName: String): Rep[Boolean] = this.userName === userName.bind def byPrimaryKey(userName: String): Rep[Boolean] = this.userName === userName.bind
} }

View File

@@ -12,7 +12,7 @@ trait AccountWebHookComponent extends TemplateComponent { self: Profile =>
val url = column[String]("URL") val url = column[String]("URL")
val token = column[Option[String]]("TOKEN") val token = column[Option[String]]("TOKEN")
val ctype = column[WebHookContentType]("CTYPE") val ctype = column[WebHookContentType]("CTYPE")
def * = (userName, url, ctype, token).<>((AccountWebHook.apply _).tupled, AccountWebHook.unapply) def * = (userName, url, ctype, token).mapTo[AccountWebHook]
def byPrimaryKey(userName: String, url: String) = (this.userName === userName.bind) && (this.url === url.bind) def byPrimaryKey(userName: String, url: String) = (this.userName === userName.bind) && (this.url === url.bind)
} }

View File

@@ -12,7 +12,7 @@ trait AccountWebHookEventComponent extends TemplateComponent { self: Profile =>
val url = column[String]("URL") val url = column[String]("URL")
val event = column[WebHook.Event]("EVENT") val event = column[WebHook.Event]("EVENT")
def * = (userName, url, event).<>((AccountWebHookEvent.apply _).tupled, AccountWebHookEvent.unapply) def * = (userName, url, event).mapTo[AccountWebHookEvent]
def byAccountWebHook(userName: String, url: String) = (this.userName === userName.bind) && (this.url === url.bind) def byAccountWebHook(userName: String, url: String) = (this.userName === userName.bind) && (this.url === url.bind)

View File

@@ -11,7 +11,7 @@ trait ActivityComponent extends TemplateComponent { self: Profile =>
lazy val Activities = TableQuery[Activities] lazy val Activities = TableQuery[Activities]
class Activities(tag: Tag) extends Table[Activity](tag, "ACTIVITY") with BasicTemplate { class Activities(tag: Tag) extends Table[Activity](tag, "ACTIVITY") with BasicTemplate {
def * = ??? def * : slick.lifted.ProvenShape[Activity] = ???
} }
} }

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

@@ -8,7 +8,7 @@ trait CollaboratorComponent extends TemplateComponent { self: Profile =>
class Collaborators(tag: Tag) extends Table[Collaborator](tag, "COLLABORATOR") with BasicTemplate { class Collaborators(tag: Tag) extends Table[Collaborator](tag, "COLLABORATOR") with BasicTemplate {
val collaboratorName = column[String]("COLLABORATOR_NAME") val collaboratorName = column[String]("COLLABORATOR_NAME")
val role = column[String]("ROLE") val role = column[String]("ROLE")
def * = (userName, repositoryName, collaboratorName, role).<>(Collaborator.tupled, Collaborator.unapply) def * = (userName, repositoryName, collaboratorName, role).mapTo[Collaborator]
def byPrimaryKey(owner: String, repository: String, collaborator: String) = def byPrimaryKey(owner: String, repository: String, collaborator: String) =
byRepository(owner, repository) && (collaboratorName === collaborator.bind) byRepository(owner, repository) && (collaboratorName === collaborator.bind)

View File

@@ -21,7 +21,7 @@ trait IssueCommentComponent extends TemplateComponent { self: Profile =>
val updatedDate = column[java.util.Date]("UPDATED_DATE") val updatedDate = column[java.util.Date]("UPDATED_DATE")
def * = def * =
(userName, repositoryName, issueId, commentId, action, commentedUserName, content, registeredDate, updatedDate) (userName, repositoryName, issueId, commentId, action, commentedUserName, content, registeredDate, updatedDate)
.<>(IssueComment.tupled, IssueComment.unapply) .mapTo[IssueComment]
def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind
} }
@@ -75,7 +75,7 @@ trait CommitCommentComponent extends TemplateComponent { self: Profile =>
originalCommitId, originalCommitId,
originalOldLine, originalOldLine,
originalNewLine originalNewLine
).<>(CommitComment.tupled, CommitComment.unapply) ).mapTo[CommitComment]
def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind
} }

View File

@@ -30,7 +30,7 @@ trait CommitStatusComponent extends TemplateComponent { self: Profile =>
creator, creator,
registeredDate, registeredDate,
updatedDate updatedDate
).<>((CommitStatus.apply _).tupled, CommitStatus.unapply) ).mapTo[CommitStatus]
def byPrimaryKey(id: Int) = commitStatusId === id.bind def byPrimaryKey(id: Int) = commitStatusId === id.bind
} }
} }

View File

@@ -5,6 +5,7 @@ import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.util.StringUtil import gitbucket.core.util.StringUtil
import gitbucket.core.view.helpers import gitbucket.core.view.helpers
import org.scalatra.i18n.Messages import org.scalatra.i18n.Messages
import play.twirl.api.Html
trait CustomFieldComponent extends TemplateComponent { self: Profile => trait CustomFieldComponent extends TemplateComponent { self: Profile =>
import profile.api._ import profile.api._
@@ -15,11 +16,12 @@ trait CustomFieldComponent extends TemplateComponent { self: Profile =>
val fieldId = column[Int]("FIELD_ID", O AutoInc) val fieldId = column[Int]("FIELD_ID", O AutoInc)
val fieldName = column[String]("FIELD_NAME") val fieldName = column[String]("FIELD_NAME")
val fieldType = column[String]("FIELD_TYPE") val fieldType = column[String]("FIELD_TYPE")
val constraints = column[Option[String]]("CONSTRAINTS")
val enableForIssues = column[Boolean]("ENABLE_FOR_ISSUES") val enableForIssues = column[Boolean]("ENABLE_FOR_ISSUES")
val enableForPullRequests = column[Boolean]("ENABLE_FOR_PULL_REQUESTS") val enableForPullRequests = column[Boolean]("ENABLE_FOR_PULL_REQUESTS")
def * = def * =
(userName, repositoryName, fieldId, fieldName, fieldType, enableForIssues, enableForPullRequests) (userName, repositoryName, fieldId, fieldName, fieldType, constraints, enableForIssues, enableForPullRequests)
.<>(CustomField.tupled, CustomField.unapply) .mapTo[CustomField]
def byPrimaryKey(userName: String, repositoryName: String, fieldId: Int) = def byPrimaryKey(userName: String, repositoryName: String, fieldId: Int) =
(this.userName === userName.bind) && (this.repositoryName === repositoryName.bind) && (this.fieldId === fieldId.bind) (this.userName === userName.bind) && (this.repositoryName === repositoryName.bind) && (this.fieldId === fieldId.bind)
@@ -31,17 +33,28 @@ case class CustomField(
repositoryName: String, repositoryName: String,
fieldId: Int = 0, fieldId: Int = 0,
fieldName: String, fieldName: String,
fieldType: String, // long, double, string, or date fieldType: String, // long, double, string, date, or enum
constraints: Option[String],
enableForIssues: Boolean, enableForIssues: Boolean,
enableForPullRequests: Boolean enableForPullRequests: Boolean
) )
trait CustomFieldBehavior { trait CustomFieldBehavior {
def createHtml(repository: RepositoryInfo, fieldId: Int)(implicit conext: Context): String def createHtml(repository: RepositoryInfo, fieldId: Int, fieldName: String, constraints: Option[String])(implicit
def fieldHtml(repository: RepositoryInfo, issueId: Int, fieldId: Int, value: String, editable: Boolean)( context: Context
implicit context: Context
): String ): String
def validate(name: String, value: String, messages: Messages): Option[String] def fieldHtml(
repository: RepositoryInfo,
issueId: Int,
fieldId: Int,
fieldName: String,
constraints: Option[String],
value: String,
editable: Boolean
)(implicit
context: Context
): String
def validate(name: String, constraints: Option[String], value: String, messages: Messages): Option[String]
} }
object CustomFieldBehavior { object CustomFieldBehavior {
@@ -49,7 +62,7 @@ object CustomFieldBehavior {
if (value.isEmpty) None if (value.isEmpty) None
else { else {
CustomFieldBehavior(field.fieldType).flatMap { behavior => CustomFieldBehavior(field.fieldType).flatMap { behavior =>
behavior.validate(field.fieldName, value, messages) behavior.validate(field.fieldName, field.constraints, value, messages)
} }
} }
} }
@@ -60,12 +73,18 @@ object CustomFieldBehavior {
case "double" => Some(DoubleFieldBehavior) case "double" => Some(DoubleFieldBehavior)
case "string" => Some(StringFieldBehavior) case "string" => Some(StringFieldBehavior)
case "date" => Some(DateFieldBehavior) case "date" => Some(DateFieldBehavior)
case "enum" => Some(EnumFieldBehavior)
case _ => None case _ => None
} }
} }
case object LongFieldBehavior extends TextFieldBehavior { case object LongFieldBehavior extends TextFieldBehavior {
override def validate(name: String, value: String, messages: Messages): Option[String] = { override def validate(
name: String,
constraints: Option[String],
value: String,
messages: Messages
): Option[String] = {
try { try {
value.toLong value.toLong
None None
@@ -75,7 +94,12 @@ object CustomFieldBehavior {
} }
} }
case object DoubleFieldBehavior extends TextFieldBehavior { case object DoubleFieldBehavior extends TextFieldBehavior {
override def validate(name: String, value: String, messages: Messages): Option[String] = { override def validate(
name: String,
constraints: Option[String],
value: String,
messages: Messages
): Option[String] = {
try { try {
value.toDouble value.toDouble
None None
@@ -89,7 +113,12 @@ object CustomFieldBehavior {
private val pattern = "yyyy-MM-dd" private val pattern = "yyyy-MM-dd"
override protected val fieldType: String = "date" override protected val fieldType: String = "date"
override def validate(name: String, value: String, messages: Messages): Option[String] = { override def validate(
name: String,
constraints: Option[String],
value: String,
messages: Messages
): Option[String] = {
try { try {
new java.text.SimpleDateFormat(pattern).parse(value) new java.text.SimpleDateFormat(pattern).parse(value)
None None
@@ -100,10 +129,140 @@ object CustomFieldBehavior {
} }
} }
case object EnumFieldBehavior extends CustomFieldBehavior {
override def createHtml(repository: RepositoryInfo, fieldId: Int, fieldName: String, constraints: Option[String])(
implicit context: Context
): String = {
createPulldownHtml(repository, fieldId, fieldName, constraints, None, None)
}
override def fieldHtml(
repository: RepositoryInfo,
issueId: Int,
fieldId: Int,
fieldName: String,
constraints: Option[String],
value: String,
editable: Boolean
)(implicit context: Context): String = {
if (!editable) {
val sb = new StringBuilder
sb.append("""</div>""")
sb.append("""<div>""")
if (value == "") {
sb.append(s"""<span id="label-custom-field-$fieldId"><span class="muted small">No ${StringUtil.escapeHtml(
fieldName
)}</span></span>""")
} else {
sb.append(s"""<span id="label-custom-field-$fieldId"><span class="muted small">${StringUtil
.escapeHtml(value)}</span></span>""")
}
sb.toString()
} else {
createPulldownHtml(repository, fieldId, fieldName, constraints, Some(issueId), Some(value))
}
}
private def createPulldownHtml(
repository: RepositoryInfo,
fieldId: Int,
fieldName: String,
constraints: Option[String],
issueId: Option[Int],
value: Option[String]
)(implicit context: Context): String = {
val sb = new StringBuilder
sb.append("""<div class="pull-right">""")
sb.append(
gitbucket.core.helper.html
.dropdown("Edit", right = true, filter = (fieldName, s"Filter $fieldName")) {
val options = new StringBuilder()
options.append(
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>"""
)
constraints.foreach { x =>
x.split(",").map(_.trim).foreach { item =>
options.append(s"""<li>
| <a href="javascript:void(0);" class="custom-field-option-$fieldId" data-value="${StringUtil
.escapeHtml(item)}">
| ${gitbucket.core.helper.html.checkicon(value.contains(item))}
| ${StringUtil.escapeHtml(item)}
| </a>
|</li>
|""".stripMargin)
}
}
Html(options.toString())
}
.toString()
)
sb.append("""</div>""")
sb.append("""</div>""")
sb.append("""<div>""")
value match {
case None =>
sb.append(s"""<span id="label-custom-field-$fieldId"><span class="muted small">No ${StringUtil.escapeHtml(
fieldName
)}</span></span>""")
case Some(value) =>
sb.append(s"""<span id="label-custom-field-$fieldId"><span class="muted small">${StringUtil
.escapeHtml(value)}</span></span>""")
}
if (value.isEmpty || issueId.isEmpty) {
sb.append(s"""<input type="hidden" id="custom-field-$fieldId" name="custom-field-$fieldId" value=""/>""")
sb.append(s"""<script>
|$$('a.custom-field-option-$fieldId').click(function(){
| const value = $$(this).data('value');
| $$('a.custom-field-option-$fieldId i.octicon-check').removeClass('octicon-check');
| $$('#custom-field-$fieldId').val(value);
| if (value == '') {
| $$('#label-custom-field-$fieldId').html($$('<span class="muted small">').text('No ${StringUtil
.escapeHtml(fieldName)}'));
| } else {
| $$('#label-custom-field-$fieldId').html($$('<span class="muted small">').text(value));
| $$('a.custom-field-option-$fieldId[data-value=' + value + '] i').addClass('octicon-check');
| }
|});
|</script>""".stripMargin)
} else {
sb.append(s"""<script>
|$$('a.custom-field-option-$fieldId').click(function(){
| const value = $$(this).data('value');
| $$.post('${helpers.url(repository)}/issues/${issueId.get}/customfield/$fieldId',
| { value: value },
| function(data){
| $$('a.custom-field-option-$fieldId i.octicon-check').removeClass('octicon-check');
| if (value == '') {
| $$('#label-custom-field-$fieldId').html($$('<span class="muted small">').text('No ${StringUtil
.escapeHtml(fieldName)}'));
| } else {
| $$('#label-custom-field-$fieldId').html($$('<span class="muted small">').text(value));
| $$('a.custom-field-option-$fieldId[data-value=' + value + '] i').addClass('octicon-check');
| }
| }
| );
|});
|</script>
|""".stripMargin)
}
sb.toString()
}
override def validate(
name: String,
constraints: Option[String],
value: String,
messages: Messages
): Option[String] = None
}
trait TextFieldBehavior extends CustomFieldBehavior { trait TextFieldBehavior extends CustomFieldBehavior {
protected val fieldType = "text" protected val fieldType = "text"
def createHtml(repository: RepositoryInfo, fieldId: Int)(implicit context: Context): String = { override def createHtml(repository: RepositoryInfo, fieldId: Int, fieldName: String, constraints: Option[String])(
implicit context: Context
): String = {
val sb = new StringBuilder val sb = new StringBuilder
sb.append( sb.append(
s"""<input type="$fieldType" class="form-control input-sm" id="custom-field-$fieldId" name="custom-field-$fieldId" data-field-id="$fieldId" style="width: 120px;"/>""" s"""<input type="$fieldType" class="form-control input-sm" id="custom-field-$fieldId" name="custom-field-$fieldId" data-field-id="$fieldId" style="width: 120px;"/>"""
@@ -111,8 +270,7 @@ object CustomFieldBehavior {
sb.append(s"""<script> sb.append(s"""<script>
|$$('#custom-field-$fieldId').focusout(function(){ |$$('#custom-field-$fieldId').focusout(function(){
| const $$this = $$(this); | const $$this = $$(this);
| const fieldId = $$this.data('field-id'); | $$.post('${helpers.url(repository)}/issues/customfield_validation/$fieldId',
| $$.post('${helpers.url(repository)}/issues/customfield_validation/' + fieldId,
| { value: $$this.val() }, | { value: $$this.val() },
| function(data){ | function(data){
| if (data != '') { | if (data != '') {
@@ -128,14 +286,34 @@ object CustomFieldBehavior {
sb.toString() sb.toString()
} }
def fieldHtml(repository: RepositoryInfo, issueId: Int, fieldId: Int, value: String, editable: Boolean)( override def fieldHtml(
implicit context: Context repository: RepositoryInfo,
issueId: Int,
fieldId: Int,
fieldName: String,
constraints: Option[String],
value: String,
editable: Boolean
)(implicit
context: Context
): String = { ): String = {
val sb = new StringBuilder val sb = new StringBuilder
sb.append( if (value.nonEmpty) {
s"""<span id="custom-field-$fieldId-label" class="custom-field-label">${StringUtil sb.append(
.escapeHtml(value)}</span>""".stripMargin s"""<span id="custom-field-$fieldId-label" class="custom-field-label">${StringUtil
) .escapeHtml(value)}</span>"""
)
} else {
if (editable) {
sb.append(
s"""<span id="custom-field-$fieldId-label" class="custom-field-label"><i class="octicon octicon-pencil" style="cursor: pointer;"></i></span>"""
)
} else {
sb.append(
s"""<span id="custom-field-$fieldId-label" class="custom-field-label">N/A</span>"""
)
}
}
if (editable) { if (editable) {
sb.append( sb.append(
s"""<input type="$fieldType" id="custom-field-$fieldId-editor" class="form-control input-sm custom-field-editor" data-field-id="$fieldId" style="width: 120px; display: none;"/>""" s"""<input type="$fieldType" id="custom-field-$fieldId-editor" class="form-control input-sm custom-field-editor" data-field-id="$fieldId" style="width: 120px; display: none;"/>"""
@@ -149,19 +327,22 @@ object CustomFieldBehavior {
| |
|$$('#custom-field-$fieldId-editor').focusout(function(){ |$$('#custom-field-$fieldId-editor').focusout(function(){
| const $$this = $$(this); | const $$this = $$(this);
| const fieldId = $$this.data('field-id'); | $$.post('${helpers.url(repository)}/issues/customfield_validation/$fieldId',
| $$.post('${helpers.url(repository)}/issues/customfield_validation/' + fieldId,
| { value: $$this.val() }, | { value: $$this.val() },
| function(data){ | function(data){
| if (data != '') { | if (data != '') {
| $$('#custom-field-$fieldId-error').text(data); | $$('#custom-field-$fieldId-error').text(data);
| } else { | } else {
| $$('#custom-field-$fieldId-error').text(''); | $$('#custom-field-$fieldId-error').text('');
| $$.post('${helpers.url(repository)}/issues/$issueId/customfield/' + fieldId, | $$.post('${helpers.url(repository)}/issues/$issueId/customfield/$fieldId',
| { value: $$this.val() }, | { value: $$this.val() },
| function(data){ | function(data){
| $$this.hide(); | $$this.hide();
| $$this.prev().text(data).show(); | if (data == '') {
| $$this.prev().html('<i class="octicon octicon-pencil" style="cursor: pointer;">').show();
| } else {
| $$this.prev().text(data).show();
| }
| } | }
| ); | );
| } | }
@@ -186,6 +367,11 @@ object CustomFieldBehavior {
sb.toString() sb.toString()
} }
def validate(name: String, value: String, messages: Messages): Option[String] = None override def validate(
name: String,
constraints: Option[String],
value: String,
messages: Messages
): Option[String] = None
} }
} }

View File

@@ -11,7 +11,7 @@ trait DeployKeyComponent extends TemplateComponent { self: Profile =>
val publicKey = column[String]("PUBLIC_KEY") val publicKey = column[String]("PUBLIC_KEY")
val allowWrite = column[Boolean]("ALLOW_WRITE") val allowWrite = column[Boolean]("ALLOW_WRITE")
def * = def * =
(userName, repositoryName, deployKeyId, title, publicKey, allowWrite).<>(DeployKey.tupled, DeployKey.unapply) (userName, repositoryName, deployKeyId, title, publicKey, allowWrite).mapTo[DeployKey]
def byPrimaryKey(userName: String, repositoryName: String, deployKeyId: Int) = def byPrimaryKey(userName: String, repositoryName: String, deployKeyId: Int) =
(this.userName === userName.bind) && (this.repositoryName === repositoryName.bind) && (this.deployKeyId === deployKeyId.bind) (this.userName === userName.bind) && (this.repositoryName === repositoryName.bind) && (this.deployKeyId === deployKeyId.bind)

View File

@@ -11,7 +11,7 @@ trait GpgKeyComponent { self: Profile =>
val gpgKeyId = column[Long]("GPG_KEY_ID") val gpgKeyId = column[Long]("GPG_KEY_ID")
val title = column[String]("TITLE") val title = column[String]("TITLE")
val publicKey = column[String]("PUBLIC_KEY") val publicKey = column[String]("PUBLIC_KEY")
def * = (userName, keyId, gpgKeyId, title, publicKey).<>(GpgKey.tupled, GpgKey.unapply) def * = (userName, keyId, gpgKeyId, title, publicKey).mapTo[GpgKey]
def byPrimaryKey(userName: String, keyId: Int) = def byPrimaryKey(userName: String, keyId: Int) =
(this.userName === userName.bind) && (this.keyId === keyId.bind) (this.userName === userName.bind) && (this.keyId === keyId.bind)

View File

@@ -9,7 +9,7 @@ trait GroupMemberComponent { self: Profile =>
val groupName = column[String]("GROUP_NAME", O PrimaryKey) val groupName = column[String]("GROUP_NAME", O PrimaryKey)
val userName = column[String]("USER_NAME", O PrimaryKey) val userName = column[String]("USER_NAME", O PrimaryKey)
val isManager = column[Boolean]("MANAGER") val isManager = column[Boolean]("MANAGER")
def * = (groupName, userName, isManager).<>(GroupMember.tupled, GroupMember.unapply) def * = (groupName, userName, isManager).mapTo[GroupMember]
} }
} }

View File

@@ -47,7 +47,7 @@ trait IssueComponent extends TemplateComponent { self: Profile =>
registeredDate, registeredDate,
updatedDate, updatedDate,
pullRequest pullRequest
).<>(Issue.tupled, Issue.unapply) ).mapTo[Issue]
def byPrimaryKey(owner: String, repository: String, issueId: Int) = byIssue(owner, repository, issueId) def byPrimaryKey(owner: String, repository: String, issueId: Int) = byIssue(owner, repository, issueId)
} }

View File

@@ -9,8 +9,7 @@ trait IssueAssigneeComponent extends TemplateComponent { self: Profile =>
class IssueAssignees(tag: Tag) extends Table[IssueAssignee](tag, "ISSUE_ASSIGNEE") with IssueTemplate { class IssueAssignees(tag: Tag) extends Table[IssueAssignee](tag, "ISSUE_ASSIGNEE") with IssueTemplate {
val assigneeUserName = column[String]("ASSIGNEE_USER_NAME") val assigneeUserName = column[String]("ASSIGNEE_USER_NAME")
def * = def * =
(userName, repositoryName, issueId, assigneeUserName) (userName, repositoryName, issueId, assigneeUserName).mapTo[IssueAssignee]
.<>(IssueAssignee.tupled, IssueAssignee.unapply)
def byPrimaryKey(owner: String, repository: String, issueId: Int, assigneeUserName: String) = { def byPrimaryKey(owner: String, repository: String, issueId: Int, assigneeUserName: String) = {
byIssue(owner, repository, issueId) && this.assigneeUserName === assigneeUserName.bind byIssue(owner, repository, issueId) && this.assigneeUserName === assigneeUserName.bind

View File

@@ -13,8 +13,7 @@ trait IssueCustomFieldComponent extends TemplateComponent { self: Profile =>
val fieldId = column[Int]("FIELD_ID", O.PrimaryKey) val fieldId = column[Int]("FIELD_ID", O.PrimaryKey)
val value = column[String]("VALUE") val value = column[String]("VALUE")
def * = def * =
(userName, repositoryName, issueId, fieldId, value) (userName, repositoryName, issueId, fieldId, value).mapTo[IssueCustomField]
.<>(IssueCustomField.tupled, IssueCustomField.unapply)
def byPrimaryKey(owner: String, repository: String, issueId: Int, fieldId: Int) = { def byPrimaryKey(owner: String, repository: String, issueId: Int, fieldId: Int) = {
this.userName === owner.bind && this.repositoryName === repository.bind && this.issueId === issueId.bind && this.fieldId === fieldId.bind this.userName === owner.bind && this.repositoryName === repository.bind && this.issueId === issueId.bind && this.fieldId === fieldId.bind

View File

@@ -6,7 +6,7 @@ trait IssueLabelComponent extends TemplateComponent { self: Profile =>
lazy val IssueLabels = TableQuery[IssueLabels] lazy val IssueLabels = TableQuery[IssueLabels]
class IssueLabels(tag: Tag) extends Table[IssueLabel](tag, "ISSUE_LABEL") with IssueTemplate with LabelTemplate { class IssueLabels(tag: Tag) extends Table[IssueLabel](tag, "ISSUE_LABEL") with IssueTemplate with LabelTemplate {
def * = (userName, repositoryName, issueId, labelId).<>(IssueLabel.tupled, IssueLabel.unapply) def * = (userName, repositoryName, issueId, labelId).mapTo[IssueLabel]
def byPrimaryKey(owner: String, repository: String, issueId: Int, labelId: Int) = def byPrimaryKey(owner: String, repository: String, issueId: Int, labelId: Int) =
byIssue(owner, repository, issueId) && (this.labelId === labelId.bind) byIssue(owner, repository, issueId) && (this.labelId === labelId.bind)
} }

View File

@@ -9,7 +9,7 @@ trait LabelComponent extends TemplateComponent { self: Profile =>
override val labelId = column[Int]("LABEL_ID", O AutoInc) override val labelId = column[Int]("LABEL_ID", O AutoInc)
override val labelName = column[String]("LABEL_NAME") override val labelName = column[String]("LABEL_NAME")
val color = column[String]("COLOR") val color = column[String]("COLOR")
def * = (userName, repositoryName, labelId, labelName, color).<>(Label.tupled, Label.unapply) def * = (userName, repositoryName, labelId, labelName, color).mapTo[Label]
def byPrimaryKey(owner: String, repository: String, labelId: Int) = byLabel(owner, repository, labelId) def byPrimaryKey(owner: String, repository: String, labelId: Int) = byLabel(owner, repository, labelId)
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], labelId: Rep[Int]) = def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], labelId: Rep[Int]) =

View File

@@ -13,8 +13,7 @@ trait MilestoneComponent extends TemplateComponent { self: Profile =>
val dueDate = column[Option[java.util.Date]]("DUE_DATE") val dueDate = column[Option[java.util.Date]]("DUE_DATE")
val closedDate = column[Option[java.util.Date]]("CLOSED_DATE") val closedDate = column[Option[java.util.Date]]("CLOSED_DATE")
def * = def * =
(userName, repositoryName, milestoneId, title, description, dueDate, closedDate) (userName, repositoryName, milestoneId, title, description, dueDate, closedDate).mapTo[Milestone]
.<>(Milestone.tupled, Milestone.unapply)
def byPrimaryKey(owner: String, repository: String, milestoneId: Int) = byMilestone(owner, repository, milestoneId) def byPrimaryKey(owner: String, repository: String, milestoneId: Int) = byMilestone(owner, repository, milestoneId)
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], milestoneId: Rep[Int]) = def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], milestoneId: Rep[Int]) =

View File

@@ -13,8 +13,7 @@ trait PriorityComponent extends TemplateComponent { self: Profile =>
val isDefault = column[Boolean]("IS_DEFAULT") val isDefault = column[Boolean]("IS_DEFAULT")
val color = column[String]("COLOR") val color = column[String]("COLOR")
def * = def * =
(userName, repositoryName, priorityId, priorityName, description.?, isDefault, ordering, color) (userName, repositoryName, priorityId, priorityName, description.?, isDefault, ordering, color).mapTo[Priority]
.<>(Priority.tupled, Priority.unapply)
def byPrimaryKey(owner: String, repository: String, priorityId: Int) = byPriority(owner, repository, priorityId) def byPrimaryKey(owner: String, repository: String, priorityId: Int) = byPriority(owner, repository, priorityId)
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], priorityId: Rep[Int]) = def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], priorityId: Rep[Int]) =

View File

@@ -7,7 +7,7 @@ trait ProtectedBranchComponent extends TemplateComponent { self: Profile =>
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")
def * = (userName, repositoryName, branch, statusCheckAdmin).<>(ProtectedBranch.tupled, ProtectedBranch.unapply) def * = (userName, repositoryName, branch, statusCheckAdmin).mapTo[ProtectedBranch]
def byPrimaryKey(userName: String, repositoryName: String, branch: String) = def byPrimaryKey(userName: String, repositoryName: String, branch: String) =
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]) =
@@ -20,7 +20,7 @@ trait ProtectedBranchComponent extends TemplateComponent { self: Profile =>
with BranchTemplate { with BranchTemplate {
val context = column[String]("CONTEXT") val context = column[String]("CONTEXT")
def * = def * =
(userName, repositoryName, branch, context).<>(ProtectedBranchContext.tupled, ProtectedBranchContext.unapply) (userName, repositoryName, branch, context).mapTo[ProtectedBranchContext]
} }
} }

View File

@@ -25,7 +25,7 @@ trait PullRequestComponent extends TemplateComponent { self: Profile =>
commitIdFrom, commitIdFrom,
commitIdTo, commitIdTo,
isDraft isDraft
).<>(PullRequest.tupled, PullRequest.unapply) ).mapTo[PullRequest]
def byPrimaryKey(userName: String, repositoryName: String, issueId: Int) = def byPrimaryKey(userName: String, repositoryName: String, issueId: Int) =
byIssue(userName, repositoryName, issueId) byIssue(userName, repositoryName, issueId)

View File

@@ -20,7 +20,7 @@ trait ReleaseAssetComponent extends TemplateComponent { self: Profile =>
def * = def * =
(userName, repositoryName, tag, releaseAssetId, fileName, label, size, uploader, registeredDate, updatedDate) (userName, repositoryName, tag, releaseAssetId, fileName, label, size, uploader, registeredDate, updatedDate)
.<>(ReleaseAsset.tupled, ReleaseAsset.unapply) .mapTo[ReleaseAsset]
def byPrimaryKey(owner: String, repository: String, tag: String, fileName: String) = def byPrimaryKey(owner: String, repository: String, tag: String, fileName: String) =
byTag(owner, repository, tag) && (this.fileName === fileName.bind) byTag(owner, repository, tag) && (this.fileName === fileName.bind)
def byTag(owner: String, repository: String, tag: String) = def byTag(owner: String, repository: String, tag: String) =

View File

@@ -15,8 +15,7 @@ trait ReleaseTagComponent extends TemplateComponent { self: Profile =>
val updatedDate = column[java.util.Date]("UPDATED_DATE") val updatedDate = column[java.util.Date]("UPDATED_DATE")
def * = def * =
(userName, repositoryName, name, tag, author, content, registeredDate, updatedDate) (userName, repositoryName, name, tag, author, content, registeredDate, updatedDate).mapTo[ReleaseTag]
.<>(ReleaseTag.tupled, ReleaseTag.unapply)
def byPrimaryKey(owner: String, repository: String, tag: String) = byTag(owner, repository, tag) def byPrimaryKey(owner: String, repository: String, tag: String) = byTag(owner, repository, tag)
def byTag(owner: String, repository: String, tag: String) = def byTag(owner: String, repository: String, tag: String) =
byRepository(owner, repository) && (this.tag === tag.bind) byRepository(owner, repository) && (this.tag === tag.bind)

View File

@@ -53,24 +53,24 @@ 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, repository._3,
repository._3, repository._4,
repository._4, repository._5,
repository._5, repository._6,
repository._6, repository._7,
repository._7, repository._8,
repository._8, repository._9,
repository._9, repository._10,
repository._10, repository._11,
repository._11, repository._12,
repository._12, RepositoryOptions.tupled.apply(options)
RepositoryOptions.tupled.apply(options) )
) },
}, { (r: Repository) => { (r: Repository) =>
Some( Some(
( (
( (

View File

@@ -14,8 +14,7 @@ trait RepositoryWebHookComponent extends TemplateComponent { self: Profile =>
val token = column[Option[String]]("TOKEN") val token = column[Option[String]]("TOKEN")
val ctype = column[WebHookContentType]("CTYPE") val ctype = column[WebHookContentType]("CTYPE")
def * = def * =
(userName, repositoryName, hookId, url, ctype, token) (userName, repositoryName, hookId, url, ctype, token).mapTo[RepositoryWebHook]
.<>((RepositoryWebHook.apply _).tupled, RepositoryWebHook.unapply)
def byRepositoryUrl(owner: String, repository: String, url: String) = def byRepositoryUrl(owner: String, repository: String, url: String) =
byRepository(owner, repository) && (this.url === url.bind) byRepository(owner, repository) && (this.url === url.bind)

View File

@@ -12,7 +12,7 @@ trait RepositoryWebHookEventComponent extends TemplateComponent { self: Profile
val url = column[String]("URL") val url = column[String]("URL")
val event = column[WebHook.Event]("EVENT") val event = column[WebHook.Event]("EVENT")
def * = def * =
(userName, repositoryName, url, event).<>((RepositoryWebHookEvent.apply _).tupled, RepositoryWebHookEvent.unapply) (userName, repositoryName, url, event).mapTo[RepositoryWebHookEvent]
def byRepositoryWebHook(owner: String, repository: String, url: String) = def byRepositoryWebHook(owner: String, repository: String, url: String) =
byRepository(owner, repository) && (this.url === url.bind) byRepository(owner, repository) && (this.url === url.bind)

View File

@@ -10,7 +10,7 @@ trait SshKeyComponent { self: Profile =>
val sshKeyId = column[Int]("SSH_KEY_ID", O AutoInc) val sshKeyId = column[Int]("SSH_KEY_ID", O AutoInc)
val title = column[String]("TITLE") val title = column[String]("TITLE")
val publicKey = column[String]("PUBLIC_KEY") val publicKey = column[String]("PUBLIC_KEY")
def * = (userName, sshKeyId, title, publicKey).<>(SshKey.tupled, SshKey.unapply) def * = (userName, sshKeyId, title, publicKey).mapTo[SshKey]
def byPrimaryKey(userName: String, sshKeyId: Int) = def byPrimaryKey(userName: String, sshKeyId: Int) =
(this.userName === userName.bind) && (this.sshKeyId === sshKeyId.bind) (this.userName === userName.bind) && (this.sshKeyId === sshKeyId.bind)

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,25 +340,20 @@ 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 =>
registry.addRepositoryRouting(routing) registry.addRepositoryRouting(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 = ()
} }

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