Compare commits

...

175 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
98 changed files with 1341 additions and 639 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,9 +8,9 @@ jobs:
timeout-minutes: 30
strategy:
matrix:
java: [8, 11, 17]
java: [11, 21]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Cache
uses: actions/cache@v3
env:
@@ -27,9 +27,9 @@ jobs:
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.1.2!' update # TODO
run: sbt '++ 3.x' update # TODO
- name: Build executable
run: sbt executable
- name: Upload artifacts

1
.gitignore vendored
View File

@@ -4,6 +4,7 @@
.ensime_cache
.DS_Store
.java-version
.tmp
# sbt specific
dist/*

View File

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

View File

@@ -1,6 +1,22 @@
# Changelog
All changes to the project will be documented in this file.
## 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

View File

@@ -59,29 +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.38.x
What's New in 4.40.x
-------------
## 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.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.

108
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.38.3"
val ScalatraVersion = "2.8.4"
val JettyVersion = "9.4.49.v20220914"
val JgitVersion = "5.13.1.202206130422-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,7 +15,9 @@ sourcesInBase := false
organization := Organization
name := Name
version := GitBucketVersion
scalaVersion := "2.13.10"
scalaVersion := "2.13.12"
crossScalaVersions += "3.3.1"
// scalafmtOnCompile := true
@@ -30,56 +32,47 @@ resolvers ++= Seq(
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.6" cross CrossVersion.for3Use2_13,
"commons-io" % "commons-io" % "2.11.0",
"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.17",
"org.apache.commons" % "commons-compress" % "1.21",
"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.9.1" exclude ("org.slf4j", "slf4j-jdk14") exclude ("org.apache.sshd", "sshd-mina") exclude ("org.apache.sshd", "sshd-netty"),
"org.apache.tika" % "tika-core" % "2.5.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" % "3.0.8",
"org.postgresql" % "postgresql" % "42.5.0",
"ch.qos.logback" % "logback-classic" % "1.2.11",
"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.2",
"com.typesafe" % "config" % "1.4.3",
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.1.0",
"io.github.java-diff-utils" % "java-diff-utils" % "4.12",
"org.cache2k" % "cache2k-all" % "1.6.0.Final",
"net.coobird" % "thumbnailator" % "0.4.18",
"net.coobird" % "thumbnailator" % "0.4.20",
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
"com.nimbusds" % "oauth2-oidc-sdk" % "10.1",
"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.8.1" % "test",
"com.dimafeng" %% "testcontainers-scala" % "0.40.11" % "test",
"org.testcontainers" % "mysql" % "1.17.5" % "test",
"org.testcontainers" % "postgresql" % "1.17.5" % "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.313" % "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",
@@ -89,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
@@ -123,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
@@ -159,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
@@ -185,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 _ => ()
}
}
@@ -286,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)

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.7.2
sbt.version=1.9.6

View File

@@ -1,11 +1,11 @@
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6")
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.1")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.0.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" % "2.0.0")
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

@@ -65,9 +65,15 @@ public class JettyLauncher {
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) {
@@ -149,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.11.0
gist:4.22.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>

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

@@ -114,5 +114,8 @@ object GitBucketCoreModule
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.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

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

@@ -185,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))),
@@ -196,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(
@@ -268,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
)
@@ -813,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}")

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
@@ -42,7 +43,7 @@ trait DashboardControllerBase extends ControllerBase {
withoutPhysicalInfo = true,
limit = context.settings.basicBehavior.limitVisibleRepositories
)
html.repos(getGroupNames(loginAccount.userName), repos, repos)
html.repos(getGroupNames(loginAccount.userName), repos, repos, isNewsFeedEnabled)
}
})
@@ -130,7 +131,8 @@ trait DashboardControllerBase extends ControllerBase {
None,
withoutPhysicalInfo = true,
limit = context.settings.basicBehavior.limitVisibleRepositories
)
),
isNewsFeedEnabled
)
}
@@ -172,7 +174,8 @@ trait DashboardControllerBase extends ControllerBase {
None,
withoutPhysicalInfo = true,
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.basicBehavior.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
@@ -293,15 +324,15 @@ trait IndexControllerBase extends ControllerBase {
)
val repositories = {
context.settings.basicBehavior.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

@@ -89,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))
}
})
@@ -531,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

@@ -102,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))
}
})
@@ -690,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

@@ -126,6 +126,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
case class CustomFieldForm(
fieldName: String,
fieldType: String,
constraints: Option[String],
enableForIssues: Boolean,
enableForPullRequests: Boolean
)
@@ -133,6 +134,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
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)
@@ -511,6 +513,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
repository.name,
form.fieldName,
form.fieldType,
if (form.fieldType == "enum") form.constraints else None,
form.enableForIssues,
form.enableForPullRequests
)
@@ -533,6 +536,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
params("fieldId").toInt,
form.fieldName,
form.fieldType,
if (form.fieldType == "enum") form.constraints else None,
form.enableForIssues,
form.enableForPullRequests
)

View File

@@ -124,7 +124,8 @@ 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) {

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

@@ -57,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 { repository =>
post("/api/v3/repos/:owner/:repository/git/refs")(writableUsersOnly { repository =>
extractFromJsonBody[CreateARef].map {
data =>
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.owner))) { 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), ref))
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()
})
@@ -85,7 +90,7 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
val refName = multiParams("splat").mkString("/")
extractFromJsonBody[UpdateARef].map {
data =>
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.owner))) { 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.")
@@ -96,7 +101,7 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
val result = update.update()
result match {
case Result.FORCED | Result.FAST_FORWARD | Result.NO_CHANGE =>
JsonFormat(ApiRef.fromRef(RepositoryName(repository), update.getRef))
JsonFormat(ApiRef.fromRef(RepositoryName(repository), git.getRepository.findRef(refName)))
case _ => UnprocessableEntity(result.name())
}
}

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

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

@@ -93,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)
@@ -130,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 =>

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

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

View File

@@ -11,7 +11,7 @@ trait DeployKeyComponent extends TemplateComponent { self: Profile =>
val publicKey = column[String]("PUBLIC_KEY")
val 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

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

View File

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

View File

@@ -13,8 +13,7 @@ trait IssueCustomFieldComponent extends TemplateComponent { self: Profile =>
val fieldId = column[Int]("FIELD_ID", O.PrimaryKey)
val value = column[String]("VALUE")
def * =
(userName, repositoryName, issueId, fieldId, value)
.<>(IssueCustomField.tupled, IssueCustomField.unapply)
(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

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

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

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

@@ -28,6 +28,7 @@ trait CustomFieldsService {
repository: String,
fieldName: String,
fieldType: String,
constraints: Option[String],
enableForIssues: Boolean,
enableForPullRequests: Boolean
)(implicit s: Session): Int = {
@@ -36,6 +37,7 @@ trait CustomFieldsService {
repositoryName = repository,
fieldName = fieldName,
fieldType = fieldType,
constraints = constraints,
enableForIssues = enableForIssues,
enableForPullRequests = enableForPullRequests
)
@@ -47,6 +49,7 @@ trait CustomFieldsService {
fieldId: Int,
fieldName: String,
fieldType: String,
constraints: Option[String],
enableForIssues: Boolean,
enableForPullRequests: Boolean
)(
@@ -54,8 +57,8 @@ trait CustomFieldsService {
): Unit =
CustomFields
.filter(_.byPrimaryKey(owner, repository, fieldId))
.map(t => (t.fieldName, t.fieldType, t.enableForIssues, t.enableForPullRequests))
.update((fieldName, fieldType, enableForIssues, enableForPullRequests))
.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

View File

@@ -12,6 +12,7 @@ import gitbucket.core.model.{
IssueComment,
IssueLabel,
Label,
Profile,
PullRequest,
Repository,
Role
@@ -22,6 +23,8 @@ 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._
@@ -379,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 {
@@ -390,8 +393,8 @@ 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.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 {
@@ -439,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
)
@@ -457,6 +460,34 @@ 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,
@@ -584,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 =>
@@ -943,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,
@@ -954,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 = {
@@ -993,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 {
@@ -1055,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"))
}
}

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

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

@@ -34,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,
@@ -254,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

View File

@@ -90,6 +90,7 @@ trait SystemSettingsService {
props.setProperty(UploadLargeMaxFileSize, settings.upload.largeMaxFileSize.toString)
props.setProperty(UploadLargeTimeout, settings.upload.largeTimeout.toString)
props.setProperty(RepositoryViewerMaxFiles, settings.repositoryViewer.maxFiles.toString)
props.setProperty(DefaultBranch, settings.defaultBranch)
Using.resource(new java.io.FileOutputStream(GitBucketConf)) { out =>
props.store(out, null)
@@ -205,7 +206,8 @@ trait SystemSettingsService {
),
RepositoryViewerSettings(
getValue(props, RepositoryViewerMaxFiles, 0)
)
),
getValue(props, DefaultBranch, "main")
)
}
}
@@ -231,7 +233,8 @@ object SystemSettingsService {
showMailAddress: Boolean,
webHook: WebHook,
upload: Upload,
repositoryViewer: RepositoryViewerSettings
repositoryViewer: RepositoryViewerSettings,
defaultBranch: String
) {
def baseUrl(request: HttpServletRequest): String =
baseUrl.getOrElse(parseBaseUrl(request)).stripSuffix("/")
@@ -402,7 +405,6 @@ object SystemSettingsService {
private val RepositoryOperationFork = "repository_operation_fork"
private val Gravatar = "gravatar"
private val Notification = "notification"
private val ActivityLogLimit = "activity_log_limit"
private val LimitVisibleRepositories = "limitVisibleRepositories"
private val SshEnabled = "ssh"
private val SshHost = "ssh.host"
@@ -448,6 +450,7 @@ object SystemSettingsService {
private val UploadLargeMaxFileSize = "upload.largeMaxFileSize"
private val UploadLargeTimeout = "upload.largeTimeout"
private val RepositoryViewerMaxFiles = "repository_viewer_max_files"
private val DefaultBranch = "default_branch"
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = {
getConfigValue(key).getOrElse {

View File

@@ -51,11 +51,11 @@ object WikiService {
trait WikiService {
import WikiService._
def createWikiRepository(loginAccount: Account, owner: String, repository: String): Unit =
def createWikiRepository(loginAccount: Account, owner: String, repository: String, defaultBranch: String): Unit =
LockUtil.lock(s"${owner}/${repository}/wiki") {
val dir = Directory.getWikiRepositoryDir(owner, repository)
if (!dir.exists) {
JGitUtil.initRepository(dir)
JGitUtil.initRepository(dir, defaultBranch)
saveWikiPage(
owner,
repository,
@@ -72,11 +72,11 @@ trait WikiService {
/**
* Returns the wiki page.
*/
def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = {
def getWikiPage(owner: String, repository: String, pageName: String, branch: String): Option[WikiPageInfo] = {
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
if (!JGitUtil.isEmpty(git)) {
val fileName = pageName + ".md"
JGitUtil.getLatestCommitFromPath(git, fileName, "master").map { latestCommit =>
JGitUtil.getLatestCommitFromPath(git, fileName, branch).map { latestCommit =>
val content = JGitUtil.getContentFromPath(git, latestCommit.getTree, fileName, true)
WikiPageInfo(
fileName,
@@ -93,10 +93,10 @@ trait WikiService {
/**
* Returns the list of wiki page names.
*/
def getWikiPageList(owner: String, repository: String): List[String] = {
def getWikiPageList(owner: String, repository: String, branch: String): List[String] = {
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
JGitUtil
.getFileList(git, "master", ".")
.getFileList(git, branch, ".")
.filter(_.name.endsWith(".md"))
.filterNot(_.name.startsWith("_"))
.map(_.name.stripSuffix(".md"))
@@ -113,7 +113,8 @@ trait WikiService {
from: String,
to: String,
committer: Account,
pageName: Option[String]
pageName: Option[String],
branch: String
): Boolean = {
case class RevertInfo(operation: String, filePath: String, source: String)
@@ -151,7 +152,7 @@ trait WikiService {
fh.getChangeType match {
case DiffEntry.ChangeType.MODIFY => {
val source =
getWikiPage(owner, repository, fh.getNewPath.stripSuffix(".md")).map(_.content).getOrElse("")
getWikiPage(owner, repository, fh.getNewPath.stripSuffix(".md"), branch).map(_.content).getOrElse("")
val applied = PatchUtil.apply(source, patch, fh)
if (applied != null) {
Seq(RevertInfo("ADD", fh.getNewPath, applied))

View File

@@ -245,7 +245,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
with RequestCache {
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
private var existIds: Seq[String] = Nil
private var newCommitIds: Seq[String] = Nil
def onPreReceive(receivePack: ReceivePack, commands: java.util.Collection[ReceiveCommand]): Unit = {
Database() withTransaction { implicit session =>
@@ -260,13 +260,43 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
}
}
Using.resource(Git.open(Directory.getRepositoryDir(owner, repository))) { git =>
existIds = JGitUtil.getAllCommitIds(git)
commands.asScala.foreach { command =>
val commits = getCommits(git, command)
if (commits.size < 100) {
newCommitIds = commits
.takeWhile { commit =>
!existCommit(git, commit)
}
.map(_.id)
} else {
val allCommits = JGitUtil.getAllCommitIds(git)
newCommitIds = commits.collect {
case commit if !allCommits.contains(commit.id) =>
commit.id
}
}
}
}
} catch {
case ex: Exception => {
case ex: Exception =>
logger.error(ex.toString, ex)
throw ex
}
}
}
}
private def existCommit(git: Git, commit: CommitInfo): Boolean = {
JGitUtil.getBranchesOfCommit(git, commit.id).nonEmpty
}
private def getCommits(git: Git, command: ReceiveCommand): Seq[CommitInfo] = {
val refName = command.getRefName.split("/")
if (refName(1) == "tags") {
Nil
} else {
command.getType match {
case ReceiveCommand.Type.DELETE => Nil
case _ => JGitUtil.getCommitLog(git, command.getOldId.name, command.getNewId.name)
}
}
}
@@ -279,20 +309,12 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
Using.resource(Git.open(Directory.getRepositoryDir(owner, repository))) { git =>
JGitUtil.removeCache(git)
val pushedIds = scala.collection.mutable.Set[String]()
commands.asScala.foreach { command =>
logger.debug(s"commandType: ${command.getType}, refName: ${command.getRefName}")
implicit val apiContext: Context = api.JsonFormat.Context(baseUrl, sshUrl)
val refName = command.getRefName.split("/")
val branchName = refName.drop(2).mkString("/")
val commits = if (refName(1) == "tags") {
Nil
} else {
command.getType match {
case ReceiveCommand.Type.DELETE => Nil
case _ => JGitUtil.getCommitLog(git, command.getOldId.name, command.getNewId.name)
}
}
val commits = getCommits(git, command)
val repositoryInfo = getRepository(owner, repository).get
@@ -312,8 +334,9 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
// Extract new commit and apply issue comment
val defaultBranch = repositoryInfo.repository.defaultBranch
val newCommits = commits.flatMap { commit =>
if (!existIds.contains(commit.id) && !pushedIds.contains(commit.id)) {
val pushedIds = scala.collection.mutable.Set[String]()
val newCommits = commits.collect {
case commit if newCommitIds.contains(commit.id) && !pushedIds.contains(commit.id) =>
if (issueCount > 0) {
pushedIds.add(commit.id)
createIssueComment(owner, repository, commit)
@@ -333,9 +356,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
}
}
}
Some(commit)
} else None
}
commit
}.toList
// set PR as merged
val pulls = getPullRequestsByBranch(owner, repository, branchName, Some(false))
@@ -431,10 +453,9 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
// update repository last modified time.
updateLastActivityDate(owner, repository)
} catch {
case ex: Exception => {
case ex: Exception =>
logger.error(ex.toString, ex)
throw ex
}
}
}
}
@@ -506,10 +527,9 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl:
}
}
} catch {
case ex: Exception => {
case ex: Exception =>
logger.error(ex.toString, ex)
throw ex
}
}
}
}

View File

@@ -99,26 +99,26 @@ object DatabaseType {
object H2 extends DatabaseType {
val jdbcDriver = "org.h2.Driver"
val slickDriver = BlockingH2Driver
val liquiDriver = new H2Database()
val slickDriver: BlockingJdbcProfile = BlockingH2Driver
val liquiDriver: AbstractJdbcDatabase = new H2Database()
}
object MySQL extends DatabaseType {
val jdbcDriver = "org.mariadb.jdbc.Driver"
val slickDriver = BlockingMySQLDriver
val liquiDriver = new MySQLDatabase()
val slickDriver: BlockingJdbcProfile = BlockingMySQLDriver
val liquiDriver: AbstractJdbcDatabase = new MySQLDatabase()
}
object MariaDb extends DatabaseType {
val jdbcDriver = "org.mariadb.jdbc.Driver"
val slickDriver = BlockingMySQLDriver
val liquiDriver = new MariaDBDatabase()
val slickDriver: BlockingJdbcProfile = BlockingMySQLDriver
val liquiDriver: AbstractJdbcDatabase = new MariaDBDatabase()
}
object PostgreSQL extends DatabaseType {
val jdbcDriver = "org.postgresql.Driver2"
val slickDriver = BlockingPostgresDriver
val liquiDriver = new PostgresDatabase()
val slickDriver: BlockingJdbcProfile = BlockingPostgresDriver
val liquiDriver: AbstractJdbcDatabase = new PostgresDatabase()
}
object BlockingPostgresDriver extends slick.jdbc.PostgresProfile with BlockingJdbcProfile {

View File

@@ -1,8 +1,7 @@
package gitbucket.core.util
import java.io.ByteArrayInputStream
import scala.jdk.CollectionConverters._
import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile.blockingApi._
import org.bouncycastle.bcpg.ArmoredInputStream
@@ -34,29 +33,33 @@ object GpgUtil {
}
def verifySign(signInfo: JGitUtil.GpgSignInfo)(implicit s: Session): Option[JGitUtil.GpgVerifyInfo] = {
new BcPGPObjectFactory(new ArmoredInputStream(new ByteArrayInputStream(signInfo.signArmored)))
.iterator()
.asScala
.flatMap {
case signList: PGPSignatureList =>
signList
.iterator()
.asScala
.flatMap { sign =>
getGpgKey(sign.getKeyID)
.map { pubKey =>
sign.init(new BcPGPContentVerifierBuilderProvider, pubKey)
sign.update(signInfo.target)
(sign, pubKey)
}
.collect {
case (sign, pubKey) if sign.verify() =>
JGitUtil.GpgVerifyInfo(pubKey.getUserIDs.next, pubKey.getKeyID.toHexString.toUpperCase)
}
}
try {
new BcPGPObjectFactory(new ArmoredInputStream(new ByteArrayInputStream(signInfo.signArmored)))
.iterator()
.asScala
.flatMap {
case signList: PGPSignatureList =>
signList
.iterator()
.asScala
.flatMap { sign =>
getGpgKey(sign.getKeyID)
.map { pubKey =>
sign.init(new BcPGPContentVerifierBuilderProvider, pubKey)
sign.update(signInfo.target)
(sign, pubKey)
}
.collect {
case (sign, pubKey) if sign.verify() =>
JGitUtil.GpgVerifyInfo(pubKey.getUserIDs.next, pubKey.getKeyID.toHexString.toUpperCase)
}
}
}
.toList
.headOption
} catch {
case _: Throwable => None
}
}
.toList
.headOption
}
}

View File

@@ -1,7 +1,6 @@
package gitbucket.core.util
import java.io._
import gitbucket.core.service.RepositoryService
import org.eclipse.jgit.api.Git
import Directory._
@@ -18,10 +17,10 @@ import org.eclipse.jgit.treewalk.filter._
import org.eclipse.jgit.diff.DiffEntry.ChangeType
import org.eclipse.jgit.errors.{ConfigInvalidException, IncorrectObjectTypeException, MissingObjectException}
import org.eclipse.jgit.transport.RefSpec
import java.util.Date
import java.util.concurrent.TimeUnit
import org.cache2k.Cache2kBuilder
import org.cache2k.{Cache, Cache2kBuilder}
import org.eclipse.jgit.api.errors._
import org.eclipse.jgit.diff.{DiffEntry, DiffFormatter, RawTextComparator}
import org.eclipse.jgit.dircache.DirCacheEntry
@@ -40,6 +39,9 @@ object JGitUtil {
private implicit val objectDatabaseReleasable: Releasable[ObjectDatabase] =
_.close()
private def isCacheEnabled(): Boolean =
!ConfigUtil.getConfigValue[Boolean]("gitbucket.disableCache").getOrElse(false)
/**
* The repository data.
*
@@ -284,26 +286,34 @@ object JGitUtil {
revCommit
}
private val cache = new Cache2kBuilder[String, Int]() {}
.name("commit-count")
.expireAfterWrite(24, TimeUnit.HOURS)
.entryCapacity(10000)
.build()
private val cache: Cache[String, Int] = if (isCacheEnabled()) {
Cache2kBuilder
.of(classOf[String], classOf[Int])
.name("commit-count")
.expireAfterWrite(24, TimeUnit.HOURS)
.entryCapacity(10000)
.build()
} else null
private val objectCommitCache = new Cache2kBuilder[ObjectId, RevCommit]() {}
.name("object-commit")
.entryCapacity(10000)
.build()
private val objectCommitCache: Cache[ObjectId, RevCommit] = if (isCacheEnabled()) {
Cache2kBuilder
.of(classOf[ObjectId], classOf[RevCommit])
.name("object-commit")
.entryCapacity(10000)
.build()
} else null
def removeCache(git: Git): Unit = {
val dir = git.getRepository.getDirectory
val keyPrefix = dir.getAbsolutePath + "@"
if (isCacheEnabled()) {
val dir = git.getRepository.getDirectory
val keyPrefix = dir.getAbsolutePath + "@"
cache.keys.forEach(key => {
if (key.startsWith(keyPrefix)) {
cache.remove(key)
}
})
cache.keys.forEach(key => {
if (key.startsWith(keyPrefix)) {
cache.remove(key)
}
})
}
}
/**
@@ -312,16 +322,23 @@ object JGitUtil {
*/
def getCommitCount(git: Git, branch: String, max: Int = 10001): Int = {
val dir = git.getRepository.getDirectory
val key = dir.getAbsolutePath + "@" + branch
val entry = cache.getEntry(key)
if (entry == null) {
if (isCacheEnabled()) {
val key = dir.getAbsolutePath + "@" + branch
val entry = cache.getEntry(key)
if (entry == null) {
val commitId = git.getRepository.resolve(branch)
val commitCount = git.log.add(commitId).call.iterator.asScala.take(max).size
cache.put(key, commitCount)
commitCount
} else {
entry.getValue
}
} else {
val commitId = git.getRepository.resolve(branch)
val commitCount = git.log.add(commitId).call.iterator.asScala.take(max).size
cache.put(key, commitCount)
commitCount
} else {
entry.getValue
}
}
@@ -444,7 +461,7 @@ object JGitUtil {
(id, mode, name, path, opt, None)
} else if (commitCount < 10000) {
(id, mode, name, path, opt, Some(getCommit(path)))
} else {
} else if (isCacheEnabled()) {
// Use in-memory cache if the commit count is too big.
val cached = objectCommitCache.getEntry(id)
if (cached == null) {
@@ -454,6 +471,9 @@ object JGitUtil {
} else {
(id, mode, name, path, opt, Some(cached.getValue))
}
} else {
val commit = getCommit(path)
(id, mode, name, path, opt, Some(commit))
}
}
}
@@ -872,10 +892,11 @@ object JGitUtil {
.reverse
}
def initRepository(dir: java.io.File): Unit =
Using.resource(new RepositoryBuilder().setGitDir(dir).setBare.build) { repository =>
repository.create(true)
setReceivePack(repository)
def initRepository(dir: java.io.File, defaultBranch: String): Unit =
Using.resource(new RepositoryBuilder().setGitDir(dir).setBare().setInitialBranch(defaultBranch).build) {
repository =>
repository.create(true)
setReceivePack(repository)
}
def cloneRepository(from: java.io.File, to: java.io.File): Unit =

View File

@@ -28,7 +28,12 @@ object Keys {
/**
* Session key for the OpenID Connect authentication.
*/
val OidcContext = "oidcContext"
val OidcAuthContext = "oidcAuthContext"
/**
* Session key for the OpenID Connect token.
*/
val OidcSessionContext = "oidcSessionContext"
/**
* Generate session key for the issue search condition.

View File

@@ -56,6 +56,15 @@
<textarea id="information" name="information" class="form-control" style="height: 100px;">@context.settings.information</textarea>
</fieldset>
<!--====================================================================-->
<!-- Default branch -->
<!--====================================================================-->
<hr>
<label for="defaultBranch"><span class="strong">Default branch</span></label>
<fieldset>
<input type="text" name="defaultBranch" id="defaultBranch" class="form-control" value="@context.settings.defaultBranch"/>
<span id="error-defaultBranch" class="error"></span>
</fieldset>
<!--====================================================================-->
<!-- AdminLTE SkinName -->
<!--====================================================================-->
<hr>

View File

@@ -5,10 +5,11 @@
condition: gitbucket.core.service.IssuesService.IssueSearchCondition,
filter: String,
groups: List[String],
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(implicit context: gitbucket.core.controller.Context)
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
enableNewsFeed: Boolean)(implicit context: gitbucket.core.controller.Context)
@gitbucket.core.html.main("Issues"){
@gitbucket.core.dashboard.html.sidebar(recentRepositories){
@gitbucket.core.dashboard.html.tab("issues")
@gitbucket.core.dashboard.html.tab(enableNewsFeed, "issues")
<div class="container">
@gitbucket.core.dashboard.html.issuesnavi("issues", filter, openCount, closedCount, condition)
@gitbucket.core.dashboard.html.issueslist(issues, page, openCount, closedCount, condition, filter, groups)

View File

@@ -5,10 +5,11 @@
condition: gitbucket.core.service.IssuesService.IssueSearchCondition,
filter: String,
groups: List[String],
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(implicit context: gitbucket.core.controller.Context)
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
enableNewsFeed: Boolean)(implicit context: gitbucket.core.controller.Context)
@gitbucket.core.html.main("Pull requests"){
@gitbucket.core.dashboard.html.sidebar(recentRepositories){
@gitbucket.core.dashboard.html.tab("pulls")
@gitbucket.core.dashboard.html.tab(enableNewsFeed, "pulls")
<div class="container">
@gitbucket.core.dashboard.html.issuesnavi("pulls", filter, openCount, closedCount, condition)
@gitbucket.core.dashboard.html.issueslist(issues, page, openCount, closedCount, condition, filter, groups)

View File

@@ -1,10 +1,11 @@
@(groups: List[String],
visibleRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(implicit context: gitbucket.core.controller.Context)
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
enableNewsFeed: Boolean)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@gitbucket.core.html.main("Repositories"){
@gitbucket.core.dashboard.html.sidebar(recentRepositories){
@gitbucket.core.dashboard.html.tab("repos")
@gitbucket.core.dashboard.html.tab(enableNewsFeed, "repos")
<div class="container">
<div class="btn-group" id="owner-dropdown">
<button class="dropdown-toggle btn btn-default" data-toggle="dropdown" aria-expanded="false">

View File

@@ -1,14 +1,18 @@
@(active: String = "")(implicit context: gitbucket.core.controller.Context)
<ul class="nav nav-tabs" style="margin-bottom: 20px;">
<li @if(active == ""){ class="active"}><a href="@context.path/">News feed</a></li>
@if(context.loginAccount.isDefined){
<li @if(active == "repos" ){ class="active"}><a href="@context.path/dashboard/repos">Repositories</a></li>
<li @if(active == "pulls" ){ class="active"}><a href="@context.path/dashboard/pulls">Pull requests</a></li>
<li @if(active == "issues"){ class="active"}><a href="@context.path/dashboard/issues">Issues</a></li>
@gitbucket.core.plugin.PluginRegistry().getDashboardTabs.map { tab =>
@tab(context).map { link =>
<li @if(active == link.id){ class="active"}><a href="@context.path/@link.path">@link.label</a></li>
@(enableNewsFeed: Boolean, active: String = "")(implicit context: gitbucket.core.controller.Context)
@if(enableNewsFeed || context.loginAccount.isDefined) {
<ul class="nav nav-tabs" style="margin-bottom: 20px;">
@if(enableNewsFeed) {
<li @if(active == "") {class="active"}><a href="@context.path/">News feed</a></li>
}
@if(context.loginAccount.isDefined) {
<li @if(active == "repos") {class="active"}><a href="@context.path/dashboard/repos">Repositories</a></li>
<li @if(active == "pulls") {class="active"}><a href="@context.path/dashboard/pulls">Pull requests</a></li>
<li @if(active == "issues") {class="active"}><a href="@context.path/dashboard/issues">Issues</a></li>
@gitbucket.core.plugin.PluginRegistry().getDashboardTabs.map { tab =>
@tab(context).map { link =>
<li @if(active == link.id) {class="active"}><a href="@context.path/@link.path">@link.label</a></li>
}
}
}
}
</ul>
</ul>
}

View File

@@ -1,6 +1,7 @@
@(activities: List[gitbucket.core.model.Activity],
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
showBannerToCreatePersonalAccessToken: Boolean)(implicit context: gitbucket.core.controller.Context)
showBannerToCreatePersonalAccessToken: Boolean,
enableNewsFeed: Boolean)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@gitbucket.core.html.main("GitBucket"){
@gitbucket.core.dashboard.html.sidebar(recentRepositories){
@@ -19,12 +20,26 @@
</a> and use it in place of a password on the <code>git</code> command line.
</div>
}
@gitbucket.core.dashboard.html.tab()
@gitbucket.core.dashboard.html.tab(enableNewsFeed)
<div class="container">
<div class="pull-right">
<a href="@context.path/activities.atom"><img src="@helpers.assets("/common/images/feed.png")" alt="activities"></a>
</div>
@gitbucket.core.helper.html.activities(activities)
@if(enableNewsFeed) {
<div class="pull-right">
<a href="@context.path/activities.atom"><img src="@helpers.assets("/common/images/feed.png")" alt="activities"></a>
</div>
@gitbucket.core.helper.html.activities(activities)
} else {
<div class="signin-form">
@if(context.settings.basicBehavior.allowAnonymousAccess){
@context.settings.information.map { information =>
<div class="alert alert-info" style="background-color: white; color: #555; border-color: #4183c4; font-size: small; line-height: 120%;">
<button type="button" class="close" data-dismiss="alert">&times;</button>
@Html(information)
</div>
}
}
@gitbucket.core.html.signinform(context.settings)
</div>
}
</div>
}
}

View File

@@ -140,8 +140,8 @@
}
</div>
<span id="label-assigned">
@issueAssignees.map { asignee =>
<div>@helpers.avatarLink(asignee.assigneeUserName, 20) @helpers.user(asignee.assigneeUserName, styleClass="username strong small")</div>
@issueAssignees.map { assignee =>
<div>@helpers.avatarLink(assignee.assigneeUserName, 20) @helpers.user(assignee.assigneeUserName, styleClass="username strong small")</div>
}
@if(issueAssignees.isEmpty) {
<span class="muted small">No one assigned</span>
@@ -158,10 +158,10 @@
<div class="pull-right">
@gitbucket.core.model.CustomFieldBehavior(field.fieldType).map { behavior =>
@if(issue.nonEmpty) {
@Html(behavior.fieldHtml(repository, issue.get.issueId, field.fieldId, value.map(_.value).getOrElse(""), isManageable))
@Html(behavior.fieldHtml(repository, issue.get.issueId, field.fieldId, field.fieldName, field.constraints, value.map(_.value).getOrElse(""), isManageable))
}
@if(issue.isEmpty) {
@Html(behavior.createHtml(repository, field.fieldId))
@Html(behavior.createHtml(repository, field.fieldId, field.fieldName, field.constraints))
}
}
</div>

View File

@@ -24,9 +24,9 @@
<a href="@condition.copy(state = "closed").toURL">Closed <span class="badge">@closedCount</span></a>
</li>
</ul>
<form method="GET" action="@helpers.url(repository)/search" id="search-filter-form" class="form-inline pull-right" autocomplete="off">
<form method="GET" action="@helpers.url(repository)/@target" id="search-filter-form" class="form-inline pull-right" autocomplete="off">
<div class="input-group">
<input type="text" class="form-control" name="q" placeholder="Search..." aria-label="Search all issues"/>
<input type="text" class="form-control" name="q" placeholder="Search..." aria-label="Search all issues" value="@condition.toFilterString" style="width: 300px;"/>
<input type="hidden" name="type" value="@target"/>
<span class="input-group-btn">
<button type="submit" id="search-btn" class="btn btn-default" aria-label="Search all issues"><i class="fa fa-search"></i></button>

View File

@@ -46,7 +46,7 @@
</td>
<td class="text-right">
<div class="branch-action">
@if(repository.repository.defaultBranch != branch.name){
@if(repository.repository.defaultBranch != branch.name || repository.repository.originUserName.isDefined){
@branch.mergeInfo.map{ info =>
@prs.map{ case (pull, issue) =>
<a href="@helpers.url(repository)/pull/@issue.issueId" title="@issue.title">#@issue.issueId</a>
@@ -76,13 +76,14 @@
}
@if(hasWritePermission){
<span style="margin-left: 8px;">
@if(prs.map(!_._2.closed).getOrElse(false)){
@if(prs.exists(!_._2.closed)){
<a class="disabled" data-toggle="tooltip" title="You cant delete this branch because it has an open pull request"><i class="octicon octicon-trashcan"></i></a>
} else {
@if(isProtected){
@if(isProtected || repository.repository.defaultBranch == branch.name) {
<a class="disabled" data-toggle="tooltip" title="You cant delete a protected branch."><i class="octicon octicon-trashcan"></i></a>
} else {
<a href="@helpers.url(repository)/delete/@helpers.encodeRefName(branch.name)" class="delete-branch" data-name="@branch.name" @if(info.isMerged){ data-toggle="tooltip" title="this branch is merged" }><i class="octicon octicon-trashcan @if(info.isMerged){warning} else {danger}"></i></a>
<a href="@helpers.url(repository)/delete/@helpers.encodeRefName(branch.name)" class="delete-branch" data-name="@branch.name" @if(info.isMerged) {
data-toggle="tooltip" title="this branch is merged" }><i class="octicon octicon-trashcan @if(info.isMerged) {warning} else {danger}"></i></a>
}
}
</span>
@@ -99,7 +100,7 @@
<script>
$(function(){
$('.delete-branch').click(function(e){
var branchName = $(e.target).closest('a').data('name');
const branchName = $(e.target).closest('a').data('name');
return confirm('Are you sure you want to remove the ' + branchName + ' branch?');
});
$('*[data-toggle=tooltip]').tooltip().css("white-space","nowrap");

View File

@@ -21,12 +21,12 @@
git add README.md
git commit -m "first commit"
git remote add origin <span class="live-clone-url">@repository.httpUrl</span>
git push -u origin master
git push -u origin @context.settings.defaultBranch
}
<h3 style="margin-top: 30px;">Push an existing repository from the command line</h3>
@helpers.pre {
git remote add origin <span class="live-clone-url">@repository.httpUrl</span>
git push -u origin master
git push -u origin @context.settings.defaultBranch
}
<script>
$(function(){
@@ -38,4 +38,4 @@
</script>
}
}
}
}

View File

@@ -7,6 +7,9 @@
</div>
<div class="col-md-4">
@customField.fieldType
@customField.constraints.map { constraints =>
(@constraints)
}
</div>
<div class="col-md-2">
@if(customField.enableForIssues) {

View File

@@ -10,7 +10,9 @@
<option value="double" @if(field.map(_.fieldType == "double").getOrElse(false)){selected}>Double</option>
<option value="string" @if(field.map(_.fieldType == "string").getOrElse(false)){selected}>String</option>
<option value="date" @if(field.map(_.fieldType == "date").getOrElse(false)){selected}>Date</option>
<option value="enum" @if(field.map(_.fieldType == "enum").getOrElse(false)){selected}>Enum</option>
</select>
<input type="text" id="constraints-@fieldId" style="width: 300px; @if(!field.exists(_.fieldType == "enum")){display: none;}" class="form-control input-sm" value="@field.map(_.constraints)" placeholder="Comma-separated enum values">
<label for="enableForIssues-@fieldId" class="normal" style="margin-left: 4px;">
<input type="checkbox" id="enableForIssues-@fieldId" @if(field.map(_.enableForIssues).getOrElse(false)){checked}> Issues
</label>
@@ -30,6 +32,7 @@
$.post('@helpers.url(repository)/settings/issues/fields/@{if(fieldId == "new") "new" else s"$fieldId/edit"}', {
'fieldName' : $('#fieldName-@fieldId').val(),
'fieldType': $('#fieldType-@fieldId option:selected').val(),
'constraints': $('#constraints-@fieldId').val(),
'enableForIssues': $('#enableForIssues-@fieldId').prop('checked'),
'enableForPullRequests': $('#enableForPullRequests-@fieldId').prop('checked')
}, function(data, status){
@@ -61,6 +64,14 @@
$('#field-@fieldId').show();
}
});
$('#fieldType-@fieldId').change(function(){
if($(this).val() == 'enum') {
$('#constraints-@fieldId').show();
} else {
$('#constraints-@fieldId').hide();
}
});
});
</script>
}

View File

@@ -24,15 +24,16 @@ class GitBucketCoreModuleSpec extends AnyFunSuite {
)
}
implicit private val suiteDescription = Description.createSuiteDescription(getClass)
implicit private val suiteDescription: Description = Description.createSuiteDescription(getClass)
Seq("8.0", "5.7").foreach { tag =>
test(s"Migration MySQL $tag", ExternalDBTest) {
val container = new MySQLContainer() {
override val container = new org.testcontainers.containers.MySQLContainer(s"mysql:$tag") {
override def getDriverClassName = "org.mariadb.jdbc.Driver"
override def getJdbcUrl: String = super.getJdbcUrl + "?permitMysqlScheme"
}
override val container: org.testcontainers.containers.MySQLContainer[_] =
new org.testcontainers.containers.MySQLContainer(s"mysql:$tag") {
override def getDriverClassName = "org.mariadb.jdbc.Driver"
override def getJdbcUrl: String = super.getJdbcUrl + "?permitMysqlScheme"
}
// TODO https://jira.mariadb.org/browse/CONJ-663
container.withCommand("mysqld --default-authentication-plugin=mysql_native_password")
}

View File

@@ -9,12 +9,19 @@ import scala.util.Using
import org.kohsuke.github.GHCommitState
import java.io.File
import java.util.logging.{Level, Logger}
/**
* Need to run `sbt package` before running this test.
*/
class ApiIntegrationTest extends AnyFunSuite {
// Suppress warning logs caused by liquibase
private val liquibaseResourceLogger = Logger.getLogger("liquibase.resource")
liquibaseResourceLogger.setLevel(Level.SEVERE)
private val liquibaseParserLogger = Logger.getLogger("liquibase.parser")
liquibaseParserLogger.setLevel(Level.SEVERE)
test("create repository") {
Using.resource(new TestingGitBucketServer(19999)) { server =>
val github = server.client("root", "root")
@@ -29,7 +36,7 @@ class ApiIntegrationTest extends AnyFunSuite {
assert(repository.getName == "test")
assert(repository.getDescription == "test repository")
assert(repository.getDefaultBranch == "master")
assert(repository.getDefaultBranch == "main")
assert(repository.getWatchers == 0)
assert(repository.getWatchersCount == 0)
assert(repository.getForks == 0)
@@ -48,7 +55,7 @@ class ApiIntegrationTest extends AnyFunSuite {
val repository = repositories.get(0)
assert(repository.getName == "test")
assert(repository.getDescription == "test repository")
assert(repository.getDefaultBranch == "master")
assert(repository.getDefaultBranch == "main")
assert(repository.getWatchers == 0)
assert(repository.getWatchersCount == 0)
assert(repository.getForks == 0)
@@ -68,7 +75,7 @@ class ApiIntegrationTest extends AnyFunSuite {
val github = server.client("root", "root")
val repo = github.createRepository("create_status_test").autoInit(true).create()
val sha1 = repo.getBranch("master").getSHA1
val sha1 = repo.getBranch("main").getSHA1
{
val status = repo.getLastCommitStatus(sha1)
@@ -140,10 +147,10 @@ class ApiIntegrationTest extends AnyFunSuite {
// get master ref
{
val ref = repo.getRef("heads/master")
assert(ref.getRef == "refs/heads/master")
val ref = repo.getRef("heads/main")
assert(ref.getRef == "refs/heads/main")
assert(
ref.getUrl.toString == "http://localhost:19999/api/v3/repos/root/create_status_test/git/refs/heads/master"
ref.getUrl.toString == "http://localhost:19999/api/v3/repos/root/create_status_test/git/refs/heads/main"
)
assert(ref.getObject.getType == "commit")
}
@@ -169,7 +176,7 @@ class ApiIntegrationTest extends AnyFunSuite {
val createResult =
repo
.createContent()
.branch("master")
.branch("main")
.content("create")
.message("Create content")
.path("README.md")
@@ -186,7 +193,7 @@ class ApiIntegrationTest extends AnyFunSuite {
val updateResult =
repo
.createContent()
.branch("master")
.branch("main")
.content("update")
.message("Update content")
.path("README.md")
@@ -204,4 +211,100 @@ class ApiIntegrationTest extends AnyFunSuite {
}
}
test("issue labels") {
Using.resource(new TestingGitBucketServer(19999)) { server =>
val github = server.client("root", "root")
val repo = github.createRepository("issue_label_test").autoInit(true).create()
val issue = repo.createIssue("test").create()
// Initial label state
{
val labels = repo.getIssue(issue.getNumber).getLabels
assert(labels.size() == 0)
}
// Add labels
{
issue.addLabels("bug", "duplicate")
val labels = repo.getIssue(issue.getNumber).getLabels
assert(labels.size() == 2)
val i = labels.iterator()
val label1 = i.next()
assert(label1.getName == "bug")
assert(label1.getColor == "fc2929")
assert(label1.getUrl == "http://localhost:19999/api/v3/repos/root/issue_label_test/labels/bug")
val label2 = i.next()
assert(label2.getName == "duplicate")
assert(label2.getColor == "cccccc")
assert(label2.getUrl == "http://localhost:19999/api/v3/repos/root/issue_label_test/labels/duplicate")
}
// Remove a label
{
issue.removeLabel("duplicate")
val labels = repo.getIssue(issue.getNumber).getLabels
assert(labels.size() == 1)
val i = labels.iterator()
val label1 = i.next()
assert(label1.getName == "bug")
assert(label1.getColor == "fc2929")
assert(label1.getUrl == "http://localhost:19999/api/v3/repos/root/issue_label_test/labels/bug")
}
// Replace labels (Cannot test because GHLabel.setLabels() doesn't use the replace endpoint)
// {
// issue.setLabels("enhancement", "invalid", "question")
//
// val labels = repo.getIssue(issue.getNumber).getLabels
// assert(labels.size() == 3)
//
// val i = labels.iterator()
// val label1 = i.next()
// assert(label1.getName == "enhancement")
// assert(label1.getColor == "84b6eb")
// assert(label1.getUrl == "http://localhost:19999/api/v3/repos/root/issue_label_test/labels/enhancement")
//
// val label2 = i.next()
// assert(label2.getName == "invalid")
// assert(label2.getColor == "e6e6e6")
// assert(label2.getUrl == "http://localhost:19999/api/v3/repos/root/issue_label_test/labels/invalid")
//
// val label3 = i.next()
// assert(label3.getName == "question")
// assert(label3.getColor == "cc317c")
// assert(label3.getUrl == "http://localhost:19999/api/v3/repos/root/issue_label_test/labels/question")
// }
}
}
test("Git refs APIs") {
Using.resource(new TestingGitBucketServer(19999)) { server =>
val github = server.client("root", "root")
val repo = github.createRepository("git_refs_test").autoInit(true).create()
val sha1 = repo.getBranch("main").getSHA1
val refs1 = repo.listRefs().toList
assert(refs1.size() == 1)
assert(refs1.get(0).getRef == "refs/heads/main")
assert(refs1.get(0).getObject.getSha == sha1)
val ref = repo.createRef("refs/heads/testref", sha1)
assert(ref.getRef == "refs/heads/testref")
assert(ref.getObject.getSha == sha1)
val refs2 = repo.listRefs().toList
assert(refs2.size() == 2)
assert(refs2.get(0).getRef == "refs/heads/main")
assert(refs2.get(0).getObject.getSha == sha1)
assert(refs2.get(1).getRef == "refs/heads/testref")
assert(refs2.get(1).getObject.getSha == sha1)
}
}
}

View File

@@ -13,7 +13,7 @@ import org.eclipse.jgit.lib.ObjectId
object ApiSpecModels {
implicit val context = JsonFormat.Context("http://gitbucket.exmple.com", None)
implicit val context: JsonFormat.Context = JsonFormat.Context("http://gitbucket.exmple.com", None)
val date1 = {
val d = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
@@ -53,7 +53,7 @@ object ApiSpecModels {
repositoryName = repo1Name.name,
isPrivate = false,
description = Some("This your first repo!"),
defaultBranch = "master",
defaultBranch = "main",
registeredDate = date1,
updatedDate = date1,
lastActivityDate = date1,
@@ -81,7 +81,7 @@ object ApiSpecModels {
pullCount = 1,
forkedCount = 1,
milestoneCount = 1,
branchList = Seq("master", "develop"),
branchList = Seq("main", "develop"),
tags = Seq(
TagInfo(
name = "v1.0",
@@ -147,7 +147,7 @@ object ApiSpecModels {
userName = repo1Name.owner,
repositoryName = repo1Name.name,
issueId = issuePR.issueId,
branch = "master",
branch = "main",
requestUserName = "bear",
requestRepositoryName = repo1Name.name,
requestBranch = "new-topic",
@@ -363,7 +363,7 @@ object ApiSpecModels {
info = ProtectedBranchInfo(
owner = repo1Name.owner,
repository = repo1Name.name,
branch = "master",
branch = "main",
enabled = true,
contexts = Seq("continuous-integration/travis-ci"),
includeAdministrators = true
@@ -384,7 +384,7 @@ object ApiSpecModels {
)
val apiBranch = ApiBranch(
name = "master",
name = "main",
commit = ApiBranchCommit(sha1),
protection = apiBranchProtectionOutput
)(
@@ -392,7 +392,7 @@ object ApiSpecModels {
)
val apiBranchForList = ApiBranchForList(
name = "master",
name = "main",
commit = ApiBranchCommit(sha1)
)
@@ -447,8 +447,8 @@ object ApiSpecModels {
val gitHubContext = JsonFormat.Context("https://api.github.com", Some("https://api.github.com"))
val apiRefHeadsMaster = ApiRef(
ref = "refs/heads/master",
url = ApiPath("/repos/gitbucket/gitbucket/git/refs/heads/master"),
ref = "refs/heads/main",
url = ApiPath("/repos/gitbucket/gitbucket/git/refs/heads/main"),
node_id = "MDM6UmVmOTM1MDc0NjpyZWZzL2hlYWRzL21hc3Rlcg==",
`object` = ApiRefCommit(
sha = "6b2d124d092402f2c2b7131caada05ead9e7de6d",
@@ -508,7 +508,7 @@ object ApiSpecModels {
|"watchers":0,
|"forks":1,
|"private":false,
|"default_branch":"master",
|"default_branch":"main",
|"owner":$jsonUser,
|"has_issues":true,
|"id":0,
@@ -594,7 +594,7 @@ object ApiSpecModels {
|"updated_at":"2011-04-14T16:00:49Z",
|"created_at":"2011-04-14T16:00:49Z",
|"head":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e","ref":"new-topic","repo":$jsonRepository,"label":"new-topic","user":$jsonUser},
|"base":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e","ref":"master","repo":$jsonRepository,"label":"master","user":$jsonUser},
|"base":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e","ref":"main","repo":$jsonRepository,"label":"main","user":$jsonUser},
|"merged":true,
|"merged_at":"2011-04-14T16:00:49Z",
|"merged_by":$jsonUser,
@@ -730,13 +730,13 @@ object ApiSpecModels {
val jsonBranchProtectionOutput =
"""{
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/master/protection",
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main/protection",
|"enabled":true,
|"required_status_checks":{
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/master/protection/required_status_checks",
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main/protection/required_status_checks",
|"enforcement_level":"everyone",
|"contexts":["continuous-integration/travis-ci"],
|"contexts_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/master/protection/required_status_checks/contexts"}
|"contexts_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main/protection/required_status_checks/contexts"}
|}""".stripMargin
val jsonBranchProtectionInput =
@@ -749,15 +749,15 @@ object ApiSpecModels {
|}""".stripMargin
val jsonBranch = s"""{
|"name":"master",
|"name":"main",
|"commit":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"},
|"protection":$jsonBranchProtectionOutput,
|"_links":{
|"self":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/master",
|"html":"http://gitbucket.exmple.com/octocat/Hello-World/tree/master"}
|"self":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main",
|"html":"http://gitbucket.exmple.com/octocat/Hello-World/tree/main"}
|}""".stripMargin
val jsonBranchForList = """{"name":"master","commit":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
val jsonBranchForList = """{"name":"main","commit":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
val jsonContents =
"""{
@@ -800,11 +800,11 @@ object ApiSpecModels {
//I checked all refs in gitbucket repo, and there appears to be only type "commit" and type "tag"
val jsonRef = """{"ref":"refs/heads/featureA","object":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
val jsonRefHeadsMaster =
val jsonRefHeadsMain =
"""{
|"ref": "refs/heads/master",
|"ref": "refs/heads/main",
|"node_id": "MDM6UmVmOTM1MDc0NjpyZWZzL2hlYWRzL21hc3Rlcg==",
|"url": "https://api.github.com/repos/gitbucket/gitbucket/git/refs/heads/master",
|"url": "https://api.github.com/repos/gitbucket/gitbucket/git/refs/heads/main",
|"object": {
|"sha": "6b2d124d092402f2c2b7131caada05ead9e7de6d",
|"type": "commit",

View File

@@ -1,11 +1,12 @@
package gitbucket.core.api
import org.json4s.Formats
import org.json4s.jackson.JsonMethods
import org.scalatest.funsuite.AnyFunSuite
class JsonFormatSpec extends AnyFunSuite {
import ApiSpecModels._
implicit val format = JsonFormat.jsonFormats
implicit val format: Formats = JsonFormat.jsonFormats
private def expected(json: String) = json.replaceAll("\n", "")
def normalizeJson(json: String) = {
@@ -83,7 +84,7 @@ class JsonFormatSpec extends AnyFunSuite {
assert(JsonFormat(apiPusher) == expected(jsonPusher))
}
test("apiRefHead") {
assertEqualJson(JsonFormat(apiRefHeadsMaster)(gitHubContext), jsonRefHeadsMaster)
assertEqualJson(JsonFormat(apiRefHeadsMaster)(gitHubContext), jsonRefHeadsMain)
}
test("apiRefTag") {
assertEqualJson(JsonFormat(apiRefTag)(gitHubContext), jsonRefTag)

View File

@@ -40,7 +40,7 @@ class CommitStatusServiceSpec
test("createCommitState can insert and update") {
withTestDB { implicit session =>
val tester = generateNewAccount(fixture1.creator)
insertRepository(fixture1.repositoryName, fixture1.userName, None, false)
insertRepository(fixture1.repositoryName, fixture1.userName, None, false, "main")
val id = generateFixture1(tester: Account)
assert(
getCommitStatus(fixture1.userName, fixture1.repositoryName, id) == Some(fixture1.copy(commitStatusId = id))
@@ -77,7 +77,7 @@ class CommitStatusServiceSpec
test("getCommitStatus can find by commitId and context") {
withTestDB { implicit session =>
val tester = generateNewAccount(fixture1.creator)
insertRepository(fixture1.repositoryName, fixture1.userName, None, false)
insertRepository(fixture1.repositoryName, fixture1.userName, None, false, "main")
val id = generateFixture1(tester: Account)
assert(
getCommitStatus(fixture1.userName, fixture1.repositoryName, fixture1.commitId, fixture1.context) == Some(
@@ -90,7 +90,7 @@ class CommitStatusServiceSpec
test("getCommitStatus can find by commitStatusId") {
withTestDB { implicit session =>
val tester = generateNewAccount(fixture1.creator)
insertRepository(fixture1.repositoryName, fixture1.userName, None, false)
insertRepository(fixture1.repositoryName, fixture1.userName, None, false, "main")
val id = generateFixture1(tester: Account)
assert(
getCommitStatus(fixture1.userName, fixture1.repositoryName, id) == Some(fixture1.copy(commitStatusId = id))

View File

@@ -7,7 +7,7 @@ class RepositoryServiceSpec extends AnyFunSuite with ServiceSpecBase with Reposi
test("renameRepository can rename CommitState, ProtectedBranches") {
withTestDB { implicit session =>
val tester = generateNewAccount("tester")
insertRepository("repo", "root", None, false)
insertRepository("repo", "root", None, false, "main")
val service = new CommitStatusService with ProtectedBranchService {}
val id = service.createCommitStatus(
userName = "root",

View File

@@ -81,7 +81,8 @@ trait ServiceSpecBase {
),
repositoryViewer = RepositoryViewerSettings(
maxFiles = 0
)
),
defaultBranch = "main"
)
def withTestDB[A](action: (Session) => A): A = {
@@ -127,8 +128,8 @@ trait ServiceSpecBase {
if (dir.exists()) {
FileUtils.deleteQuietly(dir)
}
JGitUtil.initRepository(dir)
dummyService.insertRepository(repositoryName, userName, None, false)
JGitUtil.initRepository(dir, "main")
dummyService.insertRepository(repositoryName, userName, None, false, "main")
ac
}

View File

@@ -26,7 +26,7 @@ class WebHookJsonFormatSpec extends AnyFunSuite {
|"description":"This your first repo!",
|"ref":"v1.0",
|"ref_type":"tag",
|"master_branch":"master",
|"master_branch":"main",
|"repository":$jsonRepository,
|"pusher_type":"user"
|}""".stripMargin
@@ -41,12 +41,12 @@ class WebHookJsonFormatSpec extends AnyFunSuite {
createFile(git, Constants.HEAD, "README.md", "body1", message = "initial")
createFile(git, Constants.HEAD, "README.md", "body1\nbody2", message = "modified")
val branchId = git.getRepository.resolve("master")
val branchId = git.getRepository.resolve("main")
val payload = WebHookPushPayload(
git = git,
sender = account,
refName = "refs/heads/master",
refName = "refs/heads/main",
repositoryInfo = repositoryInfo,
commits = List(commitInfo(branchId.name)),
repositoryOwner = account,
@@ -56,7 +56,7 @@ class WebHookJsonFormatSpec extends AnyFunSuite {
val expected = s"""{
|"pusher":{"name":"octocat","email":"octocat@example.com"},
|"sender":$jsonUser,
|"ref":"refs/heads/master",
|"ref":"refs/heads/main",
|"before":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|"after":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|"commits":[${jsonCommit(branchId.name)}],

View File

@@ -34,7 +34,7 @@ object GitSpecUtil {
RepositoryCache.clear()
FileUtils.deleteQuietly(dir)
Files.createDirectories(dir.toPath())
JGitUtil.initRepository(dir)
JGitUtil.initRepository(dir, "main")
dir
}

View File

@@ -23,14 +23,14 @@ class JGitUtilSpec extends AnyFunSuite {
withTestRepository { git =>
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
val branchId = git.getRepository.resolve("master")
val branchId = git.getRepository.resolve("main")
val commit = JGitUtil.getRevCommitFromId(git, branchId)
createFile(git, Constants.HEAD, "LICENSE", "Apache License", message = "commit1")
createFile(git, Constants.HEAD, "README.md", "body1\nbody2", message = "commit1")
// latest commit
val diff1 = JGitUtil.getDiffs(git, None, "master", false, true)
val diff1 = JGitUtil.getDiffs(git, None, "main", false, true)
assert(diff1.size == 1)
assert(diff1(0).changeType == ChangeType.MODIFY)
assert(diff1(0).oldPath == "README.md")
@@ -44,7 +44,7 @@ class JGitUtilSpec extends AnyFunSuite {
|\ No newline at end of file""".stripMargin))
// from specified commit
val diff2 = JGitUtil.getDiffs(git, Some(commit.getName), "master", false, true)
val diff2 = JGitUtil.getDiffs(git, Some(commit.getName), "main", false, true)
assert(diff2.size == 2)
assert(diff2(0).changeType == ChangeType.ADD)
assert(diff2(0).oldPath == "/dev/null")
@@ -73,7 +73,7 @@ class JGitUtilSpec extends AnyFunSuite {
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
// branch name
val branchId = git.getRepository.resolve("master")
val branchId = git.getRepository.resolve("main")
val commit1 = JGitUtil.getRevCommitFromId(git, branchId)
// commit id
@@ -97,19 +97,19 @@ class JGitUtilSpec extends AnyFunSuite {
withTestRepository { git =>
// getCommitCount
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
assert(JGitUtil.getCommitCount(git, "master") == 1)
assert(JGitUtil.getCommitCount(git, "main") == 1)
createFile(git, Constants.HEAD, "README.md", "body2", message = "commit2")
assert(JGitUtil.getCommitCount(git, "master") == 2)
assert(JGitUtil.getCommitCount(git, "main") == 2)
// maximum limit
(3 to 10).foreach { i =>
createFile(git, Constants.HEAD, "README.md", "body" + i, message = "commit" + i)
}
assert(JGitUtil.getCommitCount(git, "master", 5) == 5)
assert(JGitUtil.getCommitCount(git, "main", 5) == 5)
// actual commit count
val gitLog = git.log.add(git.getRepository.resolve("master")).all
val gitLog = git.log.add(git.getRepository.resolve("main")).all
assert(gitLog.call.asScala.toSeq.size == 10)
// getAllCommitIds
@@ -123,22 +123,22 @@ class JGitUtilSpec extends AnyFunSuite {
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
// createBranch
assert(JGitUtil.createBranch(git, "master", "test1") == Right("Branch created."))
assert(JGitUtil.createBranch(git, "master", "test2") == Right("Branch created."))
assert(JGitUtil.createBranch(git, "master", "test2") == Left("Sorry, that branch already exists."))
assert(JGitUtil.createBranch(git, "main", "test1") == Right("Branch created."))
assert(JGitUtil.createBranch(git, "main", "test2") == Right("Branch created."))
assert(JGitUtil.createBranch(git, "main", "test2") == Left("Sorry, that branch already exists."))
// verify
val branches = git.branchList.call()
assert(branches.size == 3)
assert(branches.get(0).getName == "refs/heads/master")
assert(branches.get(0).getName == "refs/heads/main")
assert(branches.get(1).getName == "refs/heads/test1")
assert(branches.get(2).getName == "refs/heads/test2")
// getBranchesOfCommit
val commit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve("master"))
val commit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve("main"))
val branchesOfCommit = JGitUtil.getBranchesOfCommit(git, commit.getName)
assert(branchesOfCommit.size == 3)
assert(branchesOfCommit(0) == "master")
assert(branchesOfCommit(0) == "main")
assert(branchesOfCommit(1) == "test1")
assert(branchesOfCommit(2) == "test2")
}
@@ -147,16 +147,16 @@ class JGitUtilSpec extends AnyFunSuite {
test("getBranches") {
withTestRepository { git =>
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
JGitUtil.createBranch(git, "master", "test1")
JGitUtil.createBranch(git, "main", "test1")
createFile(git, Constants.HEAD, "README.md", "body2", message = "commit2")
JGitUtil.createBranch(git, "master", "test2")
JGitUtil.createBranch(git, "main", "test2")
// getBranches
val branches = JGitUtil.getBranches(git, "master", true)
val branches = JGitUtil.getBranches(git, "main", true)
assert(branches.size == 3)
assert(branches(0).name == "master")
assert(branches(0).name == "main")
assert(branches(0).committerName == "dummy")
assert(branches(0).committerEmailAddress == "dummy@example.com")
@@ -178,17 +178,17 @@ class JGitUtilSpec extends AnyFunSuite {
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
// createTag
assert(JGitUtil.createTag(git, "1.0", Some("test1"), "master") == Right("Tag added."))
assert(JGitUtil.createTag(git, "1.0", Some("test1"), "main") == Right("Tag added."))
assert(
JGitUtil.createTag(git, "1.0", Some("test2"), "master") == Left("Sorry, some Git operation error occurs.")
JGitUtil.createTag(git, "1.0", Some("test2"), "main") == Left("Sorry, some Git operation error occurs.")
)
// record current commit
val commit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve("master"))
val commit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve("main"))
// createTag
createFile(git, Constants.HEAD, "LICENSE", "Apache License", message = "commit2")
assert(JGitUtil.createTag(git, "1.1", Some("test3"), "master") == Right("Tag added."))
assert(JGitUtil.createTag(git, "1.1", Some("test3"), "main") == Right("Tag added."))
// verify
val allTags = git.tagList().call().asScala
@@ -203,7 +203,7 @@ class JGitUtilSpec extends AnyFunSuite {
assert(tagsOfCommit(1) == "1.0")
// getTagsOnCommit
val tagsOnCommit = JGitUtil.getTagsOnCommit(git, "master")
val tagsOnCommit = JGitUtil.getTagsOnCommit(git, "main")
assert(tagsOnCommit.size == 1)
assert(tagsOnCommit(0) == "1.1")
}
@@ -214,7 +214,7 @@ class JGitUtilSpec extends AnyFunSuite {
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
createFile(git, Constants.HEAD, "LICENSE", "Apache License", message = "commit2")
val objectId = git.getRepository.resolve("master")
val objectId = git.getRepository.resolve("main")
val commit = JGitUtil.getRevCommitFromId(git, objectId)
// Since Non-LFS file doesn't need RepositoryInfo give null
@@ -233,7 +233,7 @@ class JGitUtilSpec extends AnyFunSuite {
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
createFile(git, Constants.HEAD, "LARGE_FILE", "body1" * 1000000, message = "commit1")
val objectId = git.getRepository.resolve("master")
val objectId = git.getRepository.resolve("main")
val commit = JGitUtil.getRevCommitFromId(git, objectId)
val content1 = JGitUtil.getContentFromPath(git, commit.getTree, "README.md", true)
@@ -252,7 +252,7 @@ class JGitUtilSpec extends AnyFunSuite {
createFile(git, Constants.HEAD, "README.md", "body1\nbody2\nbody3", message = "commit1")
createFile(git, Constants.HEAD, "README.md", "body0\nbody2\nbody3", message = "commit2")
val blames = JGitUtil.getBlame(git, "master", "README.md").toSeq
val blames = JGitUtil.getBlame(git, "main", "README.md").toSeq
assert(blames.size == 2)
assert(blames(0).message == "commit2")
@@ -266,75 +266,75 @@ class JGitUtilSpec extends AnyFunSuite {
withTestRepository { git =>
def list(branch: String, path: String) =
JGitUtil.getFileList(git, branch, path).map(finfo => (finfo.name, finfo.message, finfo.isDirectory))
assert(list("master", ".") == Nil)
assert(list("master", "dir/subdir") == Nil)
assert(list("main", ".") == Nil)
assert(list("main", "dir/subdir") == Nil)
assert(list("branch", ".") == Nil)
assert(list("branch", "dir/subdir") == Nil)
createFile(git, "master", "README.md", "body1", message = "commit1")
createFile(git, "main", "README.md", "body1", message = "commit1")
assert(list("master", ".") == List(("README.md", "commit1", false)))
assert(list("master", "dir/subdir") == Nil)
assert(list("main", ".") == List(("README.md", "commit1", false)))
assert(list("main", "dir/subdir") == Nil)
assert(list("branch", ".") == Nil)
assert(list("branch", "dir/subdir") == Nil)
createFile(git, "master", "README.md", "body2", message = "commit2")
createFile(git, "main", "README.md", "body2", message = "commit2")
assert(list("master", ".") == List(("README.md", "commit2", false)))
assert(list("master", "dir/subdir") == Nil)
assert(list("main", ".") == List(("README.md", "commit2", false)))
assert(list("main", "dir/subdir") == Nil)
assert(list("branch", ".") == Nil)
assert(list("branch", "dir/subdir") == Nil)
createFile(git, "master", "dir/subdir/File3.md", "body3", message = "commit3")
createFile(git, "main", "dir/subdir/File3.md", "body3", message = "commit3")
assert(list("master", ".") == List(("dir/subdir", "commit3", true), ("README.md", "commit2", false)))
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false)))
assert(list("main", ".") == List(("dir/subdir", "commit3", true), ("README.md", "commit2", false)))
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false)))
assert(list("branch", ".") == Nil)
assert(list("branch", "dir/subdir") == Nil)
createFile(git, "master", "dir/subdir/File4.md", "body4", message = "commit4")
createFile(git, "main", "dir/subdir/File4.md", "body4", message = "commit4")
assert(list("master", ".") == List(("dir/subdir", "commit4", true), ("README.md", "commit2", false)))
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(list("main", ".") == List(("dir/subdir", "commit4", true), ("README.md", "commit2", false)))
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(list("branch", ".") == Nil)
assert(list("branch", "dir/subdir") == Nil)
createFile(git, "master", "README5.md", "body5", message = "commit5")
createFile(git, "main", "README5.md", "body5", message = "commit5")
assert(
list("master", ".") == List(
list("main", ".") == List(
("dir/subdir", "commit4", true),
("README.md", "commit2", false),
("README5.md", "commit5", false)
)
)
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(list("branch", ".") == Nil)
assert(list("branch", "dir/subdir") == Nil)
createFile(git, "master", "README.md", "body6", message = "commit6")
createFile(git, "main", "README.md", "body6", message = "commit6")
assert(
list("master", ".") == List(
list("main", ".") == List(
("dir/subdir", "commit4", true),
("README.md", "commit6", false),
("README5.md", "commit5", false)
)
)
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(list("branch", ".") == Nil)
assert(list("branch", "dir/subdir") == Nil)
git.branchCreate().setName("branch").setStartPoint("master").call()
git.branchCreate().setName("branch").setStartPoint("main").call()
assert(
list("master", ".") == List(
list("main", ".") == List(
("dir/subdir", "commit4", true),
("README.md", "commit6", false),
("README5.md", "commit5", false)
)
)
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(
list("branch", ".") == List(
("dir/subdir", "commit4", true),
@@ -347,13 +347,13 @@ class JGitUtilSpec extends AnyFunSuite {
createFile(git, "branch", "dir/subdir/File3.md", "body7", message = "commit7")
assert(
list("master", ".") == List(
list("main", ".") == List(
("dir/subdir", "commit4", true),
("README.md", "commit6", false),
("README5.md", "commit5", false)
)
)
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(
list("branch", ".") == List(
("dir/subdir", "commit7", true),
@@ -363,17 +363,17 @@ class JGitUtilSpec extends AnyFunSuite {
)
assert(list("branch", "dir/subdir") == List(("File3.md", "commit7", false), ("File4.md", "commit4", false)))
createFile(git, "master", "dir8/File8.md", "body8", message = "commit8")
createFile(git, "main", "dir8/File8.md", "body8", message = "commit8")
assert(
list("master", ".") == List(
list("main", ".") == List(
("dir/subdir", "commit4", true),
("dir8", "commit8", true),
("README.md", "commit6", false),
("README5.md", "commit5", false)
)
)
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(
list("branch", ".") == List(
("dir/subdir", "commit7", true),
@@ -386,14 +386,14 @@ class JGitUtilSpec extends AnyFunSuite {
createFile(git, "branch", "dir/subdir9/File9.md", "body9", message = "commit9")
assert(
list("master", ".") == List(
list("main", ".") == List(
("dir/subdir", "commit4", true),
("dir8", "commit8", true),
("README.md", "commit6", false),
("README5.md", "commit5", false)
)
)
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(
list("branch", ".") == List(
("dir", "commit9", true),
@@ -403,17 +403,17 @@ class JGitUtilSpec extends AnyFunSuite {
)
assert(list("branch", "dir/subdir") == List(("File3.md", "commit7", false), ("File4.md", "commit4", false)))
mergeAndCommit(git, "master", "branch", message = "merge10")
mergeAndCommit(git, "main", "branch", message = "merge10")
assert(
list("master", ".") == List(
list("main", ".") == List(
("dir", "commit9", true),
("dir8", "commit8", true),
("README.md", "commit6", false),
("README5.md", "commit5", false)
)
)
assert(list("master", "dir/subdir") == List(("File3.md", "commit7", false), ("File4.md", "commit4", false)))
assert(list("main", "dir/subdir") == List(("File3.md", "commit7", false), ("File4.md", "commit4", false)))
}
}
@@ -421,10 +421,10 @@ class JGitUtilSpec extends AnyFunSuite {
withTestRepository { git =>
def list(branch: String, path: String) =
JGitUtil.getFileList(git, branch, path).map(finfo => (finfo.name, finfo.message, finfo.isDirectory))
createFile(git, "master", "README.md", "body1", message = "commit1")
createFile(git, "main", "README.md", "body1", message = "commit1")
createFile(git, "branch", "test/text2.txt", "body2", message = "commit2")
mergeAndCommit(git, "master", "branch", message = "merge3")
assert(list("master", "test") == List(("text2.txt", "commit2", false)))
mergeAndCommit(git, "main", "branch", message = "merge3")
assert(list("main", "test") == List(("text2.txt", "commit2", false)))
}
}

View File

@@ -193,7 +193,8 @@ class AvatarImageProviderSpec extends AnyFunSpec {
),
repositoryViewer = RepositoryViewerSettings(
maxFiles = 0
)
),
"main"
)
/**

View File

@@ -9,7 +9,7 @@ import org.mockito.Mockito._
class HelpersSpec extends AnyFunSpec {
private implicit val context = mock(classOf[Context])
private implicit val context: Context = mock(classOf[Context])
private val repository = mock(classOf[RepositoryInfo])
import helpers._