Compare commits

...

393 Commits

Author SHA1 Message Date
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
Naoki Takezoe
88e59405c5 Release 4.38.0 (#3123) 2022-09-04 00:36:03 +09:00
Naoki Takezoe
8160c93c15 Fix ref and ssh_url in webhook payload (#3122) 2022-09-03 13:15:37 +09:00
Naoki Takezoe
3c8113d607 Minor code cleanup for GitRepositoryServlet (#3121) 2022-09-03 12:27:06 +09:00
scala-steward-bot
8e2f421e76 Update markedj to 1.0.17 (#3120) 2022-09-03 12:19:37 +09:00
Scala Steward
56df833b4a Update oauth2-oidc-sdk to 9.42 2022-09-02 06:27:00 +09:00
Naoki Takezoe
119d7d0be0 Revert "Drop unused ActivityComponent (#3024)" (#3117)
This reverts commit bd06e6d4dc.
2022-08-28 03:33:19 +09:00
Scala Steward
d4d2ca19a1 Update oauth2-oidc-sdk to 9.41.1 2022-08-27 07:59:51 +09:00
Naoki Takezoe
4ce5597d67 Remove a link to the demo site from README (#3115) 2022-08-26 14:33:12 +09:00
Scala Steward
17888327b3 Update postgresql to 42.5.0 2022-08-25 07:01:14 +09:00
Naoki Takezoe
997def54ab Set UTF-8 as the default commit charset at the online text editor (#3112) 2022-08-21 22:50:12 +09:00
Naoki Takezoe
a407e75459 Remove unused import (#3111) 2022-08-21 21:52:55 +09:00
Scala Steward
cece53aad9 Update github-api to 1.308 2022-08-19 08:40:16 +09:00
Scala Steward
a2d63bc5f8 Update postgresql to 42.4.2 2022-08-18 07:26:45 +09:00
Scala Steward
0c81c63ab7 Update mockito-core to 4.7.0 2022-08-13 22:35:03 +09:00
Scala Steward
3e8f1eee22 Update oauth2-oidc-sdk to 9.41 2022-08-12 07:52:55 +09:00
Scala Steward
a91d1def45 Update oauth2-oidc-sdk to 9.40 2022-08-09 22:49:48 +09:00
Scala Steward
032e6d1c57 Update oauth2-oidc-sdk to 9.39.1 2022-08-09 17:31:16 +09:00
yurafuca
93eb133cba Fix milestone count (#3103) 2022-08-09 12:28:07 +09:00
Scala Steward
b7b78842da Update mariadb-java-client to 3.0.7 2022-08-05 05:48:20 +09:00
Scala Steward
8100ccaf5f Update postgresql to 42.4.1 2022-08-04 07:13:05 +09:00
Scala Steward
936e1c0e96 Update testcontainers-scala to 0.40.10 2022-07-30 12:07:10 +09:00
Scala Steward
1a7274e475 Update solidbase to 1.0.5 2022-07-25 18:59:59 +09:00
Scala Steward
ac7b83b6ed Update java-diff-utils to 4.12 2022-07-23 12:32:01 +09:00
Scala Steward
e298279032 Update apache-sshd to 2.9.0 2022-07-23 07:55:52 +09:00
Scala Steward
f7e58c28c6 Update oauth2-oidc-sdk to 9.39 2022-07-22 16:52:09 +09:00
Scala Steward
18f3bc4056 Update testcontainers-scala to 0.40.9 2022-07-14 22:32:58 +09:00
Scala Steward
4638f82889 Update oauth2-oidc-sdk to 9.38.1 2022-07-14 05:01:11 +09:00
Naoki Takezoe
4396b6eab5 Horizontal scroll for too wide tables in Markdown (#3087) 2022-07-13 21:24:58 +09:00
Zain Aftab
594ae2bb7e #2983 fixed css for table (#3082) 2022-07-13 08:19:21 +09:00
Scala Steward
429cf54bd4 Update sbt, sbt-dependency-tree to 1.7.1 2022-07-12 14:14:05 +09:00
Scala Steward
274357abc4 Update sbt, sbt-dependency-tree to 1.7.0 2022-07-11 07:24:10 +09:00
Scala Steward
0e21247755 Update oauth2-oidc-sdk to 9.38 2022-07-06 18:52:46 +09:00
Scala Steward
37f2868ff3 Update oauth2-oidc-sdk to 9.37.3 2022-07-04 17:00:26 +09:00
Scala Steward
6a85e25a62 Update mariadb-java-client to 3.0.6 2022-06-30 06:33:58 +09:00
Scala Steward
a91b1c7e79 Update mysql, postgresql to 1.17.3 2022-06-29 18:03:50 +09:00
Scala Steward
bfd9ecd130 Update github-api to 1.307 2022-06-27 18:33:09 +09:00
Scala Steward
ec888e94fd Update sbt-scoverage to 2.0.0 2022-06-26 06:30:23 +09:00
Scala Steward
a19c80bef2 Update jetty-continuation, jetty-http, ... to 9.4.48.v20220622 2022-06-23 07:01:52 +09:00
Scala Steward
7b9ade2698 Update jetty-continuation, jetty-http, ... to 9.4.47.v20220610 2022-06-21 06:25:34 +09:00
Scala Steward
d03f28eeb9 Update tika-core to 2.4.1 2022-06-18 07:22:37 +09:00
Scala Steward
288dd788bc Update org.eclipse.jgit.archive, ... to 5.13.1.202206130422-r 2022-06-13 20:47:46 +09:00
Scala Steward
2069a5a931 Update postgresql to 42.4.0 2022-06-10 02:10:28 +09:00
Scala Steward
8a434cbd15 Update mockito-core to 4.6.1 2022-06-03 07:10:47 +09:00
Scala Steward
faafe49848 Update oauth2-oidc-sdk to 9.37.2 2022-06-02 19:33:26 +09:00
Scala Steward
17a72292ce Update oauth2-oidc-sdk to 9.37.1 2022-06-01 19:03:35 +09:00
Scala Steward
9ae09f1801 Update testcontainers-scala to 0.40.8 2022-06-01 06:54:21 +09:00
Scala Steward
abbba17e4b Update oauth2-oidc-sdk to 9.37 2022-05-31 06:29:58 +09:00
Scala Steward
479d5da17e Update mockito-core to 4.6.0 2022-05-28 07:59:15 +09:00
Scala Steward
dec7890d04 Update oauth2-oidc-sdk to 9.36 2022-05-26 19:45:42 +09:00
Scala Steward
61910a6667 Update mariadb-java-client to 3.0.5 2022-05-26 07:06:52 +09:00
Scala Steward
ac2119ac3b Update postgresql to 42.3.6 2022-05-24 23:05:58 +09:00
Scala Steward
8c71cd7369 Update mysql, postgresql to 1.17.2 2022-05-20 08:20:48 +09:00
porkotron
ec8b6ff18f Implement missing parameters on commits api (#3061)
* Add missing leading slash html_url api path
* Use HEAD as default ref
* Implement missing parameters

Co-authored-by: Petri Pyy <petri.pyy@abako.fi>
2022-05-15 01:51:16 +09:00
Naoki Takezoe
e214804d57 Bump mariadb-java-client to 3.0.4 (#3059) 2022-05-08 21:57:25 +09:00
Scala Steward
39448f2e3c Update postgresql to 42.3.5 2022-05-05 07:37:40 +09:00
Naoki Takezoe
79dc6fc247 Support multiple assignees for Issues and Pull requests (#3055) 2022-05-03 22:51:54 +09:00
Naoki Takezoe
a800f305f2 Refactor old migrations (#3056) 2022-05-03 22:24:14 +09:00
Scala Steward
67e0fb6b3f Update tika-core to 2.4.0 2022-05-03 03:41:42 +09:00
Naoki Takezoe
e401d30159 Fix wrong overwriting of updatedDate of Issues and Pull requests (#3053) 2022-05-02 22:11:46 +09:00
Scala Steward
504e651828 Update testcontainers-scala to 0.40.7 2022-04-28 06:40:34 +09:00
Scala Steward
7a8da2f074 Update oauth2-oidc-sdk to 9.35 2022-04-27 01:22:23 +09:00
Scala Steward
1e47e96634 Update testcontainers-scala to 0.40.6 2022-04-25 07:11:30 +09:00
Scala Steward
69502480b4 Update github-api to 1.306 2022-04-22 03:39:39 +09:00
Scala Steward
5833e3fcca Update mockito-core to 4.5.1 2022-04-21 22:31:44 +09:00
Scala Steward
4e47ffb1d2 Update github-api to 1.305 2022-04-20 09:28:21 +09:00
Scala Steward
3056333a7c Update github-api to 1.304 2022-04-20 06:08:23 +09:00
Scala Steward
ad0cd7f625 Update mockito-core to 4.5.0 2022-04-20 06:08:10 +09:00
kenji yoshida
dedaa263ce Scala 3.1.2 2022-04-18 07:02:22 +09:00
Scala Steward
bccf729102 Update oauth2-oidc-sdk to 9.34 2022-04-18 06:46:15 +09:00
onukura
a244c61e56 Hide header content on signin and register page (#2589)
Co-authored-by: Naoki Takezoe <takezoe@gmail.com>
2022-04-18 01:43:03 +09:00
Naoki Takezoe
37594935f0 Custom fields for issues and pull requests (#3034) 2022-04-18 01:01:15 +09:00
Scala Steward
e521c2158b Update postgresql to 42.3.4 2022-04-16 07:06:11 +09:00
Scala Steward
b1c8a8c76e Update mysql, postgresql to 1.17.1 2022-04-14 01:44:20 +09:00
Scala Steward
8d92f210f0 Update oauth2-oidc-sdk to 9.33 2022-04-13 06:02:00 +09:00
Scala Steward
287281923c Update mysql, postgresql to 1.17.0 2022-04-12 22:58:11 +09:00
dependabot[bot]
6f18135574 Bump actions/setup-java from 2 to 3 (#3037)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 2 to 3.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v2...v3)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-12 17:22:01 +09:00
dependabot[bot]
58cf9bec3b Bump actions/upload-artifact from 2 to 3 (#3038)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2 to 3.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v2...v3)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-12 17:21:43 +09:00
Scala Steward
b7a45e9901 Update json4s-jackson to 4.0.5 2022-04-05 18:26:05 +09:00
Scala Steward
f5585b39c6 Update testcontainers-scala to 0.40.5 2022-04-04 05:32:48 +09:00
Scala Steward
9c3ef37274 Update jetty-continuation, jetty-http, ... to 9.4.46.v20220331 2022-04-02 07:27:09 +09:00
Scala Steward
e904c0b689 Update oauth2-oidc-sdk to 9.32 2022-03-30 18:30:21 +09:00
Scala Steward
237f97f069 Update testcontainers-scala to 0.40.4 2022-03-30 00:37:40 +09:00
Scala Steward
1eb2a04090 Update oauth2-oidc-sdk to 9.31.1 2022-03-29 20:18:47 +09:00
Naoki Takezoe
cda6106858 Allow to configure Jetty idle timeout in standalone mode (#3027) 2022-03-29 07:44:14 +09:00
Naoki Takezoe
8c4ce5e5f4 Hash password when reset it (#3026) 2022-03-29 07:16:12 +09:00
Naoki Takezoe
b5ee6431c4 Reset password email by users themselves (#3023) 2022-03-29 01:04:57 +09:00
Naoki Takezoe
bd06e6d4dc Drop unused ActivityComponent (#3024) 2022-03-28 22:24:34 +09:00
dependabot[bot]
3b607e74ed Bump actions/cache from 2.1.7 to 3 (#3025)
Bumps [actions/cache](https://github.com/actions/cache) from 2.1.7 to 3.
- [Release notes](https://github.com/actions/cache/releases)
- [Commits](https://github.com/actions/cache/compare/v2.1.7...v3)

---
updated-dependencies:
- dependency-name: actions/cache
  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>
2022-03-28 21:55:49 +09:00
Scala Steward
dc16474ed3 Update oauth2-oidc-sdk to 9.31 2022-03-19 19:51:24 +09:00
Scala Steward
dc0f088be0 Update oauth2-oidc-sdk to 9.30 2022-03-19 09:21:48 +09:00
Scala Steward
2718e10044 Update oauth2-oidc-sdk to 9.29 2022-03-17 06:15:25 +09:00
Scala Steward
4213dcb8a2 Update testcontainers-scala to 0.40.3 2022-03-16 11:49:15 +09:00
Scala Steward
39afb31200 Update solidbase to 1.0.4 2022-03-14 06:30:05 +09:00
dependabot[bot]
8e1ce1c96a Bump actions/checkout from 2 to 3 (#3011)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [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/v2...v3)

---
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>
2022-03-13 17:52:16 +09:00
Scala Steward
fdfd8293d6 Update oauth2-oidc-sdk to 9.28 2022-03-12 08:34:36 +09:00
Scala Steward
b7d3be6a14 Update oauth2-oidc-sdk to 9.27.1 2022-03-09 05:42:19 +09:00
Scala Steward
bb5c809749 Update mockito-core to 4.4.0 2022-03-09 05:41:08 +09:00
Scala Steward
a2cbe7cbf5 Update logback-classic to 1.2.11 2022-03-06 20:37:40 +09:00
Scala Steward
b1fb09ac99 Update github-api to 1.303 2022-03-06 08:05:46 +09:00
Scala Steward
201ae7e7db Update github-api to 1.302 2022-03-02 12:23:53 +09:00
ziggystar
2978decb84 fix: the URLs returned in tag api to point to something that exists (#2998)
Co-authored-by: Thomas Geier <thomas.geier@solidat.de>
2022-02-24 14:19:24 +09:00
Scala Steward
5fbbd4d5f3 Update testcontainers-scala to 0.40.2 2022-02-21 06:39:36 +09:00
Scala Steward
4abd363f62 Update oauth2-oidc-sdk to 9.27 2022-02-18 06:19:36 +09:00
Scala Steward
8288db60a9 Update postgresql to 42.3.3 2022-02-16 05:08:44 +09:00
Scala Steward
6cdf679571 Update sbt-assembly to 1.2.0 2022-02-13 18:11:57 +09:00
Scala Steward
51179acc12 Update sbt-assembly to 1.1.1 2022-02-13 17:35:25 +09:00
Scala Steward
1ff27d3e17 Update thumbnailator to 0.4.17 2022-02-12 06:52:22 +09:00
Scala Steward
1e9bce3d5a Update oauth2-oidc-sdk to 9.25 2022-02-10 18:47:18 +09:00
Scala Steward
8811ea2b58 Update jetty-continuation, jetty-http, ... to 9.4.45.v20220203 2022-02-08 03:05:22 +09:00
Scala Steward
be7cdd0a28 Update tika-core to 2.3.0 2022-02-08 03:03:55 +09:00
Scala Steward
611a05aa48 Update testcontainers-scala to 0.40.1 2022-02-06 07:42:19 +09:00
Naoki Takezoe
9f95a757a2 Update CHANGELOG of 4.37.2 2022-02-05 08:47:17 +09:00
Scala Steward
58b577453c Update oauth2-oidc-sdk to 9.24 2022-02-05 06:57:49 +09:00
Naoki Takezoe
78acfd87e8 Update CHANGELOG.md 2022-02-03 11:41:40 +09:00
Scala Steward
bf9cb26164 Update typesafe:config to 1.4.2 2022-02-03 07:29:36 +09:00
Scala Steward
68a99ada0b Update postgresql to 42.3.2 2022-02-02 01:37:26 +09:00
Scala Steward
1d841ce171 Update sbt, sbt-dependency-tree to 1.6.2 2022-02-01 15:00:58 +09:00
Naoki Takezoe
ccfdbfd276 Mention that GitBucket doesn't support JakartaEE in README (#2986) 2022-01-31 23:51:44 +09:00
Naoki Takezoe
70d584d56d Report commit failure caused by hook (#2984) 2022-01-30 21:38:15 +09:00
kenji yoshida
a49ffb07be update Scala 3 version 2022-01-30 16:14:57 +09:00
Scala Steward
baa9caf010 Update testcontainers-scala to 0.40.0 2022-01-28 14:11:19 +09:00
Scala Steward
22025c875f Update oauth2-oidc-sdk to 9.22.2 2022-01-26 06:46:20 +09:00
Scala Steward
3806995a50 Update mockito-core to 4.3.1 2022-01-26 06:44:32 +09:00
Scala Steward
94d2650491 Update mockito-core to 4.3.0 2022-01-24 22:05:03 +09:00
Scala Steward
844f24afa4 Update json4s-jackson to 4.0.4 2022-01-23 12:27:59 +09:00
kenji yoshida
083396a8e4 update maven badge 2022-01-22 16:25:53 +09:00
kenji yoshida
e02769056b remove Resolver.jcenterRepo
- 00eab5d584
- debbc21bf3
- https://repo1.maven.org/maven2/io/github/java-diff-utils/java-diff-utils/
2022-01-22 15:49:21 +09:00
Scala Steward
024f8e8d80 Update oauth2-oidc-sdk to 9.22.1 2022-01-21 22:51:38 +09:00
Scala Steward
3b4a7c3c47 Update mariadb-java-client to 2.7.5 2022-01-20 08:22:53 +09:00
Scala Steward
d9838d9cba Update mysql, postgresql to 1.16.3 2022-01-19 23:59:28 +09:00
Naoki Takezoe
b3ca4468cd Release 4.37.2 (#2970) 2022-01-16 11:21:58 +09:00
Naoki Takezoe
452e41603a Fix activities template (#2969) 2022-01-16 03:38:16 +09:00
Scala Steward
b41de4a770 Update oauth2-oidc-sdk to 9.22 2022-01-13 07:03:53 +09:00
Scala Steward
3423d2f2f3 Update sbt-scoverage to 1.9.3 2022-01-11 22:58:10 +09:00
Scala Steward
9269acc64d Update scala-library to 2.13.8 2022-01-11 08:57:06 +09:00
Scala Steward
18b5f20f1c Update oauth2-oidc-sdk to 9.21 2022-01-11 07:14:42 +09:00
kenji yoshida
68d592a8ff pin HikariCP 2022-01-10 14:01:22 +09:00
Scala Steward
ee18d96f98 Update oauth2-oidc-sdk to 9.20.1 2022-01-04 17:48:12 +09:00
Scala Steward
6d3687cbdf Update thumbnailator to 0.4.16 2022-01-02 22:38:01 +09:00
Scala Steward
1ef1a2983b Update sbt, sbt-dependency-tree to 1.6.1 2021-12-29 16:30:22 +09:00
Scala Steward
47850fe0fa Update sbt, sbt-dependency-tree to 1.6.0 2021-12-27 07:50:11 +09:00
Scala Steward
055a37cac0 Update sbt-scalafmt to 2.4.6 2021-12-25 13:16:01 +09:00
Scala Steward
333deb4756 Update tika-core to 2.2.1 2021-12-24 07:10:06 +09:00
Scala Steward
34cde4b09a Update logback-classic to 1.2.10 2021-12-23 19:52:39 +09:00
Scala Steward
5091b4838c Update sbt, sbt-dependency-tree to 1.5.8 2021-12-21 08:49:23 +09:00
Scala Steward
2e83289061 Update tika-core to 2.2.0 2021-12-18 07:51:40 +09:00
Scala Steward
e11f651002 Update logback-classic to 1.2.9 2021-12-17 12:02:30 +09:00
Scala Steward
be684a567d Update mockito-core to 4.2.0 2021-12-16 22:51:33 +09:00
Scala Steward
97e552b10f Update sbt, sbt-dependency-tree to 1.5.7 2021-12-15 16:47:00 +09:00
Scala Steward
5bb8046c32 Update logback-classic to 1.2.8 2021-12-15 07:03:01 +09:00
Naoki Takezoe
e7192655f7 Release 4.37.1 (#2949) 2021-12-14 01:05:27 +09:00
Naoki Takezoe
19ba09740c Update gist plugin and notification plugin for GitBucket 4.37.x (#2948) 2021-12-14 00:55:35 +09:00
Naoki Takezoe
d169777722 Fix SSHCommand extension point for apache-sshd 2.x (#2941) 2021-12-11 19:11:23 +09:00
Naoki Takezoe
ff8a5f6b77 Release 4.37.0 (#2940) 2021-12-11 14:28:23 +09:00
Scala Steward
ec953df156 Update sbt, sbt-dependency-tree to 1.5.6 2021-12-10 22:27:40 +09:00
Naoki Takezoe
d6a191d95b Enhance Git Reference APIs (#2937) 2021-12-06 17:16:33 +09:00
Naoki Takezoe
aba428bba1 Fix refs API as far as Jenkins github-branch-source plugin can detect tags (#2936)
Co-authored-by: Thomas Geier <thomas.geier@solidat.de>
2021-12-06 01:06:59 +09:00
Scala Steward
6ab37fd596 Update thumbnailator to 0.4.15 2021-12-05 16:11:17 +09:00
Scala Steward
73fc70f55b Update apache-sshd to 2.8.0 2021-12-04 08:27:01 +09:00
Scala Steward
aad18b7a50 Update sbt-scalafmt to 2.4.5 2021-12-04 08:26:37 +09:00
dependabot[bot]
cc278be5cd Bump actions/cache from 2.1.6 to 2.1.7
Bumps [actions/cache](https://github.com/actions/cache) from 2.1.6 to 2.1.7.
- [Release notes](https://github.com/actions/cache/releases)
- [Commits](https://github.com/actions/cache/compare/v2.1.6...v2.1.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-30 09:24:19 +09:00
kenji yoshida
d0f4f82a0f pin jgit 5.x 2021-11-30 09:23:36 +09:00
Naoki Takezoe
1dcbf386b1 Fix SSH server handling (#2930) 2021-11-28 17:39:31 +09:00
Naoki Takezoe
414afd285c Remove unused imports 2021-11-28 15:14:51 +09:00
Naoki Takezoe
35b645d8b5 Merge branch 'keywordsalad-custom-ssh-url' 2021-11-28 15:08:04 +09:00
Naoki Takezoe
b3cba53866 Reformat GitCommandSpec 2021-11-28 15:02:29 +09:00
Naoki Takezoe
a4773bb3ca Merge branch 'master' into custom-ssh-url 2021-11-28 14:56:55 +09:00
Naoki Takezoe
863d8a4af5 Bump apache-sshd to 2.7.0 (#2929) 2021-11-28 14:47:58 +09:00
Scala Steward
3fccd7b53c Update oauth2-oidc-sdk to 9.20 2021-11-25 20:46:24 +09:00
Scala Steward
dd2760eaf7 Update github-api to 1.301 2021-11-24 12:10:25 +09:00
Scala Steward
824bafa739 Update github-api to 1.300 2021-11-22 11:15:11 +09:00
kaz-on
60cdaec05f Fix line highlighting in dark themes (#2921) 2021-11-22 01:31:52 +09:00
kaz-on
c204a435b3 Remove unnecessary loading of google-code-prettify (#2922) 2021-11-22 01:31:16 +09:00
Scala Steward
37accd92d6 Update mockito-core to 4.1.0 2021-11-20 07:35:42 +09:00
Scala Steward
01fd0ee1f0 Update sbt-scalafmt to 2.4.4 2021-11-19 07:19:22 +09:00
Scala Steward
fab1c74473 Update testcontainers-scala to 0.39.12 2021-11-15 06:14:54 +09:00
Scala Steward
0d8fcfd28d Update logback-classic to 1.2.7 2021-11-12 03:45:11 +09:00
Naoki Takezoe
b91a7c32a6 Relax max length limitation for WebHook URLs (#2915) 2021-11-11 01:39:12 +09:00
Logan McGrath
e7a6f0930b Closes #2818 - Supporting custom SSH URL's when hosting behind a proxy 2021-08-29 16:38:06 -07:00
179 changed files with 4396 additions and 1794 deletions

View File

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

View File

@@ -8,11 +8,11 @@ jobs:
timeout-minutes: 30
strategy:
matrix:
java: [8, 11, 17]
java: [11, 21]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Cache
uses: actions/cache@v2.1.6
uses: actions/cache@v3
env:
cache-name: cache-sbt-libs
with:
@@ -22,18 +22,18 @@ jobs:
~/.cache/coursier/v1
key: build-${{ env.cache-name }}-${{ hashFiles('build.sbt') }}
- name: Set up JDK
uses: actions/setup-java@v2
uses: actions/setup-java@v3
with:
java-version: ${{ matrix.java }}
distribution: adopt
- name: Run tests
run: sbt scalafmtSbtCheck scalafmtCheck test:scalafmtCheck test
run: sbt scalafmtSbtCheck scalafmtCheckAll test
- name: Scala 3
run: sbt '++ 3.0.1!' update # TODO
run: sbt '++ 3.x' update # TODO
- name: Build executable
run: sbt executable
- name: Upload artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: gitbucket-java${{ matrix.java }}-${{ github.sha }}
path: ./target/executable/gitbucket.*

3
.gitignore vendored
View File

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

View File

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

View File

@@ -1,32 +1,87 @@
# Changelog
All changes to the project will be documented in this file.
### 4.36.2 - 16 Aug 2021
## 4.40.0 - 22 Oct 2023
- Drop Java 8 support
- Improve git push performance
- Show activities of all visible repositories as news feed
- Support custom fields of issues and pull requests in search condition
- Configurable default branch name
## 4.39.0 - 29 Apr 2023
- 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
- Support multiple assignees for Issues and Pull requests
- Custom fields for issues and pull requests
- Reset password by users
- Allow to configure Jetty idle timeout in standalone mode
- Horizontal scroll for too wide tables in Markdown
- Hide header content on signin and register page
- Fix the default charset of the online editor in the repository viewer
- Fix the milestone count
- Some improvements and bugfixes for WebAPI and WebHook
## 4.37.2 - 16 Jan 2022
- Fixed a security issue reported by [Positive Technologies](https://www.ptsecurity.com/ww-en/). Great thanks for their detailed report and close support!
## 4.37.1 - 14 Dec 2021
- Update gist-plugin and notification-plugin
- Fix SSHCommand extension point for apache-sshd 2.x
## 4.37.0 - 11 Dec 2021
- Enhance Git Reference APIs
- Add milestone data to issue list API
- Support "all" in issue list API
- Support EDDSA in signed commit verification
- Support custom SSH url
- Relax max passward length limitation
- Relax max webhook url length limitation
## 4.36.2 - 16 Aug 2021
- Escape user name in avatar image tag
### 4.36.1 - 22 Jul 2021
## 4.36.1 - 22 Jul 2021
- Bump gitbucket-gist-plugin to 4.21.0
### 4.36.0 - 17 Jul 2021
## 4.36.0 - 17 Jul 2021
- Tag selector in the repository viewer
- Link issues/pull requests of other repositories
- Files and lines can be linked in the diff view
- Option to disable XSS protection
### 4.35.3 - 14 Jan 2021
## 4.35.3 - 14 Jan 2021
- Fix a bug that Wiki page cannot be deleted
- Fix a deployment issue on Tomcat
### 4.35.2 - 30 Dec 2020
## 4.35.2 - 30 Dec 2020
- Upgrade gitbucket-notifications-plugin to 1.10.0
- Upgrade oauth2-oidc-sdk to 8.29.1 to solve dependency issue
### 4.35.1 - 29 Dec 2020
## 4.35.1 - 29 Dec 2020
- Fix database migration issue which happens if webhook is configured
- Call push webhook when pull request is merged
- Show commit status at commits tab of pull request
### 4.35.0 - 25 Dec 2020
## 4.35.0 - 25 Dec 2020
- Editor and source viewer color theme
- Auto completion for issues and pull requests
- Upload image from clipboard

View File

@@ -1,4 +1,4 @@
GitBucket [![Gitter chat](https://badges.gitter.im/gitbucket/gitbucket.svg)](https://gitter.im/gitbucket/gitbucket) [![build](https://github.com/gitbucket/gitbucket/workflows/build/badge.svg?branch=master)](https://github.com/gitbucket/gitbucket/actions?query=workflow%3Abuild+branch%3Amaster) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.gitbucket/gitbucket_2.13/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.gitbucket/gitbucket_2.13) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/gitbucket/gitbucket/blob/master/LICENSE)
GitBucket [![Gitter chat](https://badges.gitter.im/gitbucket/gitbucket.svg)](https://gitter.im/gitbucket/gitbucket) [![build](https://github.com/gitbucket/gitbucket/workflows/build/badge.svg?branch=master)](https://github.com/gitbucket/gitbucket/actions?query=workflow%3Abuild+branch%3Amaster) [![gitbucket Scala version support](https://index.scala-lang.org/gitbucket/gitbucket/gitbucket/latest-by-scala-version.svg)](https://index.scala-lang.org/gitbucket/gitbucket/gitbucket) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/gitbucket/gitbucket/blob/master/LICENSE)
=========
GitBucket is a Git web platform powered by Scala offering:
@@ -10,8 +10,6 @@ GitBucket is a Git web platform powered by Scala offering:
![GitBucket](https://gitbucket.github.io/img/screenshots/screenshot-repository_viewer.png)
You can try an [online demo](https://gitbucket.herokuapp.com/) *(ID: root / Pass: root)* of GitBucket, and also get the latest information at [GitBucket News](https://gitbucket.github.io/gitbucket-news/).
Features
--------
The current version of GitBucket provides many features such as:
@@ -31,7 +29,7 @@ GitBucket requires **Java8**. You have to install it, if it is not already insta
1. Download the latest **gitbucket.war** from [the releases page](https://github.com/gitbucket/gitbucket/releases) and run it by `java -jar gitbucket.war`.
2. Go to `http://[hostname]:8080/` and log in with ID: **root** / Pass: **root**.
You can also deploy `gitbucket.war` to a servlet container which supports Servlet 3.0 (like Jetty, Tomcat, JBoss, etc)
You can also deploy `gitbucket.war` to a servlet container which supports Servlet 3.0 (like Jetty, Tomcat, JBoss, etc). Note that GitBucket doesn't support Jakarta EE yet.
For more information about installation on Mac or Windows Server (with IIS), or configuration of Apache or Nginx and also integration with other tools or services such as Jenkins or Slack, see [Wiki](https://github.com/gitbucket/gitbucket/wiki).
@@ -61,18 +59,13 @@ 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.
- 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.36.x
What's New in 4.40.x
-------------
### 4.36.2 - 16 Aug 2021
- Escape user name in avatar image tag
### 4.36.1 - 22 Jul 2021
- Bump gitbucket-gist-plugin to 4.21.0
### 4.36.0 - 17 Jul 2021
- Tag selector in the repository viewer
- Link issues/pull requests of other repositories
- Files and lines can be linked in the diff view
- Option to disable XSS protection
## 4.40.0 - 22 Oct 2023
- Drop Java 8 support
- Improve git push performance
- Show activities of all visible repositories as news feed
- Support custom fields of issues and pull requests in search condition
- Configurable default branch name
See the [change log](CHANGELOG.md) for all of the updates.

115
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._
val Organization = "io.github.gitbucket"
val Name = "gitbucket"
val GitBucketVersion = "4.36.2"
val ScalatraVersion = "2.8.2"
val JettyVersion = "9.4.44.v20210927"
val JgitVersion = "5.13.0.202109080827-r"
val GitBucketVersion = "4.40.0"
val ScalatraVersion = "3.0.0"
val JettyVersion = "10.0.17"
val JgitVersion = "6.7.0.202309050840-r"
lazy val root = (project in file("."))
.enablePlugins(SbtTwirl, ScalatraPlugin)
@@ -15,72 +15,64 @@ sourcesInBase := false
organization := Organization
name := Name
version := GitBucketVersion
scalaVersion := "2.13.7"
scalaVersion := "2.13.12"
scalafmtOnCompile := true
crossScalaVersions += "3.3.1"
// scalafmtOnCompile := true
coverageExcludedPackages := ".*\\.html\\..*"
// dependency settings
resolvers ++= Seq(
Classpaths.typesafeReleases,
Resolver.jcenterRepo,
"sonatype-snapshot" at "https://oss.sonatype.org/content/repositories/snapshots/"
)
libraryDependencies ++= Seq(
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % JgitVersion,
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % JgitVersion,
"org.scalatra" %% "scalatra" % ScalatraVersion cross CrossVersion.for3Use2_13,
"org.scalatra" %% "scalatra-json" % ScalatraVersion cross CrossVersion.for3Use2_13,
"org.scalatra" %% "scalatra-forms" % ScalatraVersion cross CrossVersion.for3Use2_13,
"org.json4s" %% "json4s-jackson" % "4.0.3" cross CrossVersion.for3Use2_13,
"commons-io" % "commons-io" % "2.11.0",
"io.github.gitbucket" % "solidbase" % "1.0.3",
"io.github.gitbucket" % "markedj" % "1.0.16",
"org.apache.commons" % "commons-compress" % "1.21",
"org.scalatra" %% "scalatra-javax" % ScalatraVersion,
"org.scalatra" %% "scalatra-json-javax" % ScalatraVersion,
"org.scalatra" %% "scalatra-forms-javax" % ScalatraVersion,
"org.json4s" %% "json4s-jackson" % "4.0.6",
"commons-io" % "commons-io" % "2.14.0",
"io.github.gitbucket" % "solidbase" % "1.0.5",
"io.github.gitbucket" % "markedj" % "1.0.18",
"org.apache.commons" % "commons-compress" % "1.24.0",
"org.apache.commons" % "commons-email" % "1.5",
"commons-net" % "commons-net" % "3.8.0",
"org.apache.httpcomponents" % "httpclient" % "4.5.13",
"org.apache.sshd" % "apache-sshd" % "2.1.0" exclude ("org.slf4j", "slf4j-jdk14") exclude ("org.apache.sshd", "sshd-mina") exclude ("org.apache.sshd", "sshd-netty"),
"org.apache.tika" % "tika-core" % "2.1.0",
"com.github.takezoe" %% "blocking-slick-32" % "0.0.12" cross CrossVersion.for3Use2_13,
"commons-net" % "commons-net" % "3.10.0",
"org.apache.httpcomponents" % "httpclient" % "4.5.14",
"org.apache.sshd" % "apache-sshd" % "2.11.0" exclude ("org.slf4j", "slf4j-jdk14") exclude ("org.apache.sshd", "sshd-mina") exclude ("org.apache.sshd", "sshd-netty"),
"org.apache.tika" % "tika-core" % "2.9.1",
"com.github.takezoe" %% "blocking-slick" % "0.0.14",
"com.novell.ldap" % "jldap" % "2009-10-07",
"com.h2database" % "h2" % "1.4.199",
"org.mariadb.jdbc" % "mariadb-java-client" % "2.7.4",
"org.postgresql" % "postgresql" % "42.3.1",
"ch.qos.logback" % "logback-classic" % "1.2.6",
"org.mariadb.jdbc" % "mariadb-java-client" % "2.7.6",
"org.postgresql" % "postgresql" % "42.6.0",
"ch.qos.logback" % "logback-classic" % "1.4.11",
"com.zaxxer" % "HikariCP" % "4.0.3" exclude ("org.slf4j", "slf4j-api"),
"com.typesafe" % "config" % "1.4.1",
"com.typesafe" % "config" % "1.4.3",
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.1.0",
"io.github.java-diff-utils" % "java-diff-utils" % "4.11",
"io.github.java-diff-utils" % "java-diff-utils" % "4.12",
"org.cache2k" % "cache2k-all" % "1.6.0.Final",
"net.coobird" % "thumbnailator" % "0.4.14",
"net.coobird" % "thumbnailator" % "0.4.20",
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
"com.nimbusds" % "oauth2-oidc-sdk" % "9.19",
"com.nimbusds" % "oauth2-oidc-sdk" % "11.4",
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
"junit" % "junit" % "4.13.2" % "test",
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test" cross CrossVersion.for3Use2_13,
"org.mockito" % "mockito-core" % "4.0.0" % "test",
"com.dimafeng" %% "testcontainers-scala" % "0.39.11" % "test",
"org.testcontainers" % "mysql" % "1.16.2" % "test",
"org.testcontainers" % "postgresql" % "1.16.2" % "test",
"org.scalatra" %% "scalatra-scalatest-javax" % ScalatraVersion % "test",
"org.mockito" % "mockito-core" % "5.6.0" % "test",
"com.dimafeng" %% "testcontainers-scala" % "0.41.0" % "test",
"org.testcontainers" % "mysql" % "1.19.1" % "test",
"org.testcontainers" % "postgresql" % "1.19.1" % "test",
"net.i2p.crypto" % "eddsa" % "0.3.0",
"is.tagomor.woothee" % "woothee-java" % "1.11.0",
"org.ec4j.core" % "ec4j-core" % "0.3.0",
"org.kohsuke" % "github-api" % "1.135" % "test"
"org.kohsuke" % "github-api" % "1.317" % "test"
)
libraryDependencies ~= {
_.map {
case x if x.name == "twirl-api" =>
x cross CrossVersion.for3Use2_13
case x =>
x
}
}
// Compiler settings
scalacOptions := Seq(
"-deprecation",
@@ -90,7 +82,15 @@ scalacOptions := Seq(
"-Wunused:imports",
"-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"
// Test settings
@@ -124,15 +124,14 @@ signedArtifacts := {
val ExecutableConfig = config("executable").hide
Keys.ivyConfigurations += ExecutableConfig
libraryDependencies ++= Seq(
"org.eclipse.jetty" % "jetty-security" % 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-xml" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-http" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-servlet" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-io" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-util" % JettyVersion % "executable"
"org.eclipse.jetty" % "jetty-security" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-server" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-xml" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-http" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-servlet" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-io" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-util" % JettyVersion % "executable"
)
// Run package task before test to generate target/webapp for integration test
@@ -160,8 +159,7 @@ executableKey := {
val jettyJars = Keys.update.value select configurationFilter(name = ExecutableConfig.name)
jettyJars foreach { jar =>
IO unzip (jar, temp, (name: String) =>
(name startsWith "javax/") ||
(name startsWith "org/"))
(name startsWith "javax/") || (name startsWith "org/") || (name startsWith "META-INF/services/"))
}
// include original war file
@@ -186,7 +184,7 @@ executableKey := {
val url = "https://github.com/" +
s"gitbucket/gitbucket-${pluginId}-plugin/releases/download/${pluginVersion}/gitbucket-${pluginId}-plugin-${pluginVersion}.jar"
log info s"Download: ${url}"
IO transfer (new java.net.URL(url).openStream, pluginsDir / url.substring(url.lastIndexOf("/") + 1))
IO transfer (new java.net.URI(url).toURL.openStream, pluginsDir / url.substring(url.lastIndexOf("/") + 1))
case _ => ()
}
}
@@ -287,6 +285,9 @@ Test / testOptions ++= {
}
Jetty / javaOptions ++= Seq(
"-Dlogback.configurationFile=/logback-dev.xml",
"-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
========
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
$ 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`.
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
--------
@@ -37,7 +51,8 @@ To build an executable war file, run
$ 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
---------

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:
```
* /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
* /USER_NAME
* /REPO_NAME.git (substance of repository. GitServlet sees this directory)
* /REPO_NAME.wiki.git (wiki repository)
* /REPO_NAME
* /issues (files which are attached to issue)
* /issues (files attached to issue)
* /lfs (LFS managed files)
* /data
* /USER_NAME
@@ -20,6 +24,8 @@ This directory has following structure:
* /plugins
* plugin.jar
* /.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
* /_upload
* /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
========
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:

View File

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

View File

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

View File

@@ -61,12 +61,19 @@ public class JettyLauncher {
String redirectHttps = getEnvironmentVariable("gitbucket.redirectHttps");
String contextPath = getEnvironmentVariable("gitbucket.prefix");
String tmpDirPath = getEnvironmentVariable("gitbucket.tempDir");
String jettyIdleTimeout = getEnvironmentVariable("gitbucket.jettyIdleTimeout");
boolean saveSessions = false;
for(String arg: args) {
if(arg.equals("--save_sessions")) {
if (arg.equals("--save_sessions")) {
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("=")) {
String[] dim = arg.split("=", 2);
if(dim.length == 2) {
@@ -107,6 +114,9 @@ public class JettyLauncher {
case "--plugin_dir":
System.setProperty("gitbucket.pluginDir", dim[1]);
break;
case "--jetty_idle_timeout":
jettyIdleTimeout = dim[1];
break;
}
}
}
@@ -130,6 +140,11 @@ public class JettyLauncher {
if (connectorsSet.contains(Connectors.HTTPS)) {
httpConfig.setSecurePort(fallback(securePort, Defaults.HTTPS_PORT, Integer::parseInt));
}
if (jettyIdleTimeout != null && jettyIdleTimeout.trim().length() != 0) {
httpConfig.setIdleTimeout(Long.parseLong(jettyIdleTimeout.trim()));
} else {
httpConfig.setIdleTimeout(300000L); // default is 5min
}
if (connectorsSet.contains(Connectors.HTTP)) {
final ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
@@ -140,7 +155,7 @@ public class JettyLauncher {
}
if (connectorsSet.contains(Connectors.HTTPS)) {
final SslContextFactory sslContextFactory = new SslContextFactory.Server();
final SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath(requireNonNull(keyStorePath,
"You must specify a path to an SSL keystore via the --key_store_path command line argument" +

View File

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

View File

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

@@ -1,18 +0,0 @@
CREATE OR REPLACE VIEW ISSUE_OUTLINE_VIEW AS
SELECT
A.USER_NAME,
A.REPOSITORY_NAME,
A.ISSUE_ID,
COALESCE(B.COMMENT_COUNT, 0) + COALESCE(C.COMMENT_COUNT, 0) AS COMMENT_COUNT
FROM ISSUE A
LEFT OUTER JOIN (
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM ISSUE_COMMENT
WHERE ACTION IN ('comment', 'close_comment', 'reopen_comment')
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
) B
ON (A.USER_NAME = B.USER_NAME AND A.REPOSITORY_NAME = B.REPOSITORY_NAME AND A.ISSUE_ID = B.ISSUE_ID)
LEFT OUTER JOIN (
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM COMMIT_COMMENT
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
) C
ON (A.USER_NAME = C.USER_NAME AND A.REPOSITORY_NAME = C.REPOSITORY_NAME AND A.ISSUE_ID = C.ISSUE_ID);

View File

@@ -343,4 +343,28 @@
<addPrimaryKey constraintName="IDX_PROTECTED_BRANCH_REQUIRE_CONTEXT_PK" tableName="PROTECTED_BRANCH_REQUIRE_CONTEXT" columnNames="USER_NAME, REPOSITORY_NAME, BRANCH, CONTEXT"/>
<addForeignKeyConstraint constraintName="IDX_PROTECTED_BRANCH_REQUIRE_CONTEXT_FK0" baseTableName="PROTECTED_BRANCH_REQUIRE_CONTEXT" baseColumnNames="USER_NAME, REPOSITORY_NAME, BRANCH" referencedTableName="PROTECTED_BRANCH" referencedColumnNames="USER_NAME, REPOSITORY_NAME, BRANCH" onDelete="CASCADE" onUpdate="CASCADE"/>
<!--================================================================================================-->
<!-- ISSUE_OUTLINE_VIEW -->
<!--================================================================================================-->
<sql>
CREATE OR REPLACE VIEW ISSUE_OUTLINE_VIEW AS
SELECT
A.USER_NAME,
A.REPOSITORY_NAME,
A.ISSUE_ID,
COALESCE(B.COMMENT_COUNT, 0) + COALESCE(C.COMMENT_COUNT, 0) AS COMMENT_COUNT
FROM ISSUE A
LEFT OUTER JOIN (
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM ISSUE_COMMENT
WHERE ACTION IN ('comment', 'close_comment', 'reopen_comment')
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
) B
ON (A.USER_NAME = B.USER_NAME AND A.REPOSITORY_NAME = B.REPOSITORY_NAME AND A.ISSUE_ID = B.ISSUE_ID)
LEFT OUTER JOIN (
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM COMMIT_COMMENT
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
) C
ON (A.USER_NAME = C.USER_NAME AND A.REPOSITORY_NAME = C.REPOSITORY_NAME AND A.ISSUE_ID = C.ISSUE_ID)
</sql>
</changeSet>

View File

@@ -1,26 +0,0 @@
CREATE OR REPLACE VIEW ISSUE_OUTLINE_VIEW AS
SELECT
A.USER_NAME,
A.REPOSITORY_NAME,
A.ISSUE_ID,
COALESCE(B.COMMENT_COUNT, 0) + COALESCE(C.COMMENT_COUNT, 0) AS COMMENT_COUNT,
COALESCE(D.ORDERING, 9999) AS PRIORITY
FROM ISSUE A
LEFT OUTER JOIN (
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM ISSUE_COMMENT
WHERE ACTION IN ('comment', 'close_comment', 'reopen_comment')
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
) B
ON (A.USER_NAME = B.USER_NAME AND A.REPOSITORY_NAME = B.REPOSITORY_NAME AND A.ISSUE_ID = B.ISSUE_ID)
LEFT OUTER JOIN (
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM COMMIT_COMMENT
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
) C
ON (A.USER_NAME = C.USER_NAME AND A.REPOSITORY_NAME = C.REPOSITORY_NAME AND A.ISSUE_ID = C.ISSUE_ID)
LEFT OUTER JOIN PRIORITY D
ON (A.PRIORITY_ID = D.PRIORITY_ID);

View File

@@ -35,4 +35,28 @@
<column name="URL" type="varchar(200)" nullable="false"/>
<column name="EVENT" type="varchar(30)" nullable="false"/>
</createTable>
<sql>
CREATE OR REPLACE VIEW ISSUE_OUTLINE_VIEW AS
SELECT
A.USER_NAME,
A.REPOSITORY_NAME,
A.ISSUE_ID,
COALESCE(B.COMMENT_COUNT, 0) + COALESCE(C.COMMENT_COUNT, 0) AS COMMENT_COUNT,
COALESCE(D.ORDERING, 9999) AS PRIORITY
FROM ISSUE A
LEFT OUTER JOIN (
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM ISSUE_COMMENT
WHERE ACTION IN ('comment', 'close_comment', 'reopen_comment')
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
) B
ON (A.USER_NAME = B.USER_NAME AND A.REPOSITORY_NAME = B.REPOSITORY_NAME AND A.ISSUE_ID = B.ISSUE_ID)
LEFT OUTER JOIN (
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM COMMIT_COMMENT
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
) C
ON (A.USER_NAME = C.USER_NAME AND A.REPOSITORY_NAME = C.REPOSITORY_NAME AND A.ISSUE_ID = C.ISSUE_ID)
LEFT OUTER JOIN PRIORITY D
ON (A.PRIORITY_ID = D.PRIORITY_ID);
</sql>
</changeSet>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<changeSet>
<dropForeignKeyConstraint constraintName="IDX_WEB_HOOK_EVENT_FK0" baseTableName="WEB_HOOK_EVENT"/>
<modifyDataType columnName="URL" newDataType="varchar(400)" tableName="WEB_HOOK_EVENT"/>
<modifyDataType columnName="URL" newDataType="varchar(400)" tableName="WEB_HOOK"/>
<addForeignKeyConstraint constraintName="IDX_WEB_HOOK_EVENT_FK0" baseTableName="WEB_HOOK_EVENT" baseColumnNames="USER_NAME, REPOSITORY_NAME, URL" referencedTableName="WEB_HOOK" referencedColumnNames="USER_NAME, REPOSITORY_NAME, URL" onDelete="CASCADE" onUpdate="CASCADE"/>
</changeSet>

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<changeSet>
<!--================================================================================================-->
<!-- CUSTOM_FIELD -->
<!--================================================================================================-->
<createTable tableName="CUSTOM_FIELD">
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
<column name="FIELD_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
<column name="FIELD_NAME" type="varchar(100)" nullable="false"/>
<column name="FIELD_TYPE" type="varchar(100)" nullable="false"/>
<column name="ENABLE_FOR_ISSUES" type="boolean" nullable="false"/>
<column name="ENABLE_FOR_PULL_REQUESTS" type="boolean" nullable="false"/>
</createTable>
<addPrimaryKey constraintName="IDX_CUSTOM_FIELD_PK" tableName="CUSTOM_FIELD" columnNames="USER_NAME, REPOSITORY_NAME, FIELD_ID"/>
<addForeignKeyConstraint constraintName="IDX_CUSTOM_FIELD_FK0" baseTableName="CUSTOM_FIELD" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
<!--================================================================================================-->
<!-- ISSUE_CUSTOM_FIELD -->
<!--================================================================================================-->
<createTable tableName="ISSUE_CUSTOM_FIELD">
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
<column name="ISSUE_ID" type="int" nullable="false"/>
<column name="FIELD_ID" type="int" nullable="false"/>
<column name="VALUE" type="varchar(200)" nullable="true"/>
</createTable>
<addPrimaryKey constraintName="IDX_ISSUE_CUSTOM_FIELD_PK" tableName="ISSUE_CUSTOM_FIELD" columnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID, FIELD_ID"/>
<addForeignKeyConstraint constraintName="IDX_ISSUE_CUSTOM_FIELD_FK0" baseTableName="ISSUE_CUSTOM_FIELD" baseColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID" referencedTableName="ISSUE" referencedColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID"/>
<addForeignKeyConstraint constraintName="IDX_ISSUE_CUSTOM_FIELD_FK1" baseTableName="ISSUE_CUSTOM_FIELD" baseColumnNames="USER_NAME, REPOSITORY_NAME, FIELD_ID" referencedTableName="CUSTOM_FIELD" referencedColumnNames="USER_NAME, REPOSITORY_NAME, FIELD_ID"/>
<!--================================================================================================-->
<!-- ISSUE_ASSIGNEE -->
<!--================================================================================================-->
<createTable tableName="ISSUE_ASSIGNEE">
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
<column name="ISSUE_ID" type="int" nullable="false"/>
<column name="ASSIGNEE_USER_NAME" type="varchar(100)" nullable="false"/>
</createTable>
<addPrimaryKey constraintName="IDX_ISSUE_ASSIGNEE_PK" tableName="ISSUE_ASSIGNEE" columnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID, ASSIGNEE_USER_NAME"/>
<addForeignKeyConstraint constraintName="IDX_ISSUE_ASSIGNEE_FK0" baseTableName="ISSUE_ASSIGNEE" baseColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID" referencedTableName="ISSUE" referencedColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID"/>
<!--
<addForeignKeyConstraint constraintName="IDX_ISSUE_ASSIGNEE_FK1" baseTableName="ISSUE_ASSIGNEE" baseColumnNames="ASSIGNEE_USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
-->
<sql>
INSERT INTO ISSUE_ASSIGNEE (USER_NAME, REPOSITORY_NAME, ISSUE_ID, ASSIGNEE_USER_NAME)
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, ASSIGNED_USER_NAME FROM ISSUE WHERE ASSIGNED_USER_NAME IS NOT NULL
</sql>
<dropColumn tableName="ISSUE" columnName="ASSIGNED_USER_NAME"/>
</changeSet>

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

@@ -1,2 +0,0 @@
-- DELETE COLLABORATORS IN GROUP REPOSITORIES
DELETE FROM COLLABORATOR WHERE USER_NAME IN (SELECT USER_NAME FROM ACCOUNT WHERE GROUP_ACCOUNT = TRUE)

View File

@@ -30,4 +30,9 @@
<dropColumn tableName="REPOSITORY" columnName="ENABLE_WIKI"/>
<dropColumn tableName="REPOSITORY" columnName="ALLOW_WIKI_EDITING"/>
<dropColumn tableName="REPOSITORY" columnName="ENABLE_ISSUES"/>
<!-- DELETE COLLABORATORS IN GROUP REPOSITORIES -->
<sql>
DELETE FROM COLLABORATOR WHERE USER_NAME IN (SELECT USER_NAME FROM ACCOUNT WHERE GROUP_ACCOUNT = TRUE)
</sql>
</changeSet>

View File

@@ -9,7 +9,7 @@ import gitbucket.core.model.Activity
import gitbucket.core.util.Directory.ActivityLog
import gitbucket.core.util.JDBCUtil
import io.github.gitbucket.solidbase.Solidbase
import io.github.gitbucket.solidbase.migration.{LiquibaseMigration, Migration, SqlMigration}
import io.github.gitbucket.solidbase.migration.{LiquibaseMigration, Migration}
import io.github.gitbucket.solidbase.model.{Module, Version}
import org.json4s.{Formats, NoTypeHints}
import org.json4s.jackson.Serialization
@@ -20,11 +20,7 @@ import scala.util.Using
object GitBucketCoreModule
extends Module(
"gitbucket-core",
new Version(
"4.0.0",
new LiquibaseMigration("update/gitbucket-core_4.0.xml"),
new SqlMigration("update/gitbucket-core_4.0.sql")
),
new Version("4.0.0", new LiquibaseMigration("update/gitbucket-core_4.0.xml")),
new Version("4.1.0"),
new Version("4.2.0", new LiquibaseMigration("update/gitbucket-core_4.2.xml")),
new Version("4.2.1"),
@@ -32,11 +28,7 @@ object GitBucketCoreModule
new Version("4.4.0"),
new Version("4.5.0"),
new Version("4.6.0", new LiquibaseMigration("update/gitbucket-core_4.6.xml")),
new Version(
"4.7.0",
new LiquibaseMigration("update/gitbucket-core_4.7.xml"),
new SqlMigration("update/gitbucket-core_4.7.sql")
),
new Version("4.7.0", new LiquibaseMigration("update/gitbucket-core_4.7.xml")),
new Version("4.7.1"),
new Version("4.8"),
new Version("4.9.0", new LiquibaseMigration("update/gitbucket-core_4.9.xml")),
@@ -45,11 +37,7 @@ object GitBucketCoreModule
new Version("4.12.0"),
new Version("4.12.1"),
new Version("4.13.0"),
new Version(
"4.14.0",
new LiquibaseMigration("update/gitbucket-core_4.14.xml"),
new SqlMigration("update/gitbucket-core_4.14.sql")
),
new Version("4.14.0", new LiquibaseMigration("update/gitbucket-core_4.14.xml")),
new Version("4.14.1"),
new Version("4.15.0"),
new Version("4.16.0"),
@@ -119,5 +107,15 @@ object GitBucketCoreModule
new Version("4.35.3"),
new Version("4.36.0", new LiquibaseMigration("update/gitbucket-core_4.36.xml")),
new Version("4.36.1"),
new Version("4.36.2")
new Version("4.36.2"),
new Version("4.37.0", new LiquibaseMigration("update/gitbucket-core_4.37.xml")),
new Version("4.37.1"),
new Version("4.37.2"),
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")
)

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

@@ -97,7 +97,7 @@ object ApiCommits {
ApiCommits(
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${commitInfo.id}"),
sha = commitInfo.id,
html_url = ApiPath(s"${repositoryName.fullName}/commit/${commitInfo.id}"),
html_url = ApiPath(s"/${repositoryName.fullName}/commit/${commitInfo.id}"),
comment_url = ApiPath(""), // TODO no API for commit comment
commit = Commit(
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${commitInfo.id}"),

View File

@@ -12,7 +12,7 @@ case class ApiIssue(
number: Int,
title: String,
user: ApiUser,
assignee: Option[ApiUser],
assignees: List[ApiUser],
labels: List[ApiLabel],
state: String,
created_at: Date,
@@ -21,7 +21,7 @@ case class ApiIssue(
milestone: Option[ApiMilestone]
)(repositoryName: RepositoryName, isPullRequest: Boolean) {
val id = 0 // dummy id
val assignees = List(assignee).flatten
val assignee = assignees.headOption
val comments_url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/issues/${number}/comments")
val html_url = ApiPath(s"/${repositoryName.fullName}/${if (isPullRequest) { "pull" } else { "issues" }}/${number}")
val pull_request = if (isPullRequest) {
@@ -43,7 +43,7 @@ object ApiIssue {
issue: Issue,
repositoryName: RepositoryName,
user: ApiUser,
assignee: Option[ApiUser],
assignees: List[ApiUser],
labels: List[ApiLabel],
milestone: Option[ApiMilestone]
): ApiIssue =
@@ -51,7 +51,7 @@ object ApiIssue {
number = issue.issueId,
title = issue.title,
user = user,
assignee = assignee,
assignees = assignees,
labels = labels,
milestone = milestone,
state = if (issue.closed) { "closed" } else { "open" },

View File

@@ -21,10 +21,11 @@ case class ApiPullRequest(
body: String,
user: ApiUser,
labels: List[ApiLabel],
assignee: Option[ApiUser],
assignees: List[ApiUser],
draft: Option[Boolean]
) {
val id = 0 // dummy id
val assignee = assignees.headOption
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 patch_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.patch")
@@ -45,7 +46,7 @@ object ApiPullRequest {
baseRepo: ApiRepository,
user: ApiUser,
labels: List[ApiLabel],
assignee: Option[ApiUser],
assignees: List[ApiUser],
mergedComment: Option[(IssueComment, Account)]
): ApiPullRequest =
ApiPullRequest(
@@ -63,7 +64,7 @@ object ApiPullRequest {
body = issue.content.getOrElse(""),
user = user,
labels = labels,
assignee = assignee,
assignees = assignees,
draft = Some(pullRequest.isDraft)
)

View File

@@ -1,5 +1,55 @@
package gitbucket.core.api
case class ApiObject(sha: String)
import gitbucket.core.util.JGitUtil.TagInfo
import gitbucket.core.util.RepositoryName
import org.eclipse.jgit.lib.Ref
case class ApiRef(ref: String, `object`: ApiObject)
case class ApiRefCommit(
sha: String,
`type`: String,
url: ApiPath
)
case class ApiRef(
ref: String,
node_id: String = "",
url: ApiPath,
`object`: ApiRefCommit,
)
object ApiRef {
def fromRef(
repositoryName: RepositoryName,
ref: Ref
): ApiRef =
ApiRef(
ref = ref.getName,
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/${ref.getName}"),
`object` = ApiRefCommit(
sha = ref.getObjectId.getName,
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${ref.getObjectId.getName}"),
`type` = "commit"
)
)
def fromTag(
repositoryName: RepositoryName,
tagInfo: TagInfo
): ApiRef =
ApiRef(
ref = s"refs/tags/${tagInfo.name}",
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/refs/tags/${tagInfo.name}"),
//the GH api distinguishes between "releases" and plain git tags
//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>"
//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,
//which GH does for tags that are not annotated
`object` = ApiRefCommit(
sha = tagInfo.objectId,
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${tagInfo.objectId}"),
`type` = "commit"
)
)
}

View File

@@ -21,7 +21,7 @@ case class ApiRepository(
val url = ApiPath(s"/api/v3/repos/${full_name}")
val clone_url = ApiPath(s"/git/${full_name}.git")
val html_url = ApiPath(s"/${full_name}")
val ssh_url = Some(SshPath(s":${full_name}.git"))
val ssh_url = Some(SshPath(""))
}
object ApiRepository {
@@ -61,7 +61,7 @@ object ApiRepository {
watchers = 0,
forks = 0,
`private` = false,
default_branch = "master",
default_branch = "main",
owner = owner,
has_issues = true
)

View File

@@ -1,29 +0,0 @@
package gitbucket.core.api
import gitbucket.core.util.RepositoryName
case class ApiTagCommit(
sha: String,
url: ApiPath
)
case class ApiTag(
name: String,
commit: ApiTagCommit,
zipball_url: ApiPath,
tarball_url: ApiPath
)
object ApiTag {
def apply(
tagName: String,
repositoryName: RepositoryName,
commitId: String
): ApiTag =
ApiTag(
name = tagName,
commit = ApiTagCommit(sha = commitId, url = ApiPath(s"/${repositoryName.fullName}/commits/${commitId}")),
zipball_url = ApiPath(s"/${repositoryName.fullName}/archive/${tagName}.zip"),
tarball_url = ApiPath(s"/${repositoryName.fullName}/archive/${tagName}.tar.gz")
)
}

View File

@@ -128,6 +128,15 @@ trait AccountControllerBase extends AccountManagementControllerBase {
"highlighterTheme" -> trim(label("Theme", text(required)))
)(SyntaxHighlighterThemeForm.apply)
val resetPasswordEmailForm = mapping(
"mailAddress" -> trim(label("Email", text(required)))
)(ResetPasswordEmailForm.apply)
val resetPasswordForm = mapping(
"token" -> trim(label("Token", text(required))),
"password" -> trim(label("Password", text(required, maxlength(40))))
)(ResetPasswordForm.apply)
case class NewGroupForm(
groupName: String,
description: Option[String],
@@ -143,6 +152,13 @@ trait AccountControllerBase extends AccountManagementControllerBase {
members: String,
clearImage: Boolean
)
case class ResetPasswordEmailForm(
mailAddress: String
)
case class ResetPasswordForm(
token: String,
password: String
)
val newGroupForm = mapping(
"groupName" -> trim(label("Group name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
@@ -169,7 +185,6 @@ trait AccountControllerBase extends AccountManagementControllerBase {
initOption: String,
sourceUrl: Option[String]
)
case class ForkRepositoryForm(owner: String, name: String)
val newRepositoryForm = mapping(
"owner" -> trim(label("Owner", text(required, maxlength(100), identifier, existsAccount))),
@@ -180,11 +195,6 @@ trait AccountControllerBase extends AccountManagementControllerBase {
"sourceUrl" -> trim(label("Source URL", optionalRequired(_.value("initOption") == "COPY", text())))
)(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)
val accountForm = mapping(
@@ -252,7 +262,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
gitbucket.core.account.html.activity(
account,
if (account.isGroupAccount) Nil else getGroupsByUserName(userName),
getActivitiesByUser(userName, true),
getActivitiesByUser(userName, publicOnly = true),
extraMailAddresses
)
@@ -602,7 +612,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
})
get("/register") {
if (context.settings.allowAccountRegistration) {
if (context.settings.basicBehavior.allowAccountRegistration) {
if (context.loginAccount.isDefined) {
redirect("/")
} else {
@@ -612,7 +622,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
}
post("/register", newForm) { form =>
if (context.settings.allowAccountRegistration) {
if (context.settings.basicBehavior.allowAccountRegistration) {
createAccount(
form.userName,
pbkdf2_sha256(form.password),
@@ -628,6 +638,63 @@ trait AccountControllerBase extends AccountManagementControllerBase {
} else NotFound()
}
get("/reset") {
if (context.settings.basicBehavior.allowResetPassword) {
html.reset()
} else NotFound()
}
post("/reset", resetPasswordEmailForm) { form =>
if (context.settings.basicBehavior.allowResetPassword) {
getAccountByMailAddress(form.mailAddress).foreach { account =>
val token = generateResetPasswordToken(form.mailAddress)
val mailer = new Mailer(context.settings)
mailer.send(
form.mailAddress,
"Reset password",
s"""Hello, ${account.fullName}!
|
|You requested to reset the password for your GitBucket account.
|If you are not sure about the request, you can ignore this email.
|Otherwise, click the following link to set the new password:
|${context.baseUrl}/reset/form/${token}
|""".stripMargin
)
}
redirect("/reset/sent")
} else NotFound()
}
get("/reset/sent") {
if (context.settings.basicBehavior.allowResetPassword) {
html.resetsent()
} else NotFound()
}
get("/reset/form/:token") {
if (context.settings.basicBehavior.allowResetPassword) {
val token = params("token")
decodeResetPasswordToken(token)
.map { _ =>
html.resetform(token)
}
.getOrElse(NotFound())
} else NotFound()
}
post("/reset/form", resetPasswordForm) { form =>
if (context.settings.basicBehavior.allowResetPassword) {
decodeResetPasswordToken(form.token)
.flatMap { mailAddress =>
getAccountByMailAddress(mailAddress).map { account =>
updateAccount(account.copy(password = pbkdf2_sha256(form.password)))
html.resetcomplete()
}
}
.getOrElse(NotFound())
} else NotFound()
}
get("/groups/new")(usersOnly {
context.withLoginAccount { loginAccount =>
html.creategroup(List(GroupMember("", loginAccount.userName, true)))
@@ -713,7 +780,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
*/
get("/new")(usersOnly {
context.withLoginAccount { loginAccount =>
html.newrepo(getGroupsByUserName(loginAccount.userName), context.settings.isCreateRepoOptionPublic)
html.newrepo(getGroupsByUserName(loginAccount.userName), context.settings.basicBehavior.isCreateRepoOptionPublic)
}
})
@@ -723,7 +790,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
post("/new", newRepositoryForm)(usersOnly { form =>
context.withLoginAccount {
loginAccount =>
if (context.settings.repositoryOperation.create || loginAccount.isAdmin) {
if (context.settings.basicBehavior.repositoryOperation.create || loginAccount.isAdmin) {
LockUtil.lock(s"${form.owner}/${form.name}") {
if (getRepository(form.owner, form.name).isDefined) {
// redirect to the repository if repository already exists
@@ -740,7 +807,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
form.description,
form.isPrivate,
form.initOption,
form.sourceUrl
form.sourceUrl,
context.settings.defaultBranch
)
// redirect to the repository
redirect(s"/${form.owner}/${form.name}")
@@ -753,7 +821,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
get("/:owner/:repository/fork")(readableUsersOnly { repository =>
context.withLoginAccount {
loginAccount =>
if (repository.repository.options.allowFork && (context.settings.repositoryOperation.fork || loginAccount.isAdmin)) {
if (repository.repository.options.allowFork && (context.settings.basicBehavior.repositoryOperation.fork || loginAccount.isAdmin)) {
val loginUserName = loginAccount.userName
val groups = getGroupsByUserName(loginUserName)
groups match {
@@ -780,7 +848,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) =>
context.withLoginAccount {
loginAccount =>
if (repository.repository.options.allowFork && (context.settings.repositoryOperation.fork || loginAccount.isAdmin)) {
if (repository.repository.options.allowFork && (context.settings.basicBehavior.repositoryOperation.fork || loginAccount.isAdmin)) {
val loginUserName = loginAccount.userName
val accountName = form.accountName

View File

@@ -1,7 +1,6 @@
package gitbucket.core.controller
import java.io.{File, FileInputStream}
import java.io.{File, FileInputStream, FileOutputStream}
import gitbucket.core.api.{ApiError, JsonFormat}
import gitbucket.core.model.Account
import gitbucket.core.service.{AccountService, RepositoryService, SystemSettingsService}
@@ -14,9 +13,9 @@ import org.scalatra._
import org.scalatra.i18n._
import org.scalatra.json._
import org.scalatra.forms._
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
import javax.servlet.{FilterChain, ServletRequest, ServletResponse}
import is.tagomor.woothee.Classifier
import scala.util.Try
@@ -29,6 +28,9 @@ import org.eclipse.jgit.treewalk._
import org.apache.commons.io.IOUtils
import org.slf4j.LoggerFactory
import org.json4s.Formats
import org.json4s.jackson.Serialization
import java.nio.charset.StandardCharsets
/**
* Provides generic features for controller implementations.
@@ -93,8 +95,16 @@ abstract class ControllerBase
}
}
private def LoginAccount: Option[Account] =
request.getAs[Account](Keys.Session.LoginAccount).orElse(session.getAs[Account](Keys.Session.LoginAccount))
private def LoginAccount: Option[Account] = {
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 =
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()
}
}
}
/**

View File

@@ -6,6 +6,7 @@ import gitbucket.core.service._
import gitbucket.core.util.{Keys, UsersAuthenticator}
import gitbucket.core.util.Implicits._
import gitbucket.core.service.IssuesService._
import gitbucket.core.service.ActivityService._
class DashboardController
extends DashboardControllerBase
@@ -40,9 +41,9 @@ trait DashboardControllerBase extends ControllerBase {
context.loginAccount,
None,
withoutPhysicalInfo = true,
limit = context.settings.limitVisibleRepositories
limit = context.settings.basicBehavior.limitVisibleRepositories
)
html.repos(getGroupNames(loginAccount.userName), repos, repos)
html.repos(getGroupNames(loginAccount.userName), repos, repos, isNewsFeedEnabled)
}
})
@@ -129,8 +130,9 @@ trait DashboardControllerBase extends ControllerBase {
context.loginAccount,
None,
withoutPhysicalInfo = true,
limit = context.settings.limitVisibleRepositories
)
limit = context.settings.basicBehavior.limitVisibleRepositories
),
isNewsFeedEnabled
)
}
@@ -171,8 +173,9 @@ trait DashboardControllerBase extends ControllerBase {
context.loginAccount,
None,
withoutPhysicalInfo = true,
limit = context.settings.limitVisibleRepositories
)
limit = context.settings.basicBehavior.limitVisibleRepositories
),
isNewsFeedEnabled
)
}

View File

@@ -1,7 +1,8 @@
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.openid.connect.sdk.Nonce
import gitbucket.core.helper.xml
@@ -13,6 +14,8 @@ import gitbucket.core.view.helpers._
import org.scalatra.Ok
import org.scalatra.forms._
import gitbucket.core.service.ActivityService._
class IndexController
extends IndexControllerBase
with RepositoryService
@@ -57,30 +60,41 @@ trait IndexControllerBase extends ControllerBase {
//
// 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("/") {
context.loginAccount
.map { account =>
val visibleOwnerSet: Set[String] = Set(account.userName) ++ getGroupsByUserName(account.userName)
gitbucket.core.html.index(
getRecentActivitiesByOwners(visibleOwnerSet),
getVisibleRepositories(
if (!isNewsFeedEnabled) {
redirect("/dashboard/repos")
} else {
val repos = getVisibleRepositories(
Some(account),
None,
withoutPhysicalInfo = true,
limit = context.settings.limitVisibleRepositories
),
showBannerToCreatePersonalAccessToken = hasAccountFederation(account.userName) && !hasAccessToken(
account.userName
limit = false
)
)
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 {
gitbucket.core.html.index(
getRecentPublicActivities(),
getVisibleRepositories(None, withoutPhysicalInfo = true),
showBannerToCreatePersonalAccessToken = false
activities = getRecentPublicActivities(),
recentRepositories = getVisibleRepositories(None, withoutPhysicalInfo = true),
showBannerToCreatePersonalAccessToken = false,
enableNewsFeed = isNewsFeedEnabled
)
}
}
@@ -120,8 +134,8 @@ trait IndexControllerBase extends ControllerBase {
case _ => "/"
}
session.setAttribute(
Keys.Session.OidcContext,
OidcContext(authenticationRequest.getState, authenticationRequest.getNonce, redirectBackURI)
Keys.Session.OidcAuthContext,
OidcAuthContext(authenticationRequest.getState, authenticationRequest.getNonce, redirectBackURI)
)
redirect(authenticationRequest.toURI.toString)
} getOrElse {
@@ -135,10 +149,12 @@ trait IndexControllerBase extends ControllerBase {
get("/signin/oidc") {
context.settings.oidc.map { oidc =>
val redirectURI = new URI(s"$baseUrl/signin/oidc")
session.get(Keys.Session.OidcContext) match {
case Some(context: OidcContext) =>
authenticate(params.toMap, redirectURI, context.state, context.nonce, oidc).map { account =>
signin(account, context.redirectBackURI)
session.get(Keys.Session.OidcAuthContext) match {
case Some(context: OidcAuthContext) =>
authenticate(params.toMap, redirectURI, context.state, context.nonce, oidc).map {
case (jwt, account) =>
session.setAttribute(Keys.Session.OidcSessionContext, OidcSessionContext(jwt))
signin(account, context.redirectBackURI)
} orElse {
flash.update("error", "Sorry, authentication failed. Please try again.")
session.invalidate()
@@ -155,7 +171,19 @@ trait IndexControllerBase extends ControllerBase {
}
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("/")
}
@@ -178,6 +206,9 @@ trait IndexControllerBase extends ControllerBase {
*/
private def signin(account: Account, redirectUrl: String = "/") = {
session.setAttribute(Keys.Session.LoginAccount, account)
if (isDevFeatureEnabled(DevFeatures.KeepSession)) {
saveLoginAccountToLocalFile(account)
}
updateLastLoginDate(account.userName)
if (LDAPUtil.isDummyMailAddress(account)) {
@@ -201,7 +232,7 @@ trait IndexControllerBase extends ControllerBase {
org.json4s.jackson.Serialization.write(
Map(
"options" -> (
getAllUsers(false)
getAllUsers(includeRemoved = false)
.withFilter { t =>
(user, group) match {
case (true, true) => true
@@ -234,7 +265,7 @@ trait IndexControllerBase extends ControllerBase {
} getOrElse ""
})
// TODO Move to RepositoryViewrController?
// TODO Move to RepositoryViewerController?
get("/:owner/:repository/search")(referrersOnly { repository =>
val query = params.getOrElse("q", "").trim
val target = params.getOrElse("type", "code")
@@ -248,8 +279,8 @@ trait IndexControllerBase extends ControllerBase {
target.toLowerCase match {
case "issues" =>
gitbucket.core.search.html.issues(
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, false) else Nil,
false,
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, pullRequest = false) else Nil,
pullRequest = false,
query,
page,
repository
@@ -257,8 +288,8 @@ trait IndexControllerBase extends ControllerBase {
case "pulls" =>
gitbucket.core.search.html.issues(
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, true) else Nil,
true,
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, pullRequest = true) else Nil,
pullRequest = true,
query,
page,
repository
@@ -289,19 +320,19 @@ trait IndexControllerBase extends ControllerBase {
context.loginAccount,
None,
withoutPhysicalInfo = true,
limit = context.settings.limitVisibleRepositories
limit = context.settings.basicBehavior.limitVisibleRepositories
)
val repositories = {
context.settings.limitVisibleRepositories match {
case true =>
getVisibleRepositories(
context.loginAccount,
None,
withoutPhysicalInfo = true,
limit = false
)
case false => visibleRepositories
if (context.settings.basicBehavior.limitVisibleRepositories) {
getVisibleRepositories(
context.loginAccount,
None,
withoutPhysicalInfo = true,
limit = false
)
} else {
visibleRepositories
}
}.filter { repository =>
repository.name.toLowerCase.indexOf(query) >= 0 || repository.owner.toLowerCase.indexOf(query) >= 0

View File

@@ -1,7 +1,7 @@
package gitbucket.core.controller
import gitbucket.core.issues.html
import gitbucket.core.model.Account
import gitbucket.core.model.{Account, CustomFieldBehavior}
import gitbucket.core.service.IssuesService._
import gitbucket.core.service._
import gitbucket.core.util.Implicits._
@@ -21,6 +21,7 @@ class IssuesController
with ActivityService
with HandleCommentService
with IssueCreationService
with CustomFieldsService
with ReadableUsersAuthenticator
with ReferrerAuthenticator
with WritableUsersAuthenticator
@@ -41,6 +42,7 @@ trait IssuesControllerBase extends ControllerBase {
with ActivityService
with HandleCommentService
with IssueCreationService
with CustomFieldsService
with ReadableUsersAuthenticator
with ReferrerAuthenticator
with WritableUsersAuthenticator
@@ -51,7 +53,7 @@ trait IssuesControllerBase extends ControllerBase {
case class IssueCreateForm(
title: String,
content: Option[String],
assignedUserName: Option[String],
assigneeUserNames: Option[String],
milestoneId: Option[Int],
priorityId: Option[Int],
labelNames: Option[String]
@@ -62,7 +64,7 @@ trait IssuesControllerBase extends ControllerBase {
val issueCreateForm = mapping(
"title" -> trim(label("Title", text(required))),
"content" -> trim(optional(text())),
"assignedUserName" -> trim(optional(text())),
"assigneeUserNames" -> trim(optional(text())),
"milestoneId" -> trim(optional(number())),
"priorityId" -> trim(optional(number())),
"labelNames" -> trim(optional(text()))
@@ -87,10 +89,13 @@ trait IssuesControllerBase extends ControllerBase {
get("/:owner/:repository/issues")(referrersOnly { repository =>
val q = request.getParameter("q")
if (Option(q).exists(_.contains("is:pr"))) {
redirect(s"/${repository.owner}/${repository.name}/pulls?q=${StringUtil.urlEncode(q)}")
} else {
searchIssues(repository)
Option(q) match {
case Some(filter) if filter.contains("is:pr") =>
redirect(s"/${repository.owner}/${repository.name}/pulls?q=${StringUtil.urlEncode(q)}")
case Some(filter) =>
searchIssues(repository, IssueSearchCondition(filter), IssueSearchCondition.page(request))
case None =>
searchIssues(repository, IssueSearchCondition(request), IssueSearchCondition.page(request))
}
})
@@ -105,10 +110,12 @@ trait IssuesControllerBase extends ControllerBase {
issue,
getComments(repository.owner, repository.name, issueId.toInt),
getIssueLabels(repository.owner, repository.name, issueId.toInt),
getIssueAssignees(repository.owner, repository.name, issueId.toInt),
getAssignableUserNames(repository.owner, repository.name),
getMilestonesWithIssueCount(repository.owner, repository.name),
getPriorities(repository.owner, repository.name),
getLabels(repository.owner, repository.name),
getCustomFieldsWithValue(repository.owner, repository.name, issueId.toInt).filter(_._1.enableForIssues),
isIssueEditable(repository),
isIssueManageable(repository),
isIssueCommentManageable(repository),
@@ -126,6 +133,7 @@ trait IssuesControllerBase extends ControllerBase {
getPriorities(repository.owner, repository.name),
getDefaultPriority(repository.owner, repository.name),
getLabels(repository.owner, repository.name),
getCustomFields(repository.owner, repository.name).filter(_.enableForIssues),
isIssueManageable(repository),
getContentTemplate(repository, "ISSUE_TEMPLATE"),
repository
@@ -141,12 +149,31 @@ trait IssuesControllerBase extends ControllerBase {
repository,
form.title,
form.content,
form.assignedUserName,
form.assigneeUserNames.toSeq.flatMap(_.split(",")),
form.milestoneId,
form.priorityId,
form.labelNames.toSeq.flatMap(_.split(",")),
loginAccount
)
// Insert custom field values
params.toMap.foreach {
case (key, value) =>
if (key.startsWith("custom-field-")) {
getCustomField(
repository.owner,
repository.name,
key.replaceFirst("^custom-field-", "").toInt
).foreach { field =>
CustomFieldBehavior.validate(field, value, messages) match {
case None =>
insertOrUpdateCustomFieldValue(field, repository.owner, repository.name, issue.issueId, value)
case Some(_) => halt(400)
}
}
}
}
redirect(s"/${issue.userName}/${issue.repositoryName}/issues/${issue.issueId}")
} else Unauthorized()
}
@@ -235,7 +262,7 @@ trait IssuesControllerBase extends ControllerBase {
loginAccount =>
getComment(repository.owner, repository.name, params("id")).map { comment =>
if (isEditableContent(repository.owner, repository.name, comment.commentedUserName, loginAccount)) {
updateComment(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}")
} else Unauthorized()
} getOrElse NotFound()
@@ -333,15 +360,16 @@ trait IssuesControllerBase extends ControllerBase {
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
})
ajaxPost("/:owner/:repository/issues/:id/assign")(writableUsersOnly { repository =>
updateAssignedUserName(
repository.owner,
repository.name,
params("id").toInt,
assignedUserName("assignedUserName"),
true
)
Ok("updated")
ajaxPost("/:owner/:repository/issues/:id/assignee/new")(writableUsersOnly { repository =>
val issueId = params("id").toInt
registerIssueAssignee(repository.owner, repository.name, issueId, params("assigneeUserName"), true)
Ok()
})
ajaxPost("/:owner/:repository/issues/:id/assignee/delete")(writableUsersOnly { repository =>
val issueId = params("id").toInt
deleteIssueAssignee(repository.owner, repository.name, issueId, params("assigneeUserName"), true)
Ok()
})
ajaxPost("/:owner/:repository/issues/:id/milestone")(writableUsersOnly { repository =>
@@ -362,6 +390,35 @@ trait IssuesControllerBase extends ControllerBase {
Ok("updated")
})
ajaxPost("/:owner/:repository/issues/customfield_validation/:fieldId")(writableUsersOnly { repository =>
val fieldId = params("fieldId").toInt
val value = params("value")
getCustomField(repository.owner, repository.name, fieldId)
.flatMap { field =>
CustomFieldBehavior.validate(field, value, messages).map { error =>
Ok(error)
}
}
.getOrElse(Ok())
})
ajaxPost("/:owner/:repository/issues/:id/customfield/:fieldId")(writableUsersOnly { repository =>
val issueId = params("id").toInt
val fieldId = params("fieldId").toInt
val value = params("value")
for {
_ <- getIssue(repository.owner, repository.name, issueId.toString)
field <- getCustomField(repository.owner, repository.name, fieldId)
} {
CustomFieldBehavior.validate(field, value, messages) match {
case None => insertOrUpdateCustomFieldValue(field, repository.owner, repository.name, issueId, value)
case Some(_) => halt(400)
}
}
Ok(value)
})
post("/:owner/:repository/issues/batchedit/state")(writableUsersOnly { repository =>
val action = params.get("value")
action match {
@@ -403,7 +460,13 @@ trait IssuesControllerBase extends ControllerBase {
post("/:owner/:repository/issues/batchedit/assign")(writableUsersOnly { repository =>
val value = assignedUserName("value")
executeBatch(repository) {
updateAssignedUserName(repository.owner, repository.name, _, value, true)
//updateAssignedUserName(repository.owner, repository.name, _, value, true)
value match {
case Some(assignedUserName) =>
registerIssueAssignee(repository.owner, repository.name, _, assignedUserName, true)
case None =>
deleteAllIssueAssignees(repository.owner, repository.name, _, true)
}
}
if (params("uri").nonEmpty) {
redirect(params("uri"))
@@ -471,10 +534,7 @@ trait IssuesControllerBase extends ControllerBase {
}
}
private def searchIssues(repository: RepositoryService.RepositoryInfo) = {
val page = IssueSearchCondition.page(request)
// retrieve search condition
val condition = IssueSearchCondition(request)
private def searchIssues(repository: RepositoryService.RepositoryInfo, condition: IssueSearchCondition, page: Int) = {
// search issues
val issues =
searchIssue(

View File

@@ -44,7 +44,7 @@ trait LabelsControllerBase extends ControllerBase {
get("/:owner/:repository/issues/labels")(referrersOnly { repository =>
html.list(
getLabels(repository.owner, repository.name),
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition()),
repository,
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
)
@@ -59,7 +59,7 @@ trait LabelsControllerBase extends ControllerBase {
html.label(
getLabel(repository.owner, repository.name, labelId).get,
// TODO futility
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition()),
repository,
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
)
@@ -76,7 +76,7 @@ trait LabelsControllerBase extends ControllerBase {
html.label(
getLabel(repository.owner, repository.name, params("labelId").toInt).get,
// TODO futility
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition()),
repository,
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
)

View File

@@ -29,7 +29,7 @@ trait PreProcessControllerBase extends ControllerBase {
* If anonymous access is allowed, pass all requests.
* But if it's not allowed, demands authentication except some paths.
*/
get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) {
get(!context.settings.basicBehavior.allowAnonymousAccess, context.loginAccount.isEmpty) {
if (!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
!context.currentPath.startsWith("/register") && !context.currentPath.endsWith("/info/refs") &&
!context.currentPath.startsWith("/plugin-assets") &&

View File

@@ -45,7 +45,7 @@ trait PrioritiesControllerBase extends ControllerBase {
get("/:owner/:repository/issues/priorities")(referrersOnly { repository =>
html.list(
getPriorities(repository.owner, repository.name),
countIssueGroupByPriorities(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
countIssueGroupByPriorities(repository.owner, repository.name, IssuesService.IssueSearchCondition()),
repository,
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))
html.priority(
getPriority(repository.owner, repository.name, priorityId).get,
countIssueGroupByPriorities(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
countIssueGroupByPriorities(repository.owner, repository.name, IssuesService.IssueSearchCondition()),
repository,
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
)
@@ -84,7 +84,7 @@ trait PrioritiesControllerBase extends ControllerBase {
)
html.priority(
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,
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
)

View File

@@ -25,6 +25,7 @@ class PullRequestsController
with PullRequestService
with MilestonesService
with LabelsService
with CustomFieldsService
with CommitsService
with ActivityService
with WebHookPullRequestService
@@ -44,6 +45,7 @@ trait PullRequestsControllerBase extends ControllerBase {
with IssuesService
with MilestonesService
with LabelsService
with CustomFieldsService
with CommitsService
with ActivityService
with PullRequestService
@@ -67,7 +69,7 @@ trait PullRequestsControllerBase extends ControllerBase {
"commitIdFrom" -> trim(text(required, maxlength(40))),
"commitIdTo" -> trim(text(required, maxlength(40))),
"isDraft" -> trim(boolean(required)),
"assignedUserName" -> trim(optional(text())),
"assigneeUserNames" -> trim(optional(text())),
"milestoneId" -> trim(optional(number())),
"priorityId" -> trim(optional(number())),
"labelNames" -> trim(optional(text()))
@@ -90,7 +92,7 @@ trait PullRequestsControllerBase extends ControllerBase {
commitIdFrom: String,
commitIdTo: String,
isDraft: Boolean,
assignedUserName: Option[String],
assigneeUserNames: Option[String],
milestoneId: Option[Int],
priorityId: Option[Int],
labelNames: Option[String]
@@ -100,10 +102,13 @@ trait PullRequestsControllerBase extends ControllerBase {
get("/:owner/:repository/pulls")(referrersOnly { repository =>
val q = request.getParameter("q")
if (Option(q).exists(_.contains("is:issue"))) {
redirect(s"/${repository.owner}/${repository.name}/issues?q=" + StringUtil.urlEncode(q))
} else {
searchPullRequests(None, repository)
Option(q) match {
case Some(filter) if filter.contains("is:issue") =>
redirect(s"/${repository.owner}/${repository.name}/issues?q=${StringUtil.urlEncode(q)}")
case Some(filter) =>
searchPullRequests(repository, IssueSearchCondition(filter), IssueSearchCondition.page(request))
case None =>
searchPullRequests(repository, IssueSearchCondition(request), IssueSearchCondition.page(request))
}
})
@@ -129,10 +134,12 @@ trait PullRequestsControllerBase extends ControllerBase {
getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
diffs.size,
getIssueLabels(repository.owner, repository.name, issueId),
getIssueAssignees(repository.owner, repository.name, issueId),
getAssignableUserNames(repository.owner, repository.name),
getMilestonesWithIssueCount(repository.owner, repository.name),
getPriorities(repository.owner, repository.name),
getLabels(repository.owner, repository.name),
getCustomFieldsWithValue(repository.owner, repository.name, issueId).filter(_._1.enableForPullRequests),
isEditable(repository),
isManageable(repository),
hasDeveloperRole(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount),
@@ -140,25 +147,6 @@ trait PullRequestsControllerBase extends ControllerBase {
getRepository(pullreq.requestUserName, pullreq.requestRepositoryName),
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()
})
@@ -392,9 +380,9 @@ trait PullRequestsControllerBase extends ControllerBase {
})
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 {
case (Some(originUserName), Some(originRepositoryName)) => {
case (Some(originUserName), Some(originRepositoryName)) =>
getRepository(originUserName, originRepositoryName).map {
originRepository =>
Using.resources(
@@ -411,8 +399,7 @@ trait PullRequestsControllerBase extends ControllerBase {
)
}
} getOrElse NotFound()
}
case _ => {
case _ =>
Using.resource(Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))) { git =>
JGitUtil.getDefaultBranch(git, forkedRepository).map {
case (_, defaultBranch) =>
@@ -423,41 +410,48 @@ trait PullRequestsControllerBase extends ControllerBase {
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 =>
val Seq(origin, forked) = multiParams("splat")
val (originOwner, originId) = parseCompareIdentifier(origin, forkedRepository.owner)
val (forkedOwner, forkedId) = parseCompareIdentifier(forked, forkedRepository.owner)
(for (originRepositoryName <- 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)
};
(for (originRepositoryName <- getOriginRepositoryName(originOwner, forkedOwner, forkedRepository);
originRepository <- getRepository(originOwner, originRepositoryName)) yield {
val (oldId, newId) =
getPullRequestCommitFromTo(originRepository, forkedRepository, originId, forkedId)
(oldId, newId) match {
case (Some(oldId), Some(newId)) => {
case (Some(oldId), Some(newId)) =>
val (commits, diffs) = getRequestCompareInfo(
originRepository.owner,
originRepository.name,
@@ -505,9 +499,9 @@ trait PullRequestsControllerBase extends ControllerBase {
getMilestones(originRepository.owner, originRepository.name),
getPriorities(originRepository.owner, originRepository.name),
getDefaultPriority(originRepository.owner, originRepository.name),
getLabels(originRepository.owner, originRepository.name)
getLabels(originRepository.owner, originRepository.name),
getCustomFields(originRepository.owner, originRepository.name).filter(_.enableForPullRequests)
)
}
case (oldId, newId) =>
redirect(
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/" +
@@ -519,6 +513,54 @@ trait PullRequestsControllerBase extends ControllerBase {
}) 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 =>
val Seq(origin, forked) = multiParams("splat")
val (originOwner, tmpOriginBranch) = parseCompareIdentifier(origin, forkedRepository.owner)
@@ -567,7 +609,6 @@ trait PullRequestsControllerBase extends ControllerBase {
loginUser = loginAccount.userName,
title = form.title,
content = form.content,
assignedUserName = if (manageable) form.assignedUserName else None,
milestoneId = if (manageable) form.milestoneId else None,
priorityId = if (manageable) form.priorityId else None,
isPullRequest = true
@@ -587,8 +628,14 @@ trait PullRequestsControllerBase extends ControllerBase {
settings = context.settings
)
// insert labels
if (manageable) {
// insert assignees
form.assigneeUserNames.foreach { value =>
value.split(",").foreach { userName =>
registerIssueAssignee(repository.owner, repository.name, issueId, userName)
}
}
// insert labels
form.labelNames.foreach { value =>
val labels = getLabels(repository.owner, repository.name)
value.split(",").foreach { labelName =>
@@ -646,10 +693,11 @@ trait PullRequestsControllerBase extends ControllerBase {
html.proposals(proposedBranches, targetRepository, repository)
})
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) = {
val page = IssueSearchCondition.page(request)
// retrieve search condition
val condition = IssueSearchCondition(request)
private def searchPullRequests(
repository: RepositoryService.RepositoryInfo,
condition: IssueSearchCondition,
page: Int
) = {
// search issues
val issues = searchIssue(
condition,

View File

@@ -138,7 +138,7 @@ trait ReleaseControllerBase extends ControllerBase {
get("/:owner/:repository/changelog/*...*")(writableUsersOnly { repository =>
val Seq(previousTag, currentTag) = multiParams("splat")
val previousTagId = repository.tags.collectFirst { case x if x.name == previousTag => x.id }.getOrElse("")
val previousTagId = repository.tags.collectFirst { case x if x.name == previousTag => x.commitId }.getOrElse("")
val commitLog = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
val commits = JGitUtil.getCommitLog(git, previousTagId, currentTag).reverse

View File

@@ -2,7 +2,6 @@ package gitbucket.core.controller
import java.time.{LocalDateTime, ZoneOffset}
import java.util.Date
import gitbucket.core.settings.html
import gitbucket.core.model.{RepositoryWebHook, WebHook}
import gitbucket.core.service._
@@ -21,7 +20,7 @@ import org.eclipse.jgit.lib.Constants
import org.eclipse.jgit.lib.ObjectId
import scala.util.Using
import org.scalatra.Forbidden
import org.scalatra.{Forbidden, Ok}
class RepositorySettingsController
extends RepositorySettingsControllerBase
@@ -31,6 +30,7 @@ class RepositorySettingsController
with ProtectedBranchService
with CommitStatusService
with DeployKeyService
with CustomFieldsService
with ActivityService
with OwnerAuthenticator
with UsersAuthenticator
@@ -43,6 +43,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
with ProtectedBranchService
with CommitStatusService
with DeployKeyService
with CustomFieldsService
with ActivityService
with OwnerAuthenticator
with UsersAuthenticator =>
@@ -121,6 +122,23 @@ trait RepositorySettingsControllerBase extends ControllerBase {
"newOwner" -> trim(label("New owner", text(required, transferUser)))
)(TransferOwnerShipForm.apply)
// for custom field
case class CustomFieldForm(
fieldName: String,
fieldType: String,
constraints: Option[String],
enableForIssues: Boolean,
enableForPullRequests: Boolean
)
val customFieldForm = mapping(
"fieldName" -> trim(label("Field name", text(required, maxlength(100)))),
"fieldType" -> trim(label("Field type", text(required))),
"constraints" -> trim(label("Constraints", optional(text()))),
"enableForIssues" -> trim(label("Enable for issues", boolean(required))),
"enableForPullRequests" -> trim(label("Enable for pull requests", boolean(required))),
)(CustomFieldForm.apply)
/**
* Redirect to the Options page.
*/
@@ -390,7 +408,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
post("/:owner/:repository/settings/rename", renameForm)(ownerOnly { (form, repository) =>
context.withLoginAccount {
loginAccount =>
if (context.settings.repositoryOperation.rename || loginAccount.isAdmin) {
if (context.settings.basicBehavior.repositoryOperation.rename || loginAccount.isAdmin) {
if (repository.name != form.repositoryName) {
// Update database and move git repository
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
@@ -414,7 +432,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) =>
context.withLoginAccount {
loginAccount =>
if (context.settings.repositoryOperation.transfer || loginAccount.isAdmin) {
if (context.settings.basicBehavior.repositoryOperation.transfer || loginAccount.isAdmin) {
// Change repository owner
if (repository.owner != form.newOwner) {
// Update database and move git repository
@@ -438,7 +456,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
*/
post("/:owner/:repository/settings/delete")(ownerOnly { repository =>
context.withLoginAccount { loginAccount =>
if (context.settings.repositoryOperation.delete || loginAccount.isAdmin) {
if (context.settings.basicBehavior.repositoryOperation.delete || loginAccount.isAdmin) {
// Delete the repository and related files
deleteRepository(repository.repository)
redirect(s"/${repository.owner}")
@@ -477,6 +495,60 @@ trait RepositorySettingsControllerBase extends ControllerBase {
redirect(s"/${repository.owner}/${repository.name}/settings/deploykey")
})
/** Custom fields for issues and pull requests */
get("/:owner/:repository/settings/issues")(ownerOnly { repository =>
val customFields = getCustomFields(repository.owner, repository.name)
html.issues(customFields, repository)
})
/** New custom field form */
get("/:owner/:repository/settings/issues/fields/new")(ownerOnly { repository =>
html.issuesfieldform(None, repository)
})
/** Add custom field */
ajaxPost("/:owner/:repository/settings/issues/fields/new", customFieldForm)(ownerOnly { (form, repository) =>
val fieldId = createCustomField(
repository.owner,
repository.name,
form.fieldName,
form.fieldType,
if (form.fieldType == "enum") form.constraints else None,
form.enableForIssues,
form.enableForPullRequests
)
html.issuesfield(getCustomField(repository.owner, repository.name, fieldId).get)
})
/** Edit custom field form */
ajaxGet("/:owner/:repository/settings/issues/fields/:fieldId/edit")(ownerOnly { repository =>
getCustomField(repository.owner, repository.name, params("fieldId").toInt).map { customField =>
html.issuesfieldform(Some(customField), repository)
} getOrElse NotFound()
})
/** Update custom field */
ajaxPost("/:owner/:repository/settings/issues/fields/:fieldId/edit", customFieldForm)(ownerOnly {
(form, repository) =>
updateCustomField(
repository.owner,
repository.name,
params("fieldId").toInt,
form.fieldName,
form.fieldType,
if (form.fieldType == "enum") form.constraints else None,
form.enableForIssues,
form.enableForPullRequests
)
html.issuesfield(getCustomField(repository.owner, repository.name, params("fieldId").toInt).get)
})
/** Delete custom field */
ajaxPost("/:owner/:repository/settings/issues/fields/:fieldId/delete")(ownerOnly { repository =>
deleteCustomField(repository.owner, repository.name, params("fieldId").toInt)
Ok()
})
/**
* Provides duplication check for web hook url.
*/

View File

@@ -329,55 +329,14 @@ trait RepositoryViewerControllerBase extends ControllerBase {
})
post("/:owner/:repository/upload", uploadForm)(writableUsersOnly { (form, repository) =>
context.withLoginAccount {
loginAccount =>
val files = form.uploadFiles
.split("\n")
.map { line =>
val i = line.indexOf(':')
CommitFile(line.substring(0, i).trim, line.substring(i + 1).trim)
}
.toSeq
val newFiles = files.map { file =>
file.copy(name = if (form.path.length == 0) file.name else s"${form.path}/${file.name}")
}
if (form.newBranch) {
val newBranchName = createNewBranchForPullRequest(repository, form.branch, loginAccount)
val objectId = _commit(newBranchName, newFiles, loginAccount)
val issueId =
createIssueAndPullRequest(
repository,
form.branch,
newBranchName,
form.commit,
objectId.name,
form.message,
loginAccount
)
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
} else {
_commit(form.branch, newFiles, loginAccount)
if (form.path.length == 0) {
redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}")
} else {
redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}/${form.path}")
}
}
}
def _commit(
branchName: String,
//files: Seq[CommitFile],
newFiles: Seq[CommitFile],
loginAccount: Account
): ObjectId = {
): Either[String, ObjectId] = {
commitFiles(
repository = repository,
branch = branchName,
//path = form.path,
//files = files.toIndexedSeq,
message = form.message.getOrElse("Add files via upload"),
loginAccount = loginAccount,
settings = context.settings
@@ -399,6 +358,52 @@ trait RepositoryViewerControllerBase extends ControllerBase {
}
}
}
context.withLoginAccount {
loginAccount =>
val files = form.uploadFiles
.split("\n")
.map { line =>
val i = line.indexOf(':')
CommitFile(line.substring(0, i).trim, line.substring(i + 1).trim)
}
.toSeq
val newFiles = files.map { file =>
file.copy(name = if (form.path.length == 0) file.name else s"${form.path}/${file.name}")
}
if (form.newBranch) {
val newBranchName = createNewBranchForPullRequest(repository, form.branch, loginAccount)
_commit(newBranchName, newFiles, loginAccount) match {
case Right(objectId) =>
val issueId =
createIssueAndPullRequest(
repository,
form.branch,
newBranchName,
form.commit,
objectId.name,
form.message,
loginAccount
)
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
case Left(error) => Forbidden(gitbucket.core.html.error(error))
}
} else {
_commit(form.branch, newFiles, loginAccount) match {
case Right(_) =>
if (form.path.length == 0) {
redirect(s"/${repository.owner}/${repository.name}/tree/${encodeRefName(form.branch)}")
} else {
redirect(
s"/${repository.owner}/${repository.name}/tree/${encodeRefName(form.branch)}/${encodeRefName(form.path)}"
)
}
case Left(error) => Forbidden(gitbucket.core.html.error(error))
}
}
}
})
get("/:owner/:repository/edit/*")(writableUsersOnly { repository =>
@@ -456,32 +461,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
})
post("/:owner/:repository/create", editorForm)(writableUsersOnly { (form, repository) =>
context.withLoginAccount {
loginAccount =>
if (form.newBranch) {
val newBranchName = createNewBranchForPullRequest(repository, form.branch, loginAccount)
val objectId = _commit(newBranchName, loginAccount)
val issueId =
createIssueAndPullRequest(
repository,
form.branch,
newBranchName,
form.commit,
objectId.name,
form.message,
loginAccount
)
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
} else {
_commit(form.branch, loginAccount)
redirect(
s"/${repository.owner}/${repository.name}/blob/${form.branch}/${if (form.path.length == 0) urlEncode(form.newFileName)
else s"${form.path}/${urlEncode(form.newFileName)}"}"
)
}
}
def _commit(branchName: String, loginAccount: Account): ObjectId = {
def _commit(branchName: String, loginAccount: Account): Either[String, ObjectId] = {
commitFile(
repository = repository,
branch = branchName,
@@ -494,37 +474,48 @@ trait RepositoryViewerControllerBase extends ControllerBase {
commit = form.commit,
loginAccount = loginAccount,
settings = context.settings
)._1
).map(_._1)
}
})
post("/:owner/:repository/update", editorForm)(writableUsersOnly { (form, repository) =>
context.withLoginAccount {
loginAccount =>
if (form.newBranch) {
val newBranchName = createNewBranchForPullRequest(repository, form.branch, loginAccount)
val objectId = _commit(newBranchName, loginAccount)
val issueId =
createIssueAndPullRequest(
repository,
form.branch,
newBranchName,
form.commit,
objectId.name,
form.message,
loginAccount
)
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
_commit(newBranchName, loginAccount) match {
case Right(objectId) =>
val issueId =
createIssueAndPullRequest(
repository,
form.branch,
newBranchName,
form.commit,
objectId.name,
form.message,
loginAccount
)
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
case Left(error) => Forbidden(gitbucket.core.html.error(error))
}
} else {
_commit(form.branch, loginAccount)
redirect(
s"/${repository.owner}/${repository.name}/blob/${urlEncode(form.branch)}/${if (form.path.length == 0) urlEncode(form.newFileName)
else s"${form.path}/${urlEncode(form.newFileName)}"}"
)
_commit(form.branch, loginAccount) match {
case Right(_) =>
if (form.path.length == 0) {
redirect(
s"/${repository.owner}/${repository.name}/blob/${encodeRefName(form.branch)}/${urlEncode(form.newFileName)}"
)
} else {
redirect(
s"/${repository.owner}/${repository.name}/blob/${encodeRefName(form.branch)}/${encodeRefName(form.path)}/${urlEncode(form.newFileName)}"
)
}
case Left(error) => Forbidden(gitbucket.core.html.error(error))
}
}
}
})
def _commit(branchName: String, loginAccount: Account): ObjectId = {
post("/:owner/:repository/update", editorForm)(writableUsersOnly { (form, repository) =>
def _commit(branchName: String, loginAccount: Account): Either[String, ObjectId] = {
commitFile(
repository = repository,
branch = branchName,
@@ -541,37 +532,48 @@ trait RepositoryViewerControllerBase extends ControllerBase {
commit = form.commit,
loginAccount = loginAccount,
settings = context.settings
)._1
).map(_._1)
}
})
post("/:owner/:repository/remove", deleteForm)(writableUsersOnly { (form, repository) =>
context.withLoginAccount {
loginAccount =>
if (form.newBranch) {
val newBranchName = createNewBranchForPullRequest(repository, form.branch, loginAccount)
val objectId = _commit(newBranchName, loginAccount)
val issueId =
createIssueAndPullRequest(
repository,
form.branch,
newBranchName,
form.commit,
objectId.name,
form.message,
loginAccount
)
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
_commit(newBranchName, loginAccount) match {
case Right(objectId) =>
val issueId =
createIssueAndPullRequest(
repository,
form.branch,
newBranchName,
form.commit,
objectId.name,
form.message,
loginAccount
)
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
case Left(error) => Forbidden(gitbucket.core.html.error(error))
}
} else {
_commit(form.branch, loginAccount)
redirect(
s"/${repository.owner}/${repository.name}/tree/${form.branch}${if (form.path.length == 0) ""
else "/" + form.path}"
)
_commit(form.branch, loginAccount) match {
case Right(_) =>
if (form.path.length == 0) {
redirect(
s"/${repository.owner}/${repository.name}/blob/${encodeRefName(form.branch)}/${urlEncode(form.newFileName)}"
)
} else {
redirect(
s"/${repository.owner}/${repository.name}/blob/${encodeRefName(form.branch)}/${encodeRefName(form.path)}/${urlEncode(form.newFileName)}"
)
}
case Left(error) => Forbidden(gitbucket.core.html.error(error))
}
}
}
})
def _commit(branchName: String, loginAccount: Account): ObjectId = {
post("/:owner/:repository/remove", deleteForm)(writableUsersOnly { (form, repository) =>
def _commit(branchName: String, loginAccount: Account): Either[String, ObjectId] = {
commitFile(
repository = repository,
branch = branchName,
@@ -584,7 +586,41 @@ trait RepositoryViewerControllerBase extends ControllerBase {
commit = form.commit,
loginAccount = loginAccount,
settings = context.settings
)._1
).map(_._1)
}
context.withLoginAccount {
loginAccount =>
if (form.newBranch) {
val newBranchName = createNewBranchForPullRequest(repository, form.branch, loginAccount)
_commit(newBranchName, loginAccount) match {
case Right(objectId) =>
val issueId =
createIssueAndPullRequest(
repository,
form.branch,
newBranchName,
form.commit,
objectId.name,
form.message,
loginAccount
)
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
case Left(error) => Forbidden(gitbucket.core.html.error(error))
}
} else {
_commit(form.branch, loginAccount) match {
case Right(_) =>
if (form.path.isEmpty) {
redirect(s"/${repository.owner}/${repository.name}/tree/${encodeRefName(form.branch)}")
} else {
redirect(
s"/${repository.owner}/${repository.name}/tree/${encodeRefName(form.branch)}/${encodeRefName(form.path)}"
)
}
case Left(error) => Forbidden(gitbucket.core.html.error(error))
}
}
}
})
@@ -640,7 +676,6 @@ trait RepositoryViewerControllerBase extends ControllerBase {
loginUser = loginAccount.userName,
title = requestBranch,
content = commitMessage,
assignedUserName = None,
milestoneId = None,
priorityId = None,
isPullRequest = true

View File

@@ -1,7 +1,6 @@
package gitbucket.core.controller
import java.io.FileInputStream
import gitbucket.core.admin.html
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service.SystemSettingsService._
@@ -35,23 +34,38 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
private val form = mapping(
"baseUrl" -> trim(label("Base URL", optional(text()))),
"information" -> trim(label("Information", optional(text()))),
"allowAccountRegistration" -> trim(label("Account registration", boolean())),
"allowAnonymousAccess" -> trim(label("Anonymous access", boolean())),
"isCreateRepoOptionPublic" -> trim(label("Default visibility of new repository", boolean())),
"repositoryOperation" -> mapping(
"create" -> trim(label("Allow all users to create repository", boolean())),
"delete" -> trim(label("Allow all users to delete repository", boolean())),
"rename" -> trim(label("Allow all users to rename repository", boolean())),
"transfer" -> trim(label("Allow all users to transfer repository", boolean())),
"fork" -> trim(label("Allow all users to fork repository", boolean()))
)(RepositoryOperation.apply),
"gravatar" -> trim(label("Gravatar", boolean())),
"notification" -> trim(label("Notification", boolean())),
"limitVisibleRepositories" -> trim(label("limitVisibleRepositories", boolean())),
"basicBehavior" -> mapping(
"allowAccountRegistration" -> trim(label("Account registration", boolean())),
"allowResetPassword" -> trim(label("Reset password", boolean())),
"allowAnonymousAccess" -> trim(label("Anonymous access", boolean())),
"isCreateRepoOptionPublic" -> trim(label("Default visibility of new repository", boolean())),
"repositoryOperation" -> mapping(
"create" -> trim(label("Allow all users to create repository", boolean())),
"delete" -> trim(label("Allow all users to delete repository", boolean())),
"rename" -> trim(label("Allow all users to rename repository", boolean())),
"transfer" -> trim(label("Allow all users to transfer repository", boolean())),
"fork" -> trim(label("Allow all users to fork repository", boolean()))
)(RepositoryOperation.apply),
"gravatar" -> trim(label("Gravatar", boolean())),
"notification" -> trim(label("Notification", boolean())),
"limitVisibleRepositories" -> trim(label("limitVisibleRepositories", boolean())),
)(BasicBehavior.apply),
"ssh" -> mapping(
"enabled" -> trim(label("SSH access", boolean())),
"host" -> trim(label("SSH host", optional(text()))),
"port" -> trim(label("SSH port", optional(number())))
"bindAddress" -> mapping(
"host" -> trim(label("Bind SSH host", optional(text()))),
"port" -> trim(label("Bind SSH port", optional(number()))),
)(
(hostOption, portOption) =>
hostOption.map(h => SshAddress(h, portOption.getOrElse(DefaultSshPort), GenericSshUser))
),
"publicAddress" -> mapping(
"host" -> trim(label("Public SSH host", optional(text()))),
"port" -> trim(label("Public SSH port", optional(number()))),
)(
(hostOption, portOption) =>
hostOption.map(h => SshAddress(h, portOption.getOrElse(PublicSshPort), GenericSshUser))
),
)(Ssh.apply),
"useSMTP" -> trim(label("SMTP", boolean())),
"smtp" -> optionalIfNotChecked(
@@ -110,14 +124,15 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
)(Upload.apply),
"repositoryViewer" -> mapping(
"maxFiles" -> trim(label("Max files", number(required)))
)(RepositoryViewerSettings.apply)
)(RepositoryViewerSettings.apply),
"defaultBranch" -> trim(label("Default branch", text(required)))
)(SystemSettings.apply).verifying { settings =>
Vector(
if (settings.ssh.enabled && settings.baseUrl.isEmpty) {
Some("baseUrl" -> "Base URL is required if SSH access is enabled.")
} else None,
if (settings.ssh.enabled && settings.ssh.sshHost.isEmpty) {
Some("sshHost" -> "SSH host is required if SSH access is enabled.")
if (settings.ssh.enabled && settings.ssh.bindAddress.isEmpty) {
Some("ssh.bindAddress.host" -> "SSH bind host is required if SSH access is enabled.")
} else None
).flatten
}
@@ -308,12 +323,13 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
post("/admin/system", form)(adminOnly { form =>
saveSystemSettings(form)
if (form.sshAddress != context.settings.sshAddress) {
if (form.ssh.bindAddress != context.settings.sshBindAddress || form.ssh.publicAddress != context.settings.sshPublicAddress) {
SshServer.stop()
for {
sshAddress <- form.sshAddress
bindAddress <- form.ssh.bindAddress
publicAddress <- form.ssh.publicAddress.orElse(form.ssh.bindAddress)
baseUrl <- form.baseUrl
} SshServer.start(sshAddress, baseUrl)
} SshServer.start(bindAddress, publicAddress, baseUrl)
}
flash.update("info", "System settings has been updated.")
@@ -322,7 +338,12 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
post("/admin/system/sendmail", sendMailForm)(adminOnly { form =>
try {
new Mailer(context.settings.copy(smtp = Some(form.smtp), notification = true)).send(
new Mailer(
context.settings.copy(
smtp = Some(form.smtp),
basicBehavior = context.settings.basicBehavior.copy(notification = true)
)
).send(
to = form.testAddress,
subject = "Test message from GitBucket",
textMsg = "This is a test message from GitBucket.",

View File

@@ -46,7 +46,7 @@ trait WikiControllerBase extends ControllerBase {
)
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))),
"message" -> trim(label("Message", optional(text()))),
"currentPageName" -> trim(label("Current page name", text())),
@@ -54,7 +54,7 @@ trait WikiControllerBase extends ControllerBase {
)(WikiPageEditForm.apply)
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))),
"message" -> trim(label("Message", optional(text()))),
"currentPageName" -> trim(label("Current page name", text(required))),
@@ -62,46 +62,56 @@ trait WikiControllerBase extends ControllerBase {
)(WikiPageEditForm.apply)
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(
"Home",
page,
getWikiPageList(repository.owner, repository.name),
getWikiPageList(repository.owner, repository.name, branch),
repository,
isEditable(repository),
getWikiPage(repository.owner, repository.name, "_Sidebar"),
getWikiPage(repository.owner, repository.name, "_Footer")
getWikiPage(repository.owner, repository.name, "_Sidebar", branch),
getWikiPage(repository.owner, repository.name, "_Footer", branch)
)
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/Home/_edit")
})
get("/:owner/:repository/wiki/:page")(referrersOnly { repository =>
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(
pageName,
page,
getWikiPageList(repository.owner, repository.name),
getWikiPageList(repository.owner, repository.name, branch),
repository,
isEditable(repository),
getWikiPage(repository.owner, repository.name, "_Sidebar"),
getWikiPage(repository.owner, repository.name, "_Footer")
getWikiPage(repository.owner, repository.name, "_Sidebar", branch),
getWikiPage(repository.owner, repository.name, "_Footer", branch)
)
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_edit")
})
get("/:owner/:repository/wiki/:page/_history")(referrersOnly { repository =>
val pageName = StringUtil.urlDecode(params("page"))
val branch = getWikiBranch(repository.owner, repository.name)
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 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 =>
val pageName = StringUtil.urlDecode(params("page"))
val Array(from, to) = params("commitId").split("\\.\\.\\.")
@@ -141,8 +151,9 @@ trait WikiControllerBase extends ControllerBase {
if (isEditable(repository)) {
val pageName = StringUtil.urlDecode(params("page"))
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)}")
} else {
flash.update("info", "This patch was not able to be reversed.")
@@ -159,8 +170,9 @@ trait WikiControllerBase extends ControllerBase {
loginAccount =>
if (isEditable(repository)) {
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")
} else {
flash.update("info", "This patch was not able to be reversed.")
@@ -173,7 +185,9 @@ trait WikiControllerBase extends ControllerBase {
get("/:owner/:repository/wiki/:page/_edit")(readableUsersOnly { repository =>
if (isEditable(repository)) {
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()
})
@@ -280,7 +294,8 @@ trait WikiControllerBase extends ControllerBase {
})
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 =>
@@ -309,13 +324,18 @@ trait WikiControllerBase extends ControllerBase {
value: String,
params: Map[String, Seq[String]],
messages: Messages
): Option[String] =
getWikiPageList(params.value("owner"), params.value("repository"))
): Option[String] = {
val owner = params.value("owner")
val repository = params.value("repository")
val branch = getWikiBranch(owner, repository)
getWikiPageList(owner, repository, branch)
.find(_ == value)
.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] =
if (value.exists("\\/:*?\"<>|".contains(_))) {
Some(s"${name} contains invalid character.")
@@ -326,7 +346,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() {
override def validate(name: String, value: String, messages: Messages): Option[String] = {
@@ -344,7 +364,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 = {
repository.repository.options.wikiOption match {

View File

@@ -1,9 +1,10 @@
package gitbucket.core.controller.api
import gitbucket.core.api.{ApiObject, ApiRef, CreateARef, JsonFormat, UpdateARef}
import gitbucket.core.api.{ApiError, ApiRef, CreateARef, JsonFormat, UpdateARef}
import gitbucket.core.controller.ControllerBase
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.util.Directory.getRepositoryDir
import gitbucket.core.util.ReferrerAuthenticator
import gitbucket.core.util.Implicits._
import gitbucket.core.util.{ReferrerAuthenticator, RepositoryName, WritableUsersAuthenticator}
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.lib.RefUpdate.Result
@@ -14,47 +15,38 @@ import scala.jdk.CollectionConverters._
import scala.util.Using
trait ApiGitReferenceControllerBase extends ControllerBase {
self: ReferrerAuthenticator =>
self: ReferrerAuthenticator with WritableUsersAuthenticator =>
private val logger = LoggerFactory.getLogger(classOf[ApiGitReferenceControllerBase])
get("/api/v3/repos/:owner/:repository/git/refs")(referrersOnly { repository =>
val result = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
val refs = git
.getRepository()
.getRefDatabase()
.getRefsByPrefix("refs")
.asScala
refs.map(ApiRef.fromRef(RepositoryName(s"${repository.owner}/${repository.name}"), _))
}
JsonFormat(result)
})
/*
* i. Get a reference
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#get-a-reference
*/
get("/api/v3/repos/:owner/:repository/git/ref/*")(referrersOnly { repository =>
getRef()
val revstr = multiParams("splat").head
getRef(revstr, repository)
})
// Some versions of GHE support this path
get("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { repository =>
logger.warn("git/refs/ endpoint may not be compatible with GitHub API v3. Consider using git/ref/ endpoint instead")
getRef()
})
private def getRef() = {
val revstr = multiParams("splat").head
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
val ref = git.getRepository().findRef(revstr)
if (ref != null) {
val sha = ref.getObjectId().name()
JsonFormat(ApiRef(revstr, ApiObject(sha)))
} else {
val refs = git
.getRepository()
.getRefDatabase()
.getRefsByPrefix("refs/")
.asScala
JsonFormat(refs.map { ref =>
val sha = ref.getObjectId().name()
ApiRef(revstr, ApiObject(sha))
})
}
}
}
getRef(revstr, repository)
})
/*
* ii. Get all references
@@ -65,22 +57,27 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
* iii. Create a reference
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#create-a-reference
*/
post("/api/v3/repos/:owner/:repository/git/refs")(referrersOnly { _ =>
post("/api/v3/repos/:owner/:repository/git/refs")(writableUsersOnly { repository =>
extractFromJsonBody[CreateARef].map {
data =>
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
val ref = git.getRepository.findRef(data.ref)
if (ref == null) {
val update = git.getRepository.updateRef(data.ref)
update.setNewObjectId(ObjectId.fromString(data.sha))
val result = update.update()
result match {
case Result.NEW => JsonFormat(ApiRef(update.getName, ApiObject(update.getNewObjectId.getName)))
case _ => UnprocessableEntity(result.name())
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
git =>
val ref = git.getRepository.findRef(data.ref)
if (ref == null) {
val update = git.getRepository.updateRef(data.ref)
update.setNewObjectId(ObjectId.fromString(data.sha))
val result = update.update()
result match {
case Result.NEW =>
JsonFormat(
ApiRef
.fromRef(RepositoryName(repository.owner, repository.name), git.getRepository.findRef(data.ref))
)
case _ => UnprocessableEntity(result.name())
}
} else {
UnprocessableEntity("Ref already exists.")
}
} else {
UnprocessableEntity("Ref already exists.")
}
}
} getOrElse BadRequest()
})
@@ -89,11 +86,11 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
* iv. Update a reference
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#update-a-reference
*/
patch("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { _ =>
patch("/api/v3/repos/:owner/:repository/git/refs/*")(writableUsersOnly { repository =>
val refName = multiParams("splat").mkString("/")
extractFromJsonBody[UpdateARef].map {
data =>
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
val ref = git.getRepository.findRef(refName)
if (ref == null) {
UnprocessableEntity("Ref does not exist.")
@@ -104,7 +101,7 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
val result = update.update()
result match {
case Result.FORCED | Result.FAST_FORWARD | Result.NO_CHANGE =>
JsonFormat(ApiRef(update.getName, ApiObject(update.getNewObjectId.getName)))
JsonFormat(ApiRef.fromRef(RepositoryName(repository), git.getRepository.findRef(refName)))
case _ => UnprocessableEntity(result.name())
}
}
@@ -116,7 +113,7 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
* v. Delete a reference
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#delete-a-reference
*/
delete("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { _ =>
delete("/api/v3/repos/:owner/:repository/git/refs/*")(writableUsersOnly { _ =>
val refName = multiParams("splat").mkString("/")
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
val ref = git.getRepository.findRef(refName)
@@ -133,4 +130,34 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
}
}
})
private def notFound(): ApiError = {
response.setStatus(404)
ApiError("Not Found")
}
protected def getRef(revstr: String, repository: RepositoryInfo): AnyRef = {
logger.debug(s"getRef: path '${revstr}'")
val name = RepositoryName(repository)
val result = JsonFormat(revstr match {
case "tags" => repository.tags.map(ApiRef.fromTag(name, _))
case x if x.startsWith("tags/") =>
val tagName = x.substring("tags/".length)
repository.tags.find(_.name == tagName) match {
case Some(tagInfo) => ApiRef.fromTag(name, tagInfo)
case None => notFound()
}
case other =>
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
git.getRepository().findRef(other) match {
case null => notFound()
case ref => ApiRef.fromRef(name, ref)
}
}
})
logger.debug(s"json result: $result")
result
}
}

View File

@@ -85,7 +85,7 @@ trait ApiIssueCommentControllerBase extends ControllerBase {
* iv. Delete a 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]]] =
for {
commentId <- params("id").toIntOpt

View File

@@ -29,9 +29,9 @@ trait ApiIssueControllerBase extends ControllerBase {
val page = IssueSearchCondition.page(request)
// TODO: more api spec condition
val condition = IssueSearchCondition(request)
val baseOwner = getAccountByUserName(repository.owner).get
//val baseOwner = getAccountByUserName(repository.owner).get
val issues: List[(Issue, Account, Option[Account])] =
val issues: List[(Issue, Account, List[Account])] =
searchIssueByApi(
condition = condition,
offset = (page - 1) * PullRequestLimit,
@@ -40,12 +40,12 @@ trait ApiIssueControllerBase extends ControllerBase {
)
JsonFormat(issues.map {
case (issue, issueUser, assignedUser) =>
case (issue, issueUser, assigneeUsers) =>
ApiIssue(
issue = issue,
repositoryName = RepositoryName(repository),
user = ApiUser(issueUser),
assignee = assignedUser.map(ApiUser(_)),
assignees = assigneeUsers.map(ApiUser(_)),
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
.map(ApiLabel(_, RepositoryName(repository))),
issue.milestoneId.flatMap { getApiMilestone(repository, _) }
@@ -61,7 +61,8 @@ trait ApiIssueControllerBase extends ControllerBase {
(for {
issueId <- params("id").toIntOpt
issue <- getIssue(repository.owner, repository.name, issueId.toString)
users = getAccountsByUserNames(Set(issue.openedUserName) ++ issue.assignedUserName, Set())
assigneeUsers = getIssueAssignees(repository.owner, repository.name, issueId)
users = getAccountsByUserNames(Set(issue.openedUserName) ++ assigneeUsers.map(_.assigneeUserName), Set())
openedUser <- users.get(issue.openedUserName)
} yield {
JsonFormat(
@@ -69,7 +70,7 @@ trait ApiIssueControllerBase extends ControllerBase {
issue,
RepositoryName(repository),
ApiUser(openedUser),
issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)),
assigneeUsers.flatMap(x => users.get(x.assigneeUserName)).map(ApiUser(_)),
getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository))),
issue.milestoneId.flatMap { getApiMilestone(repository, _) }
)
@@ -92,7 +93,7 @@ trait ApiIssueControllerBase extends ControllerBase {
repository,
data.title,
data.body,
data.assignees.headOption,
data.assignees,
milestone.map(_.milestoneId),
None,
data.labels,
@@ -103,7 +104,9 @@ trait ApiIssueControllerBase extends ControllerBase {
issue,
RepositoryName(repository),
ApiUser(loginAccount),
issue.assignedUserName.flatMap(getAccountByUserName(_)).map(ApiUser(_)),
getIssueAssignees(repository.owner, repository.name, issue.issueId)
.flatMap(x => getAccountByUserName(x.assigneeUserName, false))
.map(ApiUser.apply),
getIssueLabels(repository.owner, repository.name, issue.issueId)
.map(ApiLabel(_, RepositoryName(repository))),
issue.milestoneId.flatMap { getApiMilestone(repository, _) }

View File

@@ -1,5 +1,5 @@
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.service._
import gitbucket.core.util.Implicits._
@@ -121,10 +121,10 @@ trait ApiIssueLabelControllerBase extends ControllerBase {
*/
post("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
JsonFormat(for {
data <- extractFromJsonBody[Seq[String]]
data <- extractFromJsonBody[AddLabelsToAnIssue]
issueId <- params("id").toIntOpt
} yield {
data.map { labelName =>
data.labels.map { labelName =>
val label = getLabel(repository.owner, repository.name, labelName).getOrElse(
getLabel(
repository.owner,
@@ -160,11 +160,11 @@ trait ApiIssueLabelControllerBase extends ControllerBase {
*/
put("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
JsonFormat(for {
data <- extractFromJsonBody[Seq[String]]
data <- extractFromJsonBody[AddLabelsToAnIssue]
issueId <- params("id").toIntOpt
} yield {
deleteAllIssueLabels(repository.owner, repository.name, issueId, true)
data.map { labelName =>
data.labels.map { labelName =>
val label = getLabel(repository.owner, repository.name, labelName).getOrElse(
getLabel(
repository.owner,

View File

@@ -2,7 +2,6 @@ package gitbucket.core.controller.api
import gitbucket.core.api._
import gitbucket.core.controller.ControllerBase
import gitbucket.core.service.MilestonesService
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
import gitbucket.core.util.Implicits._
import org.scalatra.NoContent

View File

@@ -40,7 +40,7 @@ trait ApiPullRequestControllerBase extends ControllerBase {
val condition = IssueSearchCondition(request)
val baseOwner = getAccountByUserName(repository.owner).get
val issues: List[(Issue, Account, Int, PullRequest, Repository, Account, Option[Account])] =
val issues: List[(Issue, Account, Int, PullRequest, Repository, Account, List[Account])] =
searchPullRequestByApi(
condition = condition,
offset = (page - 1) * PullRequestLimit,
@@ -49,7 +49,7 @@ trait ApiPullRequestControllerBase extends ControllerBase {
)
JsonFormat(issues.map {
case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner, assignee) =>
case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner, assignees) =>
ApiPullRequest(
issue = issue,
pullRequest = pullRequest,
@@ -58,7 +58,7 @@ trait ApiPullRequestControllerBase extends ControllerBase {
user = ApiUser(issueUser),
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
.map(ApiLabel(_, RepositoryName(repository))),
assignee = assignee.map(ApiUser.apply),
assignees = assignees.map(ApiUser.apply),
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
)
})
@@ -99,7 +99,6 @@ trait ApiPullRequestControllerBase extends ControllerBase {
loginUser = context.loginAccount.get.userName,
title = createPullReq.title,
content = createPullReq.body,
assignedUserName = None,
milestoneId = None,
priorityId = None,
isPullRequest = true
@@ -319,8 +318,8 @@ trait ApiPullRequestControllerBase extends ControllerBase {
baseOwner <- users.get(repository.owner)
headOwner <- users.get(pullRequest.requestUserName)
issueUser <- users.get(issue.openedUserName)
assignee = issue.assignedUserName.flatMap { userName =>
getAccountByUserName(userName, false)
assignees = getIssueAssignees(repository.owner, repository.name, issueId).flatMap { assignedUser =>
getAccountByUserName(assignedUser.assigneeUserName, false)
}
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
} yield {
@@ -332,7 +331,7 @@ trait ApiPullRequestControllerBase extends ControllerBase {
user = ApiUser(issueUser),
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
.map(ApiLabel(_, RepositoryName(repository))),
assignee = assignee.map(ApiUser.apply),
assignees = assignees.map(ApiUser.apply),
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
)
}

View File

@@ -9,9 +9,23 @@ import gitbucket.core.util.JGitUtil.{CommitInfo, getBranches, getBranchesOfCommi
import gitbucket.core.util.{JGitUtil, ReferrerAuthenticator, RepositoryName}
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.revwalk.RevWalk
import org.eclipse.jgit.revwalk.filter.{
AndRevFilter,
AuthorRevFilter,
CommitTimeRevFilter,
MaxCountRevFilter,
RevFilter,
SkipRevFilter
}
import org.eclipse.jgit.treewalk.filter.{AndTreeFilter, PathFilterGroup, TreeFilter}
import scala.collection.mutable.ListBuffer
import scala.jdk.CollectionConverters._
import scala.util.Using
import math.min
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter._
import java.util.Date
import java.time.ZoneOffset
trait ApiRepositoryCommitControllerBase extends ControllerBase {
self: AccountService with CommitsService with ProtectedBranchService with ReferrerAuthenticator =>
@@ -22,8 +36,13 @@ trait ApiRepositoryCommitControllerBase extends ControllerBase {
get("/api/v3/repos/:owner/:repository/commits")(referrersOnly { repository =>
val owner = repository.owner
val name = repository.name
// TODO: The following parameters need to be implemented. [:path, :author, :since, :until]
val sha = params.getOrElse("sha", "refs/heads/master")
val sha = params.get("sha").filter(_.nonEmpty).getOrElse("HEAD")
val page = params.get("page").filter(_.nonEmpty).getOrElse("1").toInt
val per_page = min(params.get("per_page").filter(_.nonEmpty).getOrElse("30").toInt, 100)
val author = params.get("author").filter(_.nonEmpty)
val path = params.get("path").filter(_.nonEmpty)
val since = params.get("since").filter(_.nonEmpty)
val until = params.get("until").filter(_.nonEmpty)
Using.resource(Git.open(getRepositoryDir(owner, name))) {
git =>
val repo = git.getRepository
@@ -31,7 +50,37 @@ trait ApiRepositoryCommitControllerBase extends ControllerBase {
revWalk =>
val objectId = repo.resolve(sha)
revWalk.markStart(revWalk.parseCommit(objectId))
JsonFormat(revWalk.asScala.take(30).map {
if (path.nonEmpty) {
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(

View File

@@ -157,7 +157,7 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
Some("https://docs.github.com/en/rest/reference/repos#create-or-update-file-contents")
)
case _ =>
val (commitId, blobId) = commitFile(
commitFile(
repository,
branch,
path,
@@ -170,12 +170,12 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
data.committer.map(_.name).getOrElse(loginAccount.fullName),
data.committer.map(_.email).getOrElse(loginAccount.mailAddress),
context.settings
)
blobId match {
case None =>
) match {
case Left(error) =>
ApiError(s"Failed to commit a file: ${error}", None)
case Right((_, None)) =>
ApiError("Failed to commit a file.", None)
case Some(blobId) =>
case Right((commitId, Some(blobId))) =>
Map(
"content" -> ApiContents(
"file",

View File

@@ -16,6 +16,7 @@ import scala.util.Using
trait ApiRepositoryControllerBase extends ControllerBase {
self: RepositoryService
with ApiGitReferenceControllerBase
with RepositoryCreationService
with AccountService
with OwnerAuthenticator
@@ -92,7 +93,8 @@ trait ApiRepositoryControllerBase extends ControllerBase {
data.name,
data.description,
data.`private`,
data.auto_init
data.auto_init,
context.settings.defaultBranch
)
Await.result(f, Duration.Inf)
@@ -129,7 +131,8 @@ trait ApiRepositoryControllerBase extends ControllerBase {
data.name,
data.description,
data.`private`,
data.auto_init
data.auto_init,
context.settings.defaultBranch
)
Await.result(f, Duration.Inf)
val repository = Database() withTransaction { session =>
@@ -184,9 +187,11 @@ trait ApiRepositoryControllerBase extends ControllerBase {
* https://docs.github.com/en/rest/reference/repos#list-repository-tags
*/
get("/api/v3/repos/:owner/:repository/tags")(referrersOnly { repository =>
JsonFormat(
repository.tags.map(tagInfo => ApiTag(tagInfo.name, RepositoryName(repository), tagInfo.id))
)
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
JsonFormat(
self.getRef("tags", repository)
)
}
})
/*

View File

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

View File

@@ -35,7 +35,7 @@ trait AccountComponent { self: Profile =>
groupAccount,
removed,
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 extraMailAddress = column[String]("EXTRA_MAIL_ADDRESS", O PrimaryKey)
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 subject = column[String]("SUBJECT")
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] =
(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 highlighterTheme = column[String]("HIGHLIGHTER_THEME")
def * =
(userName, highlighterTheme).<>(AccountPreference.tupled, AccountPreference.unapply)
(userName, highlighterTheme).mapTo[AccountPreference]
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 token = column[Option[String]]("TOKEN")
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)
}

View File

@@ -12,7 +12,7 @@ trait AccountWebHookEventComponent extends TemplateComponent { self: Profile =>
val url = column[String]("URL")
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)

View File

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

View File

@@ -8,7 +8,7 @@ trait CollaboratorComponent extends TemplateComponent { self: Profile =>
class Collaborators(tag: Tag) extends Table[Collaborator](tag, "COLLABORATOR") with BasicTemplate {
val collaboratorName = column[String]("COLLABORATOR_NAME")
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) =
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")
def * =
(userName, repositoryName, issueId, commentId, action, commentedUserName, content, registeredDate, updatedDate)
.<>(IssueComment.tupled, IssueComment.unapply)
.mapTo[IssueComment]
def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind
}
@@ -75,7 +75,7 @@ trait CommitCommentComponent extends TemplateComponent { self: Profile =>
originalCommitId,
originalOldLine,
originalNewLine
).<>(CommitComment.tupled, CommitComment.unapply)
).mapTo[CommitComment]
def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind
}

View File

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

View File

@@ -0,0 +1,379 @@
package gitbucket.core.model
import gitbucket.core.controller.Context
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.util.StringUtil
import gitbucket.core.view.helpers
import org.scalatra.i18n.Messages
import play.twirl.api.Html
trait CustomFieldComponent extends TemplateComponent { self: Profile =>
import profile.api._
lazy val CustomFields = TableQuery[CustomFields]
class CustomFields(tag: Tag) extends Table[CustomField](tag, "CUSTOM_FIELD") with BasicTemplate {
val fieldId = column[Int]("FIELD_ID", O AutoInc)
val fieldName = column[String]("FIELD_NAME")
val fieldType = column[String]("FIELD_TYPE")
val constraints = column[Option[String]]("CONSTRAINTS")
val enableForIssues = column[Boolean]("ENABLE_FOR_ISSUES")
val enableForPullRequests = column[Boolean]("ENABLE_FOR_PULL_REQUESTS")
def * =
(userName, repositoryName, fieldId, fieldName, fieldType, constraints, enableForIssues, enableForPullRequests)
.mapTo[CustomField]
def byPrimaryKey(userName: String, repositoryName: String, fieldId: Int) =
(this.userName === userName.bind) && (this.repositoryName === repositoryName.bind) && (this.fieldId === fieldId.bind)
}
}
case class CustomField(
userName: String,
repositoryName: String,
fieldId: Int = 0,
fieldName: String,
fieldType: String, // long, double, string, date, or enum
constraints: Option[String],
enableForIssues: Boolean,
enableForPullRequests: Boolean
)
trait CustomFieldBehavior {
def createHtml(repository: RepositoryInfo, fieldId: Int, fieldName: String, constraints: Option[String])(
implicit context: Context
): 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 {
def validate(field: CustomField, value: String, messages: Messages): Option[String] = {
if (value.isEmpty) None
else {
CustomFieldBehavior(field.fieldType).flatMap { behavior =>
behavior.validate(field.fieldName, field.constraints, value, messages)
}
}
}
def apply(fieldType: String): Option[CustomFieldBehavior] = {
fieldType match {
case "long" => Some(LongFieldBehavior)
case "double" => Some(DoubleFieldBehavior)
case "string" => Some(StringFieldBehavior)
case "date" => Some(DateFieldBehavior)
case "enum" => Some(EnumFieldBehavior)
case _ => None
}
}
case object LongFieldBehavior extends TextFieldBehavior {
override def validate(
name: String,
constraints: Option[String],
value: String,
messages: Messages
): Option[String] = {
try {
value.toLong
None
} catch {
case _: NumberFormatException => Some(messages("error.number").format(name))
}
}
}
case object DoubleFieldBehavior extends TextFieldBehavior {
override def validate(
name: String,
constraints: Option[String],
value: String,
messages: Messages
): Option[String] = {
try {
value.toDouble
None
} catch {
case _: NumberFormatException => Some(messages("error.number").format(name))
}
}
}
case object StringFieldBehavior extends TextFieldBehavior
case object DateFieldBehavior extends TextFieldBehavior {
private val pattern = "yyyy-MM-dd"
override protected val fieldType: String = "date"
override def validate(
name: String,
constraints: Option[String],
value: String,
messages: Messages
): Option[String] = {
try {
new java.text.SimpleDateFormat(pattern).parse(value)
None
} catch {
case _: java.text.ParseException =>
Some(messages("error.datePattern").format(name, pattern))
}
}
}
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 {
protected val fieldType = "text"
override def createHtml(repository: RepositoryInfo, fieldId: Int, fieldName: String, constraints: Option[String])(
implicit context: Context
): String = {
val sb = new StringBuilder
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;"/>"""
)
sb.append(s"""<script>
|$$('#custom-field-$fieldId').focusout(function(){
| const $$this = $$(this);
| $$.post('${helpers.url(repository)}/issues/customfield_validation/$fieldId',
| { value: $$this.val() },
| function(data){
| if (data != '') {
| $$('#custom-field-$fieldId-error').text(data);
| } else {
| $$('#custom-field-$fieldId-error').text('');
| }
| }
| );
|});
|</script>
|""".stripMargin)
sb.toString()
}
override def fieldHtml(
repository: RepositoryInfo,
issueId: Int,
fieldId: Int,
fieldName: String,
constraints: Option[String],
value: String,
editable: Boolean
)(
implicit context: Context
): String = {
val sb = new StringBuilder
if (value.nonEmpty) {
sb.append(
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) {
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;"/>"""
)
sb.append(s"""<script>
|$$('#custom-field-$fieldId-label').click(function(){
| const $$this = $$(this);
| $$this.hide();
| $$this.next().val($$this.text()).show().focus();
|});
|
|$$('#custom-field-$fieldId-editor').focusout(function(){
| const $$this = $$(this);
| $$.post('${helpers.url(repository)}/issues/customfield_validation/$fieldId',
| { value: $$this.val() },
| function(data){
| if (data != '') {
| $$('#custom-field-$fieldId-error').text(data);
| } else {
| $$('#custom-field-$fieldId-error').text('');
| $$.post('${helpers.url(repository)}/issues/$issueId/customfield/$fieldId',
| { value: $$this.val() },
| function(data){
| $$this.hide();
| if (data == '') {
| $$this.prev().html('<i class="octicon octicon-pencil" style="cursor: pointer;">').show();
| } else {
| $$this.prev().text(data).show();
| }
| }
| );
| }
| }
| );
|});
|
|// ESC key handling in text field
|$$('#custom-field-$fieldId-editor').keyup(function(e){
| if (e.keyCode == 27) {
| const $$this = $$(this);
| $$this.hide();
| $$this.prev().show();
| }
| if (e.keyCode == 13) {
| $$('#custom-field-$fieldId-editor').blur();
| }
|});
|</script>
|""".stripMargin)
}
sb.toString()
}
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 allowWrite = column[Boolean]("ALLOW_WRITE")
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) =
(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 title = column[String]("TITLE")
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) =
(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 userName = column[String]("USER_NAME", O PrimaryKey)
val isManager = column[Boolean]("MANAGER")
def * = (groupName, userName, isManager).<>(GroupMember.tupled, GroupMember.unapply)
def * = (groupName, userName, isManager).mapTo[GroupMember]
}
}

View File

@@ -27,7 +27,6 @@ trait IssueComponent extends TemplateComponent { self: Profile =>
with MilestoneTemplate
with PriorityTemplate {
val openedUserName = column[String]("OPENED_USER_NAME")
val assignedUserName = column[String]("ASSIGNED_USER_NAME")
val title = column[String]("TITLE")
val content = column[String]("CONTENT")
val closed = column[Boolean]("CLOSED")
@@ -42,14 +41,13 @@ trait IssueComponent extends TemplateComponent { self: Profile =>
openedUserName,
milestoneId.?,
priorityId.?,
assignedUserName.?,
title,
content.?,
closed,
registeredDate,
updatedDate,
pullRequest
).<>(Issue.tupled, Issue.unapply)
).mapTo[Issue]
def byPrimaryKey(owner: String, repository: String, issueId: Int) = byIssue(owner, repository, issueId)
}
@@ -62,7 +60,6 @@ case class Issue(
openedUserName: String,
milestoneId: Option[Int],
priorityId: Option[Int],
assignedUserName: Option[String],
title: String,
content: Option[String],
closed: Boolean,

View File

@@ -0,0 +1,25 @@
package gitbucket.core.model
trait IssueAssigneeComponent extends TemplateComponent { self: Profile =>
import profile.api._
import self._
lazy val IssueAssignees = TableQuery[IssueAssignees]
class IssueAssignees(tag: Tag) extends Table[IssueAssignee](tag, "ISSUE_ASSIGNEE") with IssueTemplate {
val assigneeUserName = column[String]("ASSIGNEE_USER_NAME")
def * =
(userName, repositoryName, issueId, assigneeUserName).mapTo[IssueAssignee]
def byPrimaryKey(owner: String, repository: String, issueId: Int, assigneeUserName: String) = {
byIssue(owner, repository, issueId) && this.assigneeUserName === assigneeUserName.bind
}
}
}
case class IssueAssignee(
userName: String,
repositoryName: String,
issueId: Int,
assigneeUserName: String
)

View File

@@ -0,0 +1,30 @@
package gitbucket.core.model
trait IssueCustomFieldComponent extends TemplateComponent { self: Profile =>
import profile.api._
import self._
lazy val IssueCustomFields = TableQuery[IssueCustomFields]
class IssueCustomFields(tag: Tag) extends Table[IssueCustomField](tag, "ISSUE_CUSTOM_FIELD") {
val userName = column[String]("USER_NAME", O.PrimaryKey)
val repositoryName = column[String]("REPOSITORY_NAME", O.PrimaryKey)
val issueId = column[Int]("ISSUE_ID", O.PrimaryKey)
val fieldId = column[Int]("FIELD_ID", O.PrimaryKey)
val value = column[String]("VALUE")
def * =
(userName, repositoryName, issueId, fieldId, value).mapTo[IssueCustomField]
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
}
}
}
case class IssueCustomField(
userName: String,
repositoryName: String,
issueId: Int,
fieldId: Int,
value: String
)

View File

@@ -6,7 +6,7 @@ trait IssueLabelComponent extends TemplateComponent { self: Profile =>
lazy val IssueLabels = TableQuery[IssueLabels]
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) =
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 labelName = column[String]("LABEL_NAME")
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(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 closedDate = column[Option[java.util.Date]]("CLOSED_DATE")
def * =
(userName, repositoryName, milestoneId, title, description, dueDate, closedDate)
.<>(Milestone.tupled, Milestone.unapply)
(userName, repositoryName, milestoneId, title, description, dueDate, closedDate).mapTo[Milestone]
def byPrimaryKey(owner: String, repository: String, milestoneId: Int) = byMilestone(owner, repository, milestoneId)
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 color = column[String]("COLOR")
def * =
(userName, repositoryName, priorityId, priorityName, description.?, isDefault, ordering, color)
.<>(Priority.tupled, Priority.unapply)
(userName, repositoryName, priorityId, priorityName, description.?, isDefault, ordering, color).mapTo[Priority]
def byPrimaryKey(owner: String, repository: String, priorityId: Int) = byPriority(owner, repository, priorityId)
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], priorityId: Rep[Int]) =

View File

@@ -73,5 +73,8 @@ trait CoreProfile
with ReleaseAssetComponent
with AccountExtraMailAddressComponent
with AccountPreferenceComponent
with CustomFieldComponent
with IssueCustomFieldComponent
with IssueAssigneeComponent
object Profile extends CoreProfile

View File

@@ -7,7 +7,7 @@ trait ProtectedBranchComponent extends TemplateComponent { self: Profile =>
lazy val ProtectedBranches = TableQuery[ProtectedBranches]
class ProtectedBranches(tag: Tag) extends Table[ProtectedBranch](tag, "PROTECTED_BRANCH") with BranchTemplate {
val statusCheckAdmin = column[Boolean]("STATUS_CHECK_ADMIN")
def * = (userName, repositoryName, branch, statusCheckAdmin).<>(ProtectedBranch.tupled, ProtectedBranch.unapply)
def * = (userName, repositoryName, branch, statusCheckAdmin).mapTo[ProtectedBranch]
def byPrimaryKey(userName: String, repositoryName: String, branch: String) =
byBranch(userName, repositoryName, branch)
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], branch: Rep[String]) =
@@ -20,7 +20,7 @@ trait ProtectedBranchComponent extends TemplateComponent { self: Profile =>
with BranchTemplate {
val context = column[String]("CONTEXT")
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,
commitIdTo,
isDraft
).<>(PullRequest.tupled, PullRequest.unapply)
).mapTo[PullRequest]
def byPrimaryKey(userName: String, repositoryName: String, issueId: Int) =
byIssue(userName, repositoryName, issueId)

View File

@@ -20,7 +20,7 @@ trait ReleaseAssetComponent extends TemplateComponent { self: Profile =>
def * =
(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) =
byTag(owner, repository, tag) && (this.fileName === fileName.bind)
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")
def * =
(userName, repositoryName, name, tag, author, content, registeredDate, updatedDate)
.<>(ReleaseTag.tupled, ReleaseTag.unapply)
(userName, repositoryName, name, tag, author, content, registeredDate, updatedDate).mapTo[ReleaseTag]
def byPrimaryKey(owner: String, repository: String, tag: String) = byTag(owner, repository, tag)
def byTag(owner: String, repository: String, tag: String) =
byRepository(owner, repository) && (this.tag === tag.bind)

View File

@@ -14,8 +14,7 @@ trait RepositoryWebHookComponent extends TemplateComponent { self: Profile =>
val token = column[Option[String]]("TOKEN")
val ctype = column[WebHookContentType]("CTYPE")
def * =
(userName, repositoryName, hookId, url, ctype, token)
.<>((RepositoryWebHook.apply _).tupled, RepositoryWebHook.unapply)
(userName, repositoryName, hookId, url, ctype, token).mapTo[RepositoryWebHook]
def byRepositoryUrl(owner: String, repository: String, url: String) =
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 event = column[WebHook.Event]("EVENT")
def * =
(userName, repositoryName, url, event).<>((RepositoryWebHookEvent.apply _).tupled, RepositoryWebHookEvent.unapply)
(userName, repositoryName, url, event).mapTo[RepositoryWebHookEvent]
def byRepositoryWebHook(owner: String, repository: String, url: String) =
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 title = column[String]("TITLE")
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) =
(this.userName === userName.bind) && (this.sshKeyId === sshKeyId.bind)

View File

@@ -1,14 +1,15 @@
package gitbucket.core.plugin
import javax.servlet.ServletContext
import gitbucket.core.controller.{Context, ControllerBase}
import gitbucket.core.model.{Account, Issue}
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.service.SystemSettingsService.SystemSettings
import io.github.gitbucket.solidbase.model.Version
import org.apache.sshd.server.channel.ChannelSession
import org.apache.sshd.server.command.Command
import play.twirl.api.Html
import scala.util.Using
/**
@@ -323,7 +324,7 @@ abstract class Plugin {
/**
* Override to add ssh command providers.
*/
val sshCommandProviders: Seq[PartialFunction[String, Command]] = Nil
val sshCommandProviders: Seq[PartialFunction[String, ChannelSession => Command]] = Nil
/**
* Override to add ssh command providers.
@@ -332,7 +333,7 @@ abstract class Plugin {
registry: PluginRegistry,
context: ServletContext,
settings: SystemSettings
): Seq[PartialFunction[String, Command]] = Nil
): Seq[PartialFunction[String, ChannelSession => Command]] = Nil
/**
* This method is invoked in initialization of plugin system.

View File

@@ -6,7 +6,6 @@ import java.nio.file.{Files, Paths, StandardWatchEventKinds}
import java.util.Base64
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.ConcurrentHashMap
import javax.servlet.ServletContext
import com.github.zafarkhaja.semver.Version
import gitbucket.core.controller.{Context, ControllerBase}
@@ -21,6 +20,7 @@ import io.github.gitbucket.solidbase.Solidbase
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
import io.github.gitbucket.solidbase.model.Module
import org.apache.commons.io.FileUtils
import org.apache.sshd.server.channel.ChannelSession
import org.apache.sshd.server.command.Command
import org.slf4j.LoggerFactory
import play.twirl.api.Html
@@ -58,7 +58,7 @@ class PluginRegistry {
private val suggestionProviders = new ConcurrentLinkedQueue[SuggestionProvider]
suggestionProviders.add(new UserNameSuggestionProvider())
suggestionProviders.add(new IssueSuggestionProvider())
private val sshCommandProviders = new ConcurrentLinkedQueue[PartialFunction[String, Command]]()
private val sshCommandProviders = new ConcurrentLinkedQueue[PartialFunction[String, ChannelSession => Command]]()
def addPlugin(pluginInfo: PluginInfo): Unit = plugins.add(pluginInfo)
@@ -177,10 +177,11 @@ class PluginRegistry {
def getSuggestionProviders: Seq[SuggestionProvider] = suggestionProviders.asScala.toSeq
def addSshCommandProvider(sshCommandProvider: PartialFunction[String, Command]): Unit =
def addSshCommandProvider(sshCommandProvider: PartialFunction[String, ChannelSession => Command]): Unit =
sshCommandProviders.add(sshCommandProvider)
def getSshCommandProviders: Seq[PartialFunction[String, Command]] = sshCommandProviders.asScala.toSeq
def getSshCommandProviders: Seq[PartialFunction[String, ChannelSession => Command]] =
sshCommandProviders.asScala.toSeq
}
/**

View File

@@ -7,9 +7,14 @@ import gitbucket.core.model.Profile.profile.blockingApi._
import gitbucket.core.model.Profile.dateColumnType
import gitbucket.core.util.{LDAPUtil, StringUtil}
import StringUtil._
import com.nimbusds.jose.{JWSAlgorithm, JWSHeader}
import com.nimbusds.jose.crypto.{MACSigner, MACVerifier}
import com.nimbusds.jwt.{JWTClaimsSet, SignedJWT}
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service.SystemSettingsService.SystemSettings
import java.security.SecureRandom
trait AccountService {
private val logger = LoggerFactory.getLogger(classOf[AccountService])
@@ -60,28 +65,28 @@ trait AccountService {
case Right(ldapUserInfo) => {
// Create or update account by LDAP information
getAccountByUserName(ldapUserInfo.userName, true) match {
case Some(x) if (!x.isRemoved) => {
if (settings.ldap.get.mailAttribute.getOrElse("").isEmpty) {
updateAccount(x.copy(fullName = ldapUserInfo.fullName))
case Some(x) =>
if (x.isRemoved) {
logger.info("LDAP Authentication Failed: Account is already registered but disabled.")
defaultAuthentication(userName, password)
} else {
updateAccount(x.copy(mailAddress = ldapUserInfo.mailAddress, fullName = ldapUserInfo.fullName))
if (settings.ldap.get.mailAttribute.getOrElse("").isEmpty) {
updateAccount(x.copy(fullName = ldapUserInfo.fullName))
} else {
updateAccount(x.copy(mailAddress = ldapUserInfo.mailAddress, fullName = ldapUserInfo.fullName))
}
getAccountByUserName(ldapUserInfo.userName)
}
getAccountByUserName(ldapUserInfo.userName)
}
case Some(x) if (x.isRemoved) => {
logger.info("LDAP Authentication Failed: Account is already registered but disabled.")
defaultAuthentication(userName, password)
}
case None =>
getAccountByMailAddress(ldapUserInfo.mailAddress, true) match {
case Some(x) if (!x.isRemoved) => {
updateAccount(x.copy(fullName = ldapUserInfo.fullName))
getAccountByUserName(ldapUserInfo.userName)
}
case Some(x) if (x.isRemoved) => {
logger.info("LDAP Authentication Failed: Account is already registered but disabled.")
defaultAuthentication(userName, password)
}
case Some(x) =>
if (x.isRemoved) {
logger.info("LDAP Authentication Failed: Account is already registered but disabled.")
defaultAuthentication(userName, password)
} else {
updateAccount(x.copy(fullName = ldapUserInfo.fullName))
getAccountByUserName(ldapUserInfo.userName)
}
case None => {
createAccount(
ldapUserInfo.userName,
@@ -337,6 +342,33 @@ trait AccountService {
}
}
def generateResetPasswordToken(mailAddress: String): String = {
val claimsSet = new JWTClaimsSet.Builder()
.claim("mailAddress", mailAddress)
.expirationTime(new java.util.Date(System.currentTimeMillis() + 10 * 1000))
.build()
val signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), claimsSet)
signedJWT.sign(new MACSigner(AccountService.jwtSecretKey))
signedJWT.serialize()
}
def decodeResetPasswordToken(token: String): Option[String] = {
try {
val signedJWT = SignedJWT.parse(token)
val verifier = new MACVerifier(AccountService.jwtSecretKey)
if (signedJWT.verify(verifier) && new java.util.Date().before(signedJWT.getJWTClaimsSet().getExpirationTime())) {
Some(signedJWT.getPayload.toJSONObject.get("mailAddress").toString)
} else None
} catch {
case _: Exception => None
}
}
}
object AccountService extends AccountService
object AccountService extends AccountService {
// 256-bit key for HS256 which must be pre-shared
val jwtSecretKey = new Array[Byte](32)
new SecureRandom().nextBytes(jwtSecretKey)
}

View File

@@ -9,9 +9,10 @@ import org.json4s.jackson.Serialization.{read, write}
import scala.util.Using
import java.io.FileOutputStream
import java.nio.charset.StandardCharsets
import gitbucket.core.controller.Context
import gitbucket.core.util.ConfigUtil
import org.apache.commons.io.input.ReversedLinesFileReader
import ActivityService._
import scala.collection.mutable.ListBuffer
@@ -26,40 +27,52 @@ trait ActivityService {
}
}
def getActivitiesByUser(activityUserName: String, isPublic: Boolean)(implicit context: Context): List[Activity] = {
if (!ActivityLog.exists()) {
List.empty
} else {
val list = new ListBuffer[Activity]
Using.resource(new ReversedLinesFileReader(ActivityLog, StandardCharsets.UTF_8)) { reader =>
var json: String = null
while (list.length < 50 && { json = reader.readLine(); json } != null) {
val activity = read[Activity](json)
if (activity.activityUserName == activityUserName) {
if (isPublic == false) {
list += activity
} else {
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) {
list += activity
}
}
}
}
}
list.toList
def getActivitiesByUser(activityUserName: String, publicOnly: Boolean)(implicit context: Context): List[Activity] = {
getActivities(includePublic = false) { activity =>
if (activity.activityUserName == activityUserName) {
!publicOnly || isPublicActivity(activity)
} else false
}
}
def getRecentPublicActivities()(implicit context: Context): List[Activity] = {
if (!ActivityLog.exists()) {
getActivities(includePublic = true) { _ =>
false
}
}
def getRecentActivitiesByRepos(repos: Set[(String, String)])(implicit context: Context): List[Activity] = {
getActivities(includePublic = true) { activity =>
repos.exists {
case (userName, repositoryName) =>
activity.userName == userName && activity.repositoryName == repositoryName
}
}
}
private def getActivities(
includePublic: Boolean
)(filter: Activity => Boolean)(implicit context: Context): List[Activity] = {
if (!isNewsFeedEnabled || !ActivityLog.exists()) {
List.empty
} else {
val list = new ListBuffer[Activity]
Using.resource(new ReversedLinesFileReader(ActivityLog, StandardCharsets.UTF_8)) { reader =>
Using.resource(
ReversedLinesFileReader
.builder()
.setFile(ActivityLog)
.setCharset(StandardCharsets.UTF_8)
.get()
) { reader =>
var json: String = null
while (list.length < 50 && { json = reader.readLine(); json } != null) {
while (list.length < 50 && {
json = reader.readLine();
json
} != null) {
val activity = read[Activity](json)
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) {
if (filter(activity)) {
list += activity
} else if (includePublic && isPublicActivity(activity)) {
list += activity
}
}
@@ -68,24 +81,8 @@ trait ActivityService {
}
}
def getRecentActivitiesByOwners(owners: Set[String])(implicit context: Context): List[Activity] = {
if (!ActivityLog.exists()) {
List.empty
} else {
val list = new ListBuffer[Activity]
Using.resource(new ReversedLinesFileReader(ActivityLog, StandardCharsets.UTF_8)) { reader =>
var json: String = null
while (list.length < 50 && { json = reader.readLine(); json } != null) {
val activity = read[Activity](json)
if (owners.contains(activity.userName)) {
list += activity
} else if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) {
list += activity
}
}
}
list.toList
}
private def isPublicActivity(activity: Activity)(implicit context: Context): Boolean = {
!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)
}
def recordActivity[T <: { def toActivity: Activity }](info: T): Unit = {
@@ -93,3 +90,8 @@ trait ActivityService {
writeLog(info.toActivity)
}
}
object ActivityService {
def isNewsFeedEnabled: Boolean =
!ConfigUtil.getConfigValue[Boolean]("gitbucket.disableNewsFeed").getOrElse(false)
}

View File

@@ -0,0 +1,97 @@
package gitbucket.core.service
import gitbucket.core.model.{CustomField, IssueCustomField}
import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile.blockingApi._
trait CustomFieldsService {
def getCustomFields(owner: String, repository: String)(implicit s: Session): List[CustomField] =
CustomFields.filter(_.byRepository(owner, repository)).sortBy(_.fieldId asc).list
def getCustomFieldsWithValue(owner: String, repository: String, issueId: Int)(
implicit s: Session
): List[(CustomField, Option[IssueCustomField])] = {
CustomFields
.filter(_.byRepository(owner, repository))
.joinLeft(IssueCustomFields)
.on { case (t1, t2) => t1.fieldId === t2.fieldId && t2.issueId === issueId.bind }
.sortBy { case (t1, t2) => t1.fieldId }
.list
}
def getCustomField(owner: String, repository: String, fieldId: Int)(implicit s: Session): Option[CustomField] =
CustomFields.filter(_.byPrimaryKey(owner, repository, fieldId)).firstOption
def createCustomField(
owner: String,
repository: String,
fieldName: String,
fieldType: String,
constraints: Option[String],
enableForIssues: Boolean,
enableForPullRequests: Boolean
)(implicit s: Session): Int = {
CustomFields returning CustomFields.map(_.fieldId) insert CustomField(
userName = owner,
repositoryName = repository,
fieldName = fieldName,
fieldType = fieldType,
constraints = constraints,
enableForIssues = enableForIssues,
enableForPullRequests = enableForPullRequests
)
}
def updateCustomField(
owner: String,
repository: String,
fieldId: Int,
fieldName: String,
fieldType: String,
constraints: Option[String],
enableForIssues: Boolean,
enableForPullRequests: Boolean
)(
implicit s: Session
): Unit =
CustomFields
.filter(_.byPrimaryKey(owner, repository, fieldId))
.map(t => (t.fieldName, t.fieldType, t.constraints, t.enableForIssues, t.enableForPullRequests))
.update((fieldName, fieldType, constraints, enableForIssues, enableForPullRequests))
def deleteCustomField(owner: String, repository: String, fieldId: Int)(implicit s: Session): Unit = {
IssueCustomFields
.filter(t => t.userName === owner.bind && t.repositoryName === repository.bind && t.fieldId === fieldId.bind)
.delete
CustomFields.filter(_.byPrimaryKey(owner, repository, fieldId)).delete
}
def getCustomFieldValues(
userName: String,
repositoryName: String,
issueId: Int,
)(implicit s: Session): List[IssueCustomField] = {
IssueCustomFields
.filter(t => t.userName === userName && t.repositoryName === repositoryName.bind && t.issueId === issueId.bind)
.list
}
def insertOrUpdateCustomFieldValue(
field: CustomField,
userName: String,
repositoryName: String,
issueId: Int,
value: String
)(implicit s: Session): Unit = {
IssueCustomFields.insertOrUpdate(
IssueCustomField(
userName = userName,
repositoryName = repositoryName,
issueId = issueId,
fieldId = field.fieldId,
value = value
)
)
}
}

View File

@@ -165,7 +165,7 @@ trait HandleCommentService {
content match {
case Some(content) =>
// Update comment
val _commentId = Some(updateComment(issue.issueId, commentId.toInt, content))
val _commentId = Some(updateComment(owner, name, issue.issueId, commentId.toInt, content))
// Record comment activity
val commentInfo = if (issue.isPullRequest) {
PullRequestCommentInfo(owner, name, userName, content, issue.issueId)

View File

@@ -16,7 +16,7 @@ trait IssueCreationService {
repository: RepositoryInfo,
title: String,
body: Option[String],
assignee: Option[String],
assignees: Seq[String],
milestoneId: Option[Int],
priorityId: Option[Int],
labelNames: Seq[String],
@@ -35,16 +35,19 @@ trait IssueCreationService {
userName,
title,
body,
if (manageable) assignee else None,
if (manageable) milestoneId else None,
if (manageable) priorityId else None
)
val issue: Issue = getIssue(owner, name, issueId.toString).get
// insert labels
if (manageable) {
// insert assignees
assignees.foreach { assignee =>
registerIssueAssignee(owner, name, issueId, assignee)
}
// insert labels
val labels = getLabels(owner, name)
labelNames.map { labelName =>
labelNames.foreach { labelName =>
labels.find(_.labelName == labelName).map { label =>
registerIssueLabel(owner, name, issueId, label.labelId)
}

View File

@@ -5,13 +5,26 @@ import gitbucket.core.util.StringUtil._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.controller.Context
import gitbucket.core.model.{Account, Issue, IssueComment, IssueLabel, Label, PullRequest, Repository, Role}
import gitbucket.core.model.{
Account,
Issue,
IssueAssignee,
IssueComment,
IssueLabel,
Label,
Profile,
PullRequest,
Repository,
Role
}
import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile._
import gitbucket.core.model.Profile.profile.blockingApi._
import gitbucket.core.model.Profile.dateColumnType
import gitbucket.core.plugin.PluginRegistry
import scala.jdk.CollectionConverters._
trait IssuesService {
self: AccountService with RepositoryService with LabelsService with PrioritiesService with MilestonesService =>
import IssuesService._
@@ -118,8 +131,7 @@ trait IssuesService {
def countIssueGroupByLabels(
owner: String,
repository: String,
condition: IssueSearchCondition,
filterUser: Map[String, String]
condition: IssueSearchCondition
)(implicit s: Session): Map[String, Int] = {
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), IssueSearchOption.Issues)
@@ -138,7 +150,7 @@ trait IssuesService {
t3.labelName
}
.map {
case labelName ~ t =>
case (labelName, t) =>
labelName -> t.length
}
.list
@@ -156,8 +168,7 @@ trait IssuesService {
def countIssueGroupByPriorities(
owner: String,
repository: String,
condition: IssueSearchCondition,
filterUser: Map[String, String]
condition: IssueSearchCondition
)(implicit s: Session): Map[String, Int] = {
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), IssueSearchOption.Issues)
@@ -171,7 +182,7 @@ trait IssuesService {
t2.priorityName
}
.map {
case priorityName ~ t =>
case (priorityName, t) =>
priorityName -> t.length
}
.list
@@ -207,9 +218,11 @@ trait IssuesService {
.on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 => t1.byPriority(t6.userName, t6.repositoryName, t6.priorityId) }
.joinLeft(PullRequests)
.on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 => t1.byIssue(t7.userName, t7.repositoryName, t7.issueId) }
.sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 => i asc }
.joinLeft(IssueAssignees)
.on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 ~ t8 => t1.byIssue(t8.userName, t8.repositoryName, t8.issueId) }
.sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 ~ t8 => i asc }
.map {
case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 =>
case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 ~ t8 =>
(
t1,
t2.commentCount,
@@ -218,7 +231,8 @@ trait IssuesService {
t4.map(_.color),
t5.map(_.title),
t6.map(_.priorityName),
t7.map(_.commitIdTo)
t7.map(_.commitIdTo),
t8.map(_.assigneeUserName)
)
}
.list
@@ -228,36 +242,51 @@ trait IssuesService {
result.map { issues =>
issues.head match {
case (issue, commentCount, _, _, _, milestone, priority, commitId) =>
case (issue, commentCount, _, _, _, milestone, priority, commitId, _) =>
IssueInfo(
issue,
issues.flatMap { t =>
t._3.map(Label(issue.userName, issue.repositoryName, _, t._4.get, t._5.get))
} toList,
issues
.flatMap { t =>
t._3.map(Label(issue.userName, issue.repositoryName, _, t._4.get, t._5.get))
}
.distinct
.toList,
milestone,
priority,
commentCount,
commitId
commitId,
issues.flatMap(_._9).distinct
)
}
} toList
}
/** for api
* @return (issue, issueUser, assignedUser)
* @return (issue, issueUser, Seq(assigneeUsers))
*/
def searchIssueByApi(condition: IssueSearchCondition, offset: Int, limit: Int, repos: (String, String)*)(
implicit s: Session
): List[(Issue, Account, Option[Account])] = {
): List[(Issue, Account, List[Account])] = {
// get issues and comment count and labels
searchIssueQueryBase(condition, IssueSearchOption.Issues, offset, limit, repos)
.join(Accounts)
.on { case t1 ~ t2 ~ i ~ t3 => t3.userName === t1.openedUserName }
.joinLeft(IssueAssignees)
.on { case t1 ~ t2 ~ i ~ t3 ~ t4 => t4.byIssue(t1.userName, t1.repositoryName, t1.issueId) }
.joinLeft(Accounts)
.on { case t1 ~ t2 ~ i ~ t3 ~ t4 => t4.userName === t1.assignedUserName }
.sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 => i asc }
.map { case t1 ~ t2 ~ i ~ t3 ~ t4 => (t1, t3, t4) }
.on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 => t5.userName === t4.map(_.assigneeUserName) }
.sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 => i asc }
.map { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 => (t1, t3, t5) }
.list
.groupBy {
case (issue, account, _) =>
(issue, account)
}
.map {
case (_, values) =>
(values.head._1, values.head._2, values.flatMap(_._3))
}
.toList
}
/** for api
@@ -265,7 +294,7 @@ trait IssuesService {
*/
def searchPullRequestByApi(condition: IssueSearchCondition, offset: Int, limit: Int, repos: (String, String)*)(
implicit s: Session
): List[(Issue, Account, Int, PullRequest, Repository, Account, Option[Account])] = {
): List[(Issue, Account, Int, PullRequest, Repository, Account, List[Account])] = {
// get issues and comment count and labels
searchIssueQueryBase(condition, IssueSearchOption.PullRequests, offset, limit, repos)
.join(PullRequests)
@@ -276,11 +305,30 @@ trait IssuesService {
.on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 => t5.userName === t1.openedUserName }
.join(Accounts)
.on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 => t6.userName === t4.userName }
.joinLeft(IssueAssignees)
.on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 => t7.byIssue(t1.userName, t1.repositoryName, t1.issueId) }
.joinLeft(Accounts)
.on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 => t7.userName === t1.assignedUserName }
.sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 => i asc }
.map { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 => (t1, t5, t2.commentCount, t3, t4, t6, t7) }
.on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 ~ t8 => t8.userName === t7.map(_.assigneeUserName) }
.sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 ~ t8 => i asc }
.map { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 ~ t8 => (t1, t5, t2.commentCount, t3, t4, t6, t8) }
.list
.groupBy {
case (issue, openedUser, commentCount, pullRequest, repository, account, assignedUser) =>
(issue, openedUser, commentCount, pullRequest, repository, account)
}
.map {
case (_, values) =>
(
values.head._1,
values.head._2,
values.head._3,
values.head._4,
values.head._5,
values.head._6,
values.flatMap(_._7)
)
}
.toList
}
private def searchIssueQueryBase(
@@ -334,8 +382,8 @@ trait IssuesService {
searchOption: IssueSearchOption
)(
implicit s: Session
) =
Issues filter { t1 =>
) = {
val query = Issues filter { t1 =>
(if (repos.sizeIs == 1) {
t1.byRepository(repos.head._1, repos.head._2)
} else {
@@ -345,9 +393,9 @@ trait IssuesService {
case "open" => t1.closed === false
case "closed" => t1.closed === true
case _ => t1.closed === true || t1.closed === false
}).&&(t1.milestoneId.? isEmpty, condition.milestone == Some(None))
.&&(t1.priorityId.? isEmpty, condition.priority == Some(None))
.&&(t1.assignedUserName.? isEmpty, condition.assigned == Some(None))
}).&&(t1.milestoneId.? isEmpty, condition.milestone.contains(None))
.&&(t1.priorityId.? isEmpty, condition.priority.contains(None))
//.&&(t1.assignedUserName.? isEmpty, condition.assigned == Some(None))
.&&(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) &&
(searchOption match {
case IssueSearchOption.Issues => t1.pullRequest === false
@@ -371,7 +419,13 @@ trait IssuesService {
condition.priority.flatten.isDefined
)
// Assignee filter
.&&(t1.assignedUserName === condition.assigned.get.get.bind, condition.assigned.flatten.isDefined)
.&&(
IssueAssignees filter { a =>
a.byIssue(t1.userName, t1.repositoryName, t1.issueId) &&
a.assigneeUserName === condition.assigned.get.get.bind
} exists,
condition.assigned.flatten.isDefined
)
// Label filter
.&&(
IssueLabels filter { t2 =>
@@ -388,7 +442,7 @@ trait IssuesService {
.&&(
Repositories filter { t2 =>
(t2.byRepository(t1.userName, t1.repositoryName)) &&
(t2.isPrivate === (condition.visibility == Some("private")).bind)
(t2.isPrivate === condition.visibility.contains("private").bind)
} exists,
condition.visibility.nonEmpty
)
@@ -396,7 +450,9 @@ trait IssuesService {
.&&(t1.userName inSetBind condition.groups, condition.groups.nonEmpty)
// Mentioned filter
.&&(
(t1.openedUserName === condition.mentioned.get.bind) || t1.assignedUserName === condition.mentioned.get.bind ||
(t1.openedUserName === condition.mentioned.get.bind) || (IssueAssignees filter { t1 =>
t1.byIssue(t1.userName, t1.repositoryName, t1.issueId) && t1.assigneeUserName === condition.mentioned.get.bind
} exists) ||
(IssueComments filter { t2 =>
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) && (t2.commentedUserName === condition.mentioned.get.bind)
} exists),
@@ -404,13 +460,40 @@ trait IssuesService {
)
}
condition.others.foldLeft(query) {
case (query, cond) =>
def condQuery(f: Rep[String] => Rep[Boolean]): Query[Profile.Issues, Issue, Seq] = {
query.filter { t1 =>
IssueCustomFields
.join(CustomFields)
.on { (t2, t3) =>
t2.userName === t3.userName && t2.repositoryName === t3.repositoryName && t2.fieldId === t3.fieldId
}
.filter {
case (t2, t3) =>
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) && t3.fieldName === cond.name.bind && f(
t2.value
)
} exists
}
}
cond.operator match {
case "eq" => condQuery(_ === cond.value.bind)
case "lt" => condQuery(_ < cond.value.bind)
case "gt" => condQuery(_ > cond.value.bind)
case "lte" => condQuery(_ <= cond.value.bind)
case "gte" => condQuery(_ >= cond.value.bind)
case _ => throw new IllegalArgumentException("Unsupported operator")
}
}
}
def insertIssue(
owner: String,
repository: String,
loginUser: String,
title: String,
content: Option[String],
assignedUserName: Option[String],
milestoneId: Option[Int],
priorityId: Option[Int],
isPullRequest: Boolean = false
@@ -427,7 +510,6 @@ trait IssuesService {
loginUser,
milestoneId,
priorityId,
assignedUserName,
title,
content,
false,
@@ -509,7 +591,7 @@ trait IssuesService {
content: String,
action: String
)(implicit s: Session): Int = {
Issues.filter(_.issueId === issueId.bind).map(_.updatedDate).update(currentDate)
Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(_.updatedDate).update(currentDate)
IssueComments returning IssueComments.map(_.commentId) insert IssueComment(
userName = owner,
repositoryName = repository,
@@ -533,7 +615,7 @@ trait IssuesService {
.update(title, content, currentDate)
}
def changeIssueToPullRequest(owner: String, repository: String, issueId: Int)(implicit s: Session) = {
def changeIssueToPullRequest(owner: String, repository: String, issueId: Int)(implicit s: Session): Int = {
Issues
.filter(_.byPrimaryKey(owner, repository, issueId))
.map { t =>
@@ -542,35 +624,91 @@ trait IssuesService {
.update(true)
}
def updateAssignedUserName(
def getIssueAssignees(owner: String, repository: String, issueId: Int)(
implicit s: Session
): List[IssueAssignee] = {
IssueAssignees.filter(_.byIssue(owner, repository, issueId)).sortBy(_.assigneeUserName).list
}
def registerIssueAssignee(
owner: String,
repository: String,
issueId: Int,
assignedUserName: Option[String],
assigneeUserName: String,
insertComment: Boolean = false
)(implicit context: Context, s: Session): Int = {
val oldAssigned = getIssue(owner, repository, s"${issueId}").get.assignedUserName
val assigned = assignedUserName
)(
implicit context: Context,
s: Session
): Int = {
val assigner = context.loginAccount.map(_.userName)
if (insertComment) {
IssueComments insert IssueComment(
userName = owner,
repositoryName = repository,
issueId = issueId,
action = "assign",
action = "add_assignee",
commentedUserName = assigner.getOrElse("Unknown user"),
content = s"""${oldAssigned.getOrElse("Not assigned")}:${assigned.getOrElse("Not assigned")}""",
content = assigneeUserName,
registeredDate = currentDate,
updatedDate = currentDate
)
}
for (issue <- getIssue(owner, repository, issueId.toString); repo <- getRepository(owner, repository)) {
PluginRegistry().getIssueHooks.foreach(_.assigned(issue, repo, assigner, assigned, oldAssigned))
PluginRegistry().getIssueHooks.foreach(_.assigned(issue, repo, assigner, Some(assigneeUserName), None))
}
Issues
.filter(_.byPrimaryKey(owner, repository, issueId))
.map(t => (t.assignedUserName ?, t.updatedDate))
.update(assignedUserName, currentDate)
IssueAssignees insert IssueAssignee(owner, repository, issueId, assigneeUserName)
}
def deleteIssueAssignee(
owner: String,
repository: String,
issueId: Int,
assigneeUserName: String,
insertComment: Boolean = false
)(
implicit context: Context,
s: Session
): Int = {
val assigner = context.loginAccount.map(_.userName)
if (insertComment) {
IssueComments insert IssueComment(
userName = owner,
repositoryName = repository,
issueId = issueId,
action = "delete_assignee",
commentedUserName = assigner.getOrElse("Unknown user"),
content = assigneeUserName,
registeredDate = currentDate,
updatedDate = currentDate
)
}
// TODO Notify plugins of unassignment as doing in registerIssueAssignee()?
IssueAssignees filter (_.byPrimaryKey(owner, repository, issueId, assigneeUserName)) delete
}
def deleteAllIssueAssignees(owner: String, repository: String, issueId: Int, insertComment: Boolean = false)(
implicit context: Context,
s: Session
): Int = {
val assigner = context.loginAccount.map(_.userName)
if (insertComment) {
IssueComments insert IssueComment(
userName = owner,
repositoryName = repository,
issueId = issueId,
action = "delete_assign",
commentedUserName = assigner.getOrElse("Unknown user"),
content = "All assignees",
registeredDate = currentDate,
updatedDate = currentDate
)
}
// TODO Notify plugins of unassignment as doing in registerIssueAssignee()?
IssueAssignees filter (_.byIssue(owner, repository, issueId)) delete
}
def updateMilestoneId(
@@ -635,8 +773,10 @@ trait IssuesService {
.update(priorityId, currentDate)
}
def updateComment(issueId: Int, commentId: Int, content: String)(implicit s: Session): Int = {
Issues.filter(_.issueId === issueId.bind).map(_.updatedDate).update(currentDate)
def updateComment(owner: String, repository: String, issueId: Int, commentId: Int, content: String)(
implicit s: Session
): Int = {
Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(_.updatedDate).update(currentDate)
IssueComments.filter(_.byPrimaryKey(commentId)).map(t => (t.content, t.updatedDate)).update(content, currentDate)
}
@@ -644,13 +784,13 @@ trait IssuesService {
implicit context: Context,
s: Session
): Int = {
Issues.filter(_.issueId === issueId.bind).map(_.updatedDate).update(currentDate)
IssueComments.filter(_.byPrimaryKey(commentId)).firstOption match {
case Some(c) if c.action == "reopen_comment" =>
Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(_.updatedDate).update(currentDate)
IssueComments.filter(_.byPrimaryKey(commentId)).first match {
case c if c.action == "reopen_comment" =>
IssueComments.filter(_.byPrimaryKey(commentId)).map(t => (t.content, t.action)).update("Reopen", "reopen")
case Some(c) if c.action == "close_comment" =>
case c if c.action == "close_comment" =>
IssueComments.filter(_.byPrimaryKey(commentId)).map(t => (t.content, t.action)).update("Close", "close")
case Some(_) =>
case _ =>
IssueComments.filter(_.byPrimaryKey(commentId)).delete
IssueComments insert IssueComment(
userName = owner,
@@ -834,6 +974,8 @@ object IssuesService {
val IssueLimit = 25
case class CustomFieldCondition(name: String, value: String, operator: String)
case class IssueSearchCondition(
labels: Set[String] = Set.empty,
milestone: Option[Option[String]] = None,
@@ -845,7 +987,8 @@ object IssuesService {
sort: String = "created",
direction: String = "desc",
visibility: Option[String] = None,
groups: Set[String] = Set.empty
groups: Set[String] = Set.empty,
others: Seq[CustomFieldCondition] = Nil
) {
def isEmpty: Boolean = {
@@ -884,48 +1027,148 @@ object IssuesService {
case ("priority", "asc") => Some("sort:priority-asc")
case x => throw new MatchError(x)
},
visibility.map(visibility => s"visibility:${visibility}")
visibility.map(visibility => s"visibility:${visibility}"),
).flatten ++
others.map { cond =>
cond.operator match {
case "eq" => s"custom.${cond.name}:${cond.value}"
case "lt" => s"custom.${cond.name}<${cond.value}"
case "lte" => s"custom.${cond.name}<=${cond.value}"
case "gt" => s"custom.${cond.name}>${cond.value}"
case "gte" => s"custom.${cond.name}>=${cond.value}"
}
} ++
groups.map(group => s"group:${group}")
).mkString(" ")
def toURL: String =
"?" + List(
def toURL: String = {
"?" + (Seq(
if (labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))),
milestone.map {
case Some(x) => "milestone=" + urlEncode(x)
case Some(x) => s"milestone=${urlEncode(x)}"
case None => "milestone=none"
},
priority.map {
case Some(x) => "priority=" + urlEncode(x)
case Some(x) => s"priority=${urlEncode(x)}"
case None => "priority=none"
},
author.map(x => "author=" + urlEncode(x)),
author.map(x => s"author=${urlEncode(x)}"),
assigned.map {
case Some(x) => "assigned=" + urlEncode(x)
case Some(x) => s"assigned=${urlEncode(x)}"
case None => "assigned=none"
},
mentioned.map(x => "mentioned=" + urlEncode(x)),
Some("state=" + urlEncode(state)),
Some("sort=" + urlEncode(sort)),
Some("direction=" + urlEncode(direction)),
visibility.map(x => "visibility=" + urlEncode(x)),
if (groups.isEmpty) None else Some("groups=" + urlEncode(groups.mkString(",")))
).flatten.mkString("&")
mentioned.map(x => s"mentioned=${urlEncode(x)}"),
Some(s"state=${urlEncode(state)}"),
Some(s"sort=${urlEncode(sort)}"),
Some(s"direction=${urlEncode(direction)}"),
visibility.map(x => s"visibility=${urlEncode(x)}"),
if (groups.isEmpty) None else Some(s"groups=${urlEncode(groups.mkString(","))}")
).flatten ++ others.map { x =>
s"custom.${urlEncode(x.name)}=${urlEncode(x.operator)}:${urlEncode(x.value)}"
}).mkString("&")
}
}
object IssueSearchCondition {
private val SupportedOperators = Seq("eq", "lt", "gt", "lte", "gte")
private def param(request: HttpServletRequest, name: String, allow: Seq[String] = Nil): Option[String] = {
val value = request.getParameter(name)
if (value == null || value.isEmpty || (allow.nonEmpty && !allow.contains(value))) None else Some(value)
}
/**
* Restores IssueSearchCondition instance from filter query.
*/
def apply(filter: String): IssueSearchCondition = {
val conditions = filter
.split("[  \t]+")
.collect {
case x if !x.startsWith("custom.") && x.indexOf(":") > 0 =>
val dim = x.split(":")
dim(0) -> dim(1)
}
.groupBy(_._1)
.map {
case (key, values) =>
key -> values.map(_._2).toSeq
}
val (sort, direction) = conditions.get("sort").flatMap(_.headOption).getOrElse("created-desc") match {
case "created-asc" => ("created", "asc")
case "comments-desc" => ("comments", "desc")
case "comments-asc" => ("comments", "asc")
case "updated-desc" => ("comments", "desc")
case "updated-asc" => ("comments", "asc")
case _ => ("created", "desc")
}
val others = filter
.split("[  \t]+")
.collect {
case x if x.startsWith("custom.") && x.indexOf(":") > 0 =>
val dim = x.split(":")
dim(0) -> ("eq", dim(1))
case x if x.startsWith("custom.") && x.indexOf("<=") > 0 =>
val dim = x.split("<=")
dim(0) -> ("lte", dim(1))
case x if x.startsWith("custom.") && x.indexOf("<") > 0 =>
val dim = x.split("<")
dim(0) -> ("lt", dim(1))
case x if x.startsWith("custom.") && x.indexOf(">=") > 0 =>
val dim = x.split(">=")
dim(0) -> ("gte", dim(1))
case x if x.startsWith("custom.") && x.indexOf(">") > 0 =>
val dim = x.split(">")
dim(0) -> ("gt", dim(1))
}
.map {
case (key, (operator, value)) =>
CustomFieldCondition(key.stripPrefix("custom."), value, operator)
}
.toSeq
IssueSearchCondition(
conditions.get("label").map(_.toSet).getOrElse(Set.empty),
conditions.get("milestone").flatMap(_.headOption) match {
case None => None
case Some("none") => Some(None)
case Some(x) => Some(Some(x)) //milestones.get(x).map(x => Some(x))
},
conditions.get("priority").map(_.headOption), // TODO
conditions.get("author").flatMap(_.headOption),
conditions.get("assignee").map(_.headOption), // TODO
conditions.get("mentions").flatMap(_.headOption),
conditions.get("is").getOrElse(Seq.empty).find(x => x == "open" || x == "closed").getOrElse("open"),
sort,
direction,
conditions.get("visibility").flatMap(_.headOption),
conditions.get("group").map(_.toSet).getOrElse(Set.empty),
others
)
}
/**
* Restores IssueSearchCondition instance from request parameters.
*/
def apply(request: HttpServletRequest): IssueSearchCondition =
def apply(request: HttpServletRequest): IssueSearchCondition = {
val others = request.getParameterMap.asScala
.collect {
// custom.<field_name> = <operator>:<value>
case (key, values) if key.startsWith("custom.") && values.nonEmpty && values.head.indexOf(":") > 0 =>
val name = key.stripPrefix("custom.")
val Array(operator, value) = values.head.split(":")
CustomFieldCondition(name, value, operator)
case (key, values) if key.startsWith("custom.") && values.nonEmpty =>
val name = key.stripPrefix("custom.")
CustomFieldCondition(name, values.head, "eq")
}
.filter { x =>
SupportedOperators.contains(x.operator)
}
.toSeq
IssueSearchCondition(
param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty),
param(request, "milestone").map {
@@ -946,31 +1189,16 @@ object IssuesService {
param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"),
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
param(request, "visibility"),
param(request, "groups").map(_.split(",").toSet).getOrElse(Set.empty)
param(request, "groups").map(_.split(",").toSet).getOrElse(Set.empty),
others
)
}
def apply(request: HttpServletRequest, milestone: String): IssueSearchCondition =
IssueSearchCondition(
param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty),
Some(Some(milestone)),
param(request, "priority").map {
case "none" => None
case x => Some(x)
},
param(request, "author"),
param(request, "assigned").map {
case "none" => None
case x => Some(x)
},
param(request, "mentioned"),
param(request, "state", Seq("open", "closed", "all")).getOrElse("open"),
param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"),
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
param(request, "visibility"),
param(request, "groups").map(_.split(",").toSet).getOrElse(Set.empty)
)
def apply(request: HttpServletRequest, milestone: String): IssueSearchCondition = {
apply(request).copy(milestone = Some(Some(milestone)))
}
def page(request: HttpServletRequest) = {
def page(request: HttpServletRequest): Int = {
PaginationHelper.page(param(request, "page"))
}
}
@@ -981,7 +1209,8 @@ object IssuesService {
milestone: Option[String],
priority: Option[String],
commentCount: Int,
commitId: Option[String]
commitId: Option[String],
assignees: Seq[String]
)
}

View File

@@ -1,11 +1,11 @@
package gitbucket.core.service
import java.net.URI
import com.nimbusds.jose.JWSAlgorithm.Family
import com.nimbusds.jose.proc.BadJOSEException
import com.nimbusds.jose.util.DefaultResourceRetriever
import com.nimbusds.jose.{JOSEException, JWSAlgorithm}
import com.nimbusds.jwt.JWT
import com.nimbusds.oauth2.sdk._
import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic
import com.nimbusds.oauth2.sdk.id.{ClientID, Issuer, State}
@@ -52,6 +52,11 @@ trait OpenIDConnectService {
)
}
def createOIDLogoutRequest(issuer: Issuer, clientID: ClientID, redirectURI: URI, token: JWT): LogoutRequest = {
val metadata = OIDCProviderMetadata.resolve(issuer)
new LogoutRequest(metadata.getEndSessionEndpointURI, token, null, clientID, redirectURI, null, null)
}
/**
* Proceed the OpenID Connect authentication.
*
@@ -60,7 +65,7 @@ trait OpenIDConnectService {
* @param state State saved in the session
* @param nonce Nonce saved in the session
* @param oidc OIDC settings
* @return ID token
* @return (ID token, GitBucket account)
*/
def authenticate(
params: Map[String, String],
@@ -68,22 +73,25 @@ trait OpenIDConnectService {
state: State,
nonce: Nonce,
oidc: SystemSettingsService.OIDC
)(implicit s: Session): Option[Account] =
)(implicit s: Session): Option[(JWT, Account)] =
validateOIDCAuthenticationResponse(params, state, redirectURI) flatMap { authenticationResponse =>
obtainOIDCToken(authenticationResponse.getAuthorizationCode, nonce, redirectURI, oidc) flatMap { claims =>
Seq("email", "preferred_username", "name").map(k => Option(claims.getStringClaim(k))) match {
case Seq(Some(email), preferredUsername, name) =>
getOrCreateFederatedUser(
claims.getIssuer.getValue,
claims.getSubject.getValue,
email,
preferredUsername,
name
)
case _ =>
logger.info(s"OIDC ID token must have an email claim: claims=${claims.toJSONObject}")
None
}
obtainOIDCToken(authenticationResponse.getAuthorizationCode, nonce, redirectURI, oidc) flatMap {
case (jwt, claims) =>
Seq("email", "preferred_username", "name").map(k => Option(claims.getStringClaim(k))) match {
case Seq(Some(email), preferredUsername, name) =>
getOrCreateFederatedUser(
claims.getIssuer.getValue,
claims.getSubject.getValue,
email,
preferredUsername,
name
).map { account =>
(jwt, account)
}
case _ =>
logger.info(s"OIDC ID token must have an email claim: claims=${claims.toJSONObject}")
None
}
}
}
@@ -136,7 +144,7 @@ trait OpenIDConnectService {
nonce: Nonce,
redirectURI: URI,
oidc: SystemSettingsService.OIDC
): Option[IDTokenClaimsSet] = {
): Option[(JWT, IDTokenClaimsSet)] = {
val metadata = OIDCProviderMetadata.resolve(oidc.issuer)
val tokenRequest = new TokenRequest(
metadata.getTokenEndpointURI,
@@ -173,7 +181,7 @@ trait OpenIDConnectService {
metadata: OIDCProviderMetadata,
nonce: Nonce,
oidc: SystemSettingsService.OIDC
): Option[IDTokenClaimsSet] =
): Option[(JWT, IDTokenClaimsSet)] =
Option(response.getOIDCTokens.getIDToken) match {
case Some(jwt) =>
val validator = oidc.jwsAlgorithm map { jwsAlgorithm =>
@@ -188,7 +196,7 @@ trait OpenIDConnectService {
new IDTokenValidator(metadata.getIssuer, oidc.clientID)
}
try {
Some(validator.validate(jwt, nonce))
Some((jwt, validator.validate(jwt, nonce)))
} catch {
case e @ (_: BadJOSEException | _: JOSEException) =>
logger.info(s"OIDC ID token has error: $e")

View File

@@ -472,6 +472,40 @@ trait PullRequestService {
}
}
def getSingleDiff(
userName: String,
repositoryName: String,
commitId: String,
path: String
): Option[DiffInfo] = {
Using.resource(
Git.open(getRepositoryDir(userName, repositoryName))
) { git =>
val newId = git.getRepository.resolve(commitId)
JGitUtil.getDiff(git, None, newId.getName, path)
}
}
def getSingleDiff(
userName: String,
repositoryName: String,
branch: String,
requestUserName: String,
requestRepositoryName: String,
requestCommitId: String,
path: String
): Option[DiffInfo] = {
Using.resources(
Git.open(getRepositoryDir(userName, repositoryName)),
Git.open(getRepositoryDir(requestUserName, requestRepositoryName))
) { (oldGit, newGit) =>
val oldId = oldGit.getRepository.resolve(branch)
val newId = newGit.getRepository.resolve(requestCommitId)
JGitUtil.getDiff(newGit, Some(oldId.getName), newId.getName, path)
}
}
def getRequestCompareInfo(
userName: String,
repositoryName: String,
@@ -579,7 +613,7 @@ trait PullRequestService {
case (oldGit, newGit) =>
if (originRepository.branchList.contains(originId)) {
val forkedId2 =
forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.id }.getOrElse(forkedId)
forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.commitId }.getOrElse(forkedId)
val originId2 = JGitUtil.getForkedCommitId(
oldGit,
@@ -596,9 +630,9 @@ trait PullRequestService {
} else {
val originId2 =
originRepository.tags.collectFirst { case x if x.name == originId => x.id }.getOrElse(originId)
originRepository.tags.collectFirst { case x if x.name == originId => x.commitId }.getOrElse(originId)
val forkedId2 =
forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.id }.getOrElse(forkedId)
forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.commitId }.getOrElse(forkedId)
(Option(oldGit.getRepository.resolve(originId2)), Option(newGit.getRepository.resolve(forkedId2)))
}

View File

@@ -36,10 +36,10 @@ trait RepositoryCommitFileService {
settings: SystemSettings
)(
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
)(implicit s: Session, c: JsonFormat.Context): Either[String, ObjectId] = {
_createFiles(repository, branch, message, loginAccount, loginAccount.fullName, loginAccount.mailAddress, settings)(
f
)._1
).map(_._1)
}
/**
@@ -58,7 +58,7 @@ trait RepositoryCommitFileService {
commit: String,
loginAccount: Account,
settings: SystemSettings
)(implicit s: Session, c: JsonFormat.Context): (ObjectId, Option[ObjectId]) = {
)(implicit s: Session, c: JsonFormat.Context): Either[String, (ObjectId, Option[ObjectId])] = {
commitFile(
repository,
branch,
@@ -92,7 +92,7 @@ trait RepositoryCommitFileService {
committerName: String,
committerMailAddress: String,
settings: SystemSettings
)(implicit s: Session, c: JsonFormat.Context): (ObjectId, Option[ObjectId]) = {
)(implicit s: Session, c: JsonFormat.Context): Either[String, (ObjectId, Option[ObjectId])] = {
val newPath = newFileName.map { newFileName =>
if (path.length == 0) newFileName else s"${path}/${newFileName}"
@@ -141,7 +141,7 @@ trait RepositoryCommitFileService {
settings: SystemSettings
)(
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => R
)(implicit s: Session, c: JsonFormat.Context): (ObjectId, R) = {
)(implicit s: Session, c: JsonFormat.Context): Either[String, (ObjectId, R)] = {
LockUtil.lock(s"${repository.owner}/${repository.name}") {
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
@@ -177,11 +177,11 @@ trait RepositoryCommitFileService {
error match {
case Some(error) =>
// commit is rejected
// TODO Notify commit failure to edited user
val refUpdate = git.getRepository.updateRef(headName)
refUpdate.setNewObjectId(headTip)
refUpdate.setForceUpdate(true)
refUpdate.update()
Left(error)
case None =>
// update refs
@@ -242,8 +242,8 @@ trait RepositoryCommitFileService {
)
}
}
Right((commitId, result))
}
(commitId, result)
}
}
}

View File

@@ -64,9 +64,19 @@ trait RepositoryCreationService {
name: String,
description: Option[String],
isPrivate: Boolean,
createReadme: Boolean
createReadme: Boolean,
defaultBranch: String
): Future[Unit] = {
createRepository(loginAccount, owner, name, description, isPrivate, if (createReadme) "README" else "EMPTY", None)
createRepository(
loginAccount,
owner,
name,
description,
isPrivate,
if (createReadme) "README" else "EMPTY",
None,
defaultBranch
)
}
def createRepository(
@@ -76,7 +86,8 @@ trait RepositoryCreationService {
description: Option[String],
isPrivate: Boolean,
initOption: String,
sourceUrl: Option[String]
sourceUrl: Option[String],
defaultBranch: String
): Future[Unit] = Future {
RepositoryCreationService.startCreation(owner, name)
try {
@@ -93,7 +104,7 @@ trait RepositoryCreationService {
} else None
// Insert to the database at first
insertRepository(name, owner, description, isPrivate)
insertRepository(name, owner, description, isPrivate, defaultBranch)
// // Add collaborators for group repository
// if(ownerAccount.isGroupAccount){
@@ -110,7 +121,7 @@ trait RepositoryCreationService {
// Create the actual repository
val gitdir = getRepositoryDir(owner, name)
JGitUtil.initRepository(gitdir)
JGitUtil.initRepository(gitdir, defaultBranch)
if (initOption == "README" || initOption == "EMPTY_COMMIT") {
Using.resource(Git.open(gitdir)) { git =>
@@ -163,7 +174,7 @@ trait RepositoryCreationService {
}
// Create Wiki repository
createWikiRepository(loginAccount, owner, name)
createWikiRepository(loginAccount, owner, name, defaultBranch)
// Record activity
recordActivity(CreateRepositoryInfo(owner, name, loginUserName))

View File

@@ -12,6 +12,7 @@ import gitbucket.core.util.JGitUtil.FileInfo
import org.apache.commons.io.FileUtils
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.{Repository => _}
import scala.util.Using
trait RepositoryService {
@@ -33,7 +34,7 @@ trait RepositoryService {
userName: String,
description: Option[String],
isPrivate: Boolean,
defaultBranch: String = "master",
defaultBranch: String,
originRepositoryName: Option[String] = None,
originUserName: Option[String] = None,
parentRepositoryName: Option[String] = None,
@@ -253,6 +254,7 @@ trait RepositoryService {
Labels.filter(_.byRepository(userName, repositoryName)).delete
IssueComments.filter(_.byRepository(userName, repositoryName)).delete
PullRequests.filter(_.byRepository(userName, repositoryName)).delete
IssueAssignees.filter(_.byRepository(userName, repositoryName)).delete
Issues.filter(_.byRepository(userName, repositoryName)).delete
Priorities.filter(_.byRepository(userName, repositoryName)).delete
IssueId.filter(_.byRepository(userName, repositoryName)).delete
@@ -341,10 +343,7 @@ trait RepositoryService {
repository.originUserName.getOrElse(repository.userName),
repository.originRepositoryName.getOrElse(repository.repositoryName)
),
getOpenMilestones(
repository.originUserName.getOrElse(repository.userName),
repository.originRepositoryName.getOrElse(repository.repositoryName)
),
getOpenMilestones(repository.userName, repository.repositoryName),
getRepositoryManagers(repository.userName, repository.repositoryName)
)
}
@@ -835,12 +834,10 @@ object RepositoryService {
def httpUrl(owner: String, name: String)(implicit context: Context): String =
s"${context.baseUrl}/git/${owner}/${name}.git"
def sshUrl(owner: String, name: String)(implicit context: Context): Option[String] =
if (context.settings.ssh.enabled) {
context.settings.sshAddress.map { x =>
s"ssh://${x.genericUser}@${x.host}:${x.port}/${owner}/${name}.git"
}
} else None
context.settings.sshUrl(owner, name)
def openRepoUrl(openUrl: String)(implicit context: Context): String =
s"github-${context.platform}://openRepo/${openUrl}"

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