Compare commits

...

577 Commits

Author SHA1 Message Date
takezoe
753403b4c8 Implement /meta API endpoint 2022-10-30 21:02:23 +09:00
Naoki Takezoe
5a0f9f8bbb Merge 4.38.3 release notes into master 2022-10-30 11:02:49 +09:00
takezoe
8fa22b4de2 Enhance .gitignore 2022-10-30 10:58:07 +09:00
Naoki Takezoe
d17cae16fd Revert "Fix IllegalStateException when returning unknown avatar image (#3158)" (#3179)
This reverts commit a0be02ce2f.
2022-10-30 10:18:49 +09:00
Naoki Takezoe
c4c48962cf Fix an issue that assignees are not saved in PR creation (#3178) 2022-10-30 09:54:33 +09:00
Naoki Takezoe
4140e92f0b Fix an issue that assignees are not saved in PR creation (#3178) 2022-10-30 09:54:18 +09:00
Scala Steward
887e560a1b Update oauth2-oidc-sdk to 10.1 2022-10-28 07:22:22 +09:00
pea-sys
e2d70181e8 png optimization (#3176) 2022-10-27 22:32:08 +09:00
Scala Steward
148c453dbc Update oauth2-oidc-sdk to 10.0 2022-10-24 20:52:36 +09:00
Scala Steward
f6ee9d311d Update thumbnailator to 0.4.18 2022-10-24 08:45:56 +09:00
Scala Steward
35209e43bb Update mockito-core to 4.8.1 2022-10-22 06:50:07 +09:00
Scala Steward
4a3ecf063d Update sbt-assembly to 2.0.0 2022-10-18 06:54:35 +09:00
Naoki Takezoe
4c79101624 Fix duplications in issue labels and assignees (#3168) 2022-10-17 01:00:22 +09:00
Naoki Takezoe
921b01661b Upgrade Scalatra to 2.8.4 (#3165) 2022-10-16 16:49:47 +09:00
Scala Steward
c63301d8e6 Update testcontainers-scala to 0.40.11 2022-10-11 12:22:12 +09:00
Naoki Takezoe
c9ed9d2237 Hide large diffs by default and show on demand (#3157) 2022-10-10 15:49:37 +09:00
Naoki Takezoe
ca55cbe456 Disable scalafmt on compile (#3160) 2022-10-10 03:15:59 +09:00
Scala Steward
d4828613ee Update scala-library to 2.13.10 2022-10-09 05:13:06 +09:00
Naoki Takezoe
a0be02ce2f Fix IllegalStateException when returning unknown avatar image (#3158) 2022-10-08 11:24:03 +09:00
Scala Steward
9b8016a4d5 Update mysql, postgresql to 1.17.5 2022-10-05 06:25:59 +09:00
Scala Steward
8fdd3bfd21 Update tika-core to 2.5.0 2022-10-04 06:11:07 +09:00
Scala Steward
695a061e3c Update sbt, sbt-dependency-tree to 1.7.2 2022-10-03 16:05:29 +09:00
Scala Steward
bd50d9218e Update mysql, postgresql to 1.17.4 2022-09-30 06:13:37 +09:00
Scala Steward
d8f13bc1ce Update json4s-jackson to 4.0.6 2022-09-29 17:14:45 +09:00
Scala Steward
ed84d1a3c9 Update github-api to 1.313 2022-09-27 18:50:29 +09:00
Scala Steward
3c765d879c Update mariadb-java-client to 3.0.8 2022-09-21 01:05:42 +09:00
Naoki Takezoe
4c76b6dd96 Release 4.38.2 (#3141) 2022-09-20 17:52:14 +09:00
Scala Steward
7e66917993 Update scala-library to 2.13.9 2022-09-20 07:35:35 +09:00
Scala Steward
eb0f985399 Update jetty-continuation, jetty-http, ... to 9.4.49.v20220914 2022-09-16 07:13:32 +09:00
Naoki Takezoe
3a2908c3a3 Resurrect assignee icons on the issue list (#3136) 2022-09-13 23:19:38 +09:00
Naoki Takezoe
f13c10859f Fix warnings in AccountService (#3135) 2022-09-13 15:42:43 +09:00
Scala Steward
8e8a314c91 Update oauth2-oidc-sdk to 9.43.1 2022-09-10 07:07:24 +09:00
Naoki Takezoe
dad0478b87 Remove unused import statement (#3134) 2022-09-10 03:06:21 +09:00
Naoki Takezoe
240b34016d Release 4.38.1 (#3133) 2022-09-10 02:43:06 +09:00
yurafuca
2c164d630c Fix comment diff in Chrome 105 (#3131) 2022-09-10 02:06:13 +09:00
Scala Steward
3d12a9038f Update mockito-core to 4.8.0 2022-09-08 06:51:44 +09:00
Scala Steward
71fc02b5cd Update apache-sshd to 2.9.1 2022-09-08 00:03:30 +09:00
Naoki Takezoe
74caaa3d94 Fix Markdown table CSS (#3126) 2022-09-06 00:11:53 +09:00
Naoki Takezoe
e024c12521 Fix HTML rendering of multiple asignees (#3125) 2022-09-05 22:39:36 +09:00
Scala Steward
1d0c364947 Update oauth2-oidc-sdk to 9.43 2022-09-05 18:53:55 +09:00
Naoki Takezoe
88e59405c5 Release 4.38.0 (#3123) 2022-09-04 00:36:03 +09:00
Naoki Takezoe
8160c93c15 Fix ref and ssh_url in webhook payload (#3122) 2022-09-03 13:15:37 +09:00
Naoki Takezoe
3c8113d607 Minor code cleanup for GitRepositoryServlet (#3121) 2022-09-03 12:27:06 +09:00
scala-steward-bot
8e2f421e76 Update markedj to 1.0.17 (#3120) 2022-09-03 12:19:37 +09:00
Scala Steward
56df833b4a Update oauth2-oidc-sdk to 9.42 2022-09-02 06:27:00 +09:00
Naoki Takezoe
119d7d0be0 Revert "Drop unused ActivityComponent (#3024)" (#3117)
This reverts commit bd06e6d4dc.
2022-08-28 03:33:19 +09:00
Scala Steward
d4d2ca19a1 Update oauth2-oidc-sdk to 9.41.1 2022-08-27 07:59:51 +09:00
Naoki Takezoe
4ce5597d67 Remove a link to the demo site from README (#3115) 2022-08-26 14:33:12 +09:00
Scala Steward
17888327b3 Update postgresql to 42.5.0 2022-08-25 07:01:14 +09:00
Naoki Takezoe
997def54ab Set UTF-8 as the default commit charset at the online text editor (#3112) 2022-08-21 22:50:12 +09:00
Naoki Takezoe
a407e75459 Remove unused import (#3111) 2022-08-21 21:52:55 +09:00
Scala Steward
cece53aad9 Update github-api to 1.308 2022-08-19 08:40:16 +09:00
Scala Steward
a2d63bc5f8 Update postgresql to 42.4.2 2022-08-18 07:26:45 +09:00
Scala Steward
0c81c63ab7 Update mockito-core to 4.7.0 2022-08-13 22:35:03 +09:00
Scala Steward
3e8f1eee22 Update oauth2-oidc-sdk to 9.41 2022-08-12 07:52:55 +09:00
Scala Steward
a91d1def45 Update oauth2-oidc-sdk to 9.40 2022-08-09 22:49:48 +09:00
Scala Steward
032e6d1c57 Update oauth2-oidc-sdk to 9.39.1 2022-08-09 17:31:16 +09:00
yurafuca
93eb133cba Fix milestone count (#3103) 2022-08-09 12:28:07 +09:00
Scala Steward
b7b78842da Update mariadb-java-client to 3.0.7 2022-08-05 05:48:20 +09:00
Scala Steward
8100ccaf5f Update postgresql to 42.4.1 2022-08-04 07:13:05 +09:00
Scala Steward
936e1c0e96 Update testcontainers-scala to 0.40.10 2022-07-30 12:07:10 +09:00
Scala Steward
1a7274e475 Update solidbase to 1.0.5 2022-07-25 18:59:59 +09:00
Scala Steward
ac7b83b6ed Update java-diff-utils to 4.12 2022-07-23 12:32:01 +09:00
Scala Steward
e298279032 Update apache-sshd to 2.9.0 2022-07-23 07:55:52 +09:00
Scala Steward
f7e58c28c6 Update oauth2-oidc-sdk to 9.39 2022-07-22 16:52:09 +09:00
Scala Steward
18f3bc4056 Update testcontainers-scala to 0.40.9 2022-07-14 22:32:58 +09:00
Scala Steward
4638f82889 Update oauth2-oidc-sdk to 9.38.1 2022-07-14 05:01:11 +09:00
Naoki Takezoe
4396b6eab5 Horizontal scroll for too wide tables in Markdown (#3087) 2022-07-13 21:24:58 +09:00
Zain Aftab
594ae2bb7e #2983 fixed css for table (#3082) 2022-07-13 08:19:21 +09:00
Scala Steward
429cf54bd4 Update sbt, sbt-dependency-tree to 1.7.1 2022-07-12 14:14:05 +09:00
Scala Steward
274357abc4 Update sbt, sbt-dependency-tree to 1.7.0 2022-07-11 07:24:10 +09:00
Scala Steward
0e21247755 Update oauth2-oidc-sdk to 9.38 2022-07-06 18:52:46 +09:00
Scala Steward
37f2868ff3 Update oauth2-oidc-sdk to 9.37.3 2022-07-04 17:00:26 +09:00
Scala Steward
6a85e25a62 Update mariadb-java-client to 3.0.6 2022-06-30 06:33:58 +09:00
Scala Steward
a91b1c7e79 Update mysql, postgresql to 1.17.3 2022-06-29 18:03:50 +09:00
Scala Steward
bfd9ecd130 Update github-api to 1.307 2022-06-27 18:33:09 +09:00
Scala Steward
ec888e94fd Update sbt-scoverage to 2.0.0 2022-06-26 06:30:23 +09:00
Scala Steward
a19c80bef2 Update jetty-continuation, jetty-http, ... to 9.4.48.v20220622 2022-06-23 07:01:52 +09:00
Scala Steward
7b9ade2698 Update jetty-continuation, jetty-http, ... to 9.4.47.v20220610 2022-06-21 06:25:34 +09:00
Scala Steward
d03f28eeb9 Update tika-core to 2.4.1 2022-06-18 07:22:37 +09:00
Scala Steward
288dd788bc Update org.eclipse.jgit.archive, ... to 5.13.1.202206130422-r 2022-06-13 20:47:46 +09:00
Scala Steward
2069a5a931 Update postgresql to 42.4.0 2022-06-10 02:10:28 +09:00
Scala Steward
8a434cbd15 Update mockito-core to 4.6.1 2022-06-03 07:10:47 +09:00
Scala Steward
faafe49848 Update oauth2-oidc-sdk to 9.37.2 2022-06-02 19:33:26 +09:00
Scala Steward
17a72292ce Update oauth2-oidc-sdk to 9.37.1 2022-06-01 19:03:35 +09:00
Scala Steward
9ae09f1801 Update testcontainers-scala to 0.40.8 2022-06-01 06:54:21 +09:00
Scala Steward
abbba17e4b Update oauth2-oidc-sdk to 9.37 2022-05-31 06:29:58 +09:00
Scala Steward
479d5da17e Update mockito-core to 4.6.0 2022-05-28 07:59:15 +09:00
Scala Steward
dec7890d04 Update oauth2-oidc-sdk to 9.36 2022-05-26 19:45:42 +09:00
Scala Steward
61910a6667 Update mariadb-java-client to 3.0.5 2022-05-26 07:06:52 +09:00
Scala Steward
ac2119ac3b Update postgresql to 42.3.6 2022-05-24 23:05:58 +09:00
Scala Steward
8c71cd7369 Update mysql, postgresql to 1.17.2 2022-05-20 08:20:48 +09:00
porkotron
ec8b6ff18f Implement missing parameters on commits api (#3061)
* Add missing leading slash html_url api path
* Use HEAD as default ref
* Implement missing parameters

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

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

Signed-off-by: dependabot[bot] <support@github.com>

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

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

Signed-off-by: dependabot[bot] <support@github.com>

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

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

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-28 21:55:49 +09:00
Scala Steward
dc16474ed3 Update oauth2-oidc-sdk to 9.31 2022-03-19 19:51:24 +09:00
Scala Steward
dc0f088be0 Update oauth2-oidc-sdk to 9.30 2022-03-19 09:21:48 +09:00
Scala Steward
2718e10044 Update oauth2-oidc-sdk to 9.29 2022-03-17 06:15:25 +09:00
Scala Steward
4213dcb8a2 Update testcontainers-scala to 0.40.3 2022-03-16 11:49:15 +09:00
Scala Steward
39afb31200 Update solidbase to 1.0.4 2022-03-14 06:30:05 +09:00
dependabot[bot]
8e1ce1c96a Bump actions/checkout from 2 to 3 (#3011)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

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

Signed-off-by: dependabot[bot] <support@github.com>

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-30 09:24:19 +09:00
kenji yoshida
d0f4f82a0f pin jgit 5.x 2021-11-30 09:23:36 +09:00
Naoki Takezoe
1dcbf386b1 Fix SSH server handling (#2930) 2021-11-28 17:39:31 +09:00
Naoki Takezoe
414afd285c Remove unused imports 2021-11-28 15:14:51 +09:00
Naoki Takezoe
35b645d8b5 Merge branch 'keywordsalad-custom-ssh-url' 2021-11-28 15:08:04 +09:00
Naoki Takezoe
b3cba53866 Reformat GitCommandSpec 2021-11-28 15:02:29 +09:00
Naoki Takezoe
a4773bb3ca Merge branch 'master' into custom-ssh-url 2021-11-28 14:56:55 +09:00
Naoki Takezoe
863d8a4af5 Bump apache-sshd to 2.7.0 (#2929) 2021-11-28 14:47:58 +09:00
Scala Steward
3fccd7b53c Update oauth2-oidc-sdk to 9.20 2021-11-25 20:46:24 +09:00
Scala Steward
dd2760eaf7 Update github-api to 1.301 2021-11-24 12:10:25 +09:00
Scala Steward
824bafa739 Update github-api to 1.300 2021-11-22 11:15:11 +09:00
kaz-on
60cdaec05f Fix line highlighting in dark themes (#2921) 2021-11-22 01:31:52 +09:00
kaz-on
c204a435b3 Remove unnecessary loading of google-code-prettify (#2922) 2021-11-22 01:31:16 +09:00
Scala Steward
37accd92d6 Update mockito-core to 4.1.0 2021-11-20 07:35:42 +09:00
Scala Steward
01fd0ee1f0 Update sbt-scalafmt to 2.4.4 2021-11-19 07:19:22 +09:00
Scala Steward
fab1c74473 Update testcontainers-scala to 0.39.12 2021-11-15 06:14:54 +09:00
Scala Steward
0d8fcfd28d Update logback-classic to 1.2.7 2021-11-12 03:45:11 +09:00
Naoki Takezoe
b91a7c32a6 Relax max length limitation for WebHook URLs (#2915) 2021-11-11 01:39:12 +09:00
Naoki Takezoe
7f665c649b Fix duplicated diff issue in exporting patch feature(#2913) 2021-11-05 19:31:52 +09:00
Scala Steward
dcbadb4071 Update sbt-scoverage to 1.9.2 2021-11-03 02:44:03 +09:00
Mira
e3096d15ff resolve #2907 API - not milestone data in issue list (#2908) 2021-11-03 01:23:18 +09:00
Scala Steward
a83c24e7b3 Update scala-library to 2.13.7 2021-11-02 07:15:21 +09:00
Scala Steward
73457c9658 Update testcontainers-scala to 0.39.11 2021-11-02 06:59:06 +09:00
Naoki Takezoe
cfc8d9f3f1 Relax max password length limitation to 40 (#2906) 2021-10-31 11:30:39 +09:00
Scala Steward
8f423b83ea Update postgresql to 42.3.1 2021-10-30 07:15:00 +09:00
Scala Steward
1e7ac532b6 Update testcontainers-scala to 0.39.10 2021-10-28 12:17:14 +09:00
Scala Steward
0fd1db4596 Update mysql, postgresql to 1.16.2 2021-10-23 06:48:28 +09:00
Scala Steward
0755b7ab7f Update testcontainers-scala to 0.39.9 2021-10-22 07:58:09 +09:00
Scala Steward
052382e5c4 Update github-api to 1.135 2021-10-22 07:22:16 +09:00
Scala Steward
f44a63cec1 Update oauth2-oidc-sdk to 9.19 2021-10-20 22:36:12 +09:00
Scala Steward
603d67354a Update mysql, postgresql to 1.16.1 2021-10-19 22:06:56 +09:00
Scala Steward
aafa423b9f Update postgresql to 42.3.0 2021-10-19 06:59:29 +09:00
Mészáros István
2da9d0a801 Add HTTPS support for JettyLauncher (#2896) 2021-10-14 11:27:26 +09:00
Scala Steward
b60c112a74 Update sbt-scoverage to 1.9.1 2021-10-11 19:04:31 +09:00
Scala Steward
843ed6df37 Update mockito-core to 4.0.0 2021-10-07 23:48:03 +09:00
Scala Steward
0f0a849677 Update jetty-continuation, jetty-http, ... to 9.4.44.v20210927 2021-10-01 12:15:06 +09:00
Scala Steward
682901ccbb Update oauth2-oidc-sdk to 9.18 2021-09-30 17:50:08 +09:00
Naoki Takezoe
048fdb8837 Remove unnecessary workaround for scala-xml version conflict (#2888) 2021-09-27 03:00:15 +09:00
scala-steward-bot
3353616789 Update scalatra, scalatra-forms, ... to 2.8.2 (#2886) 2021-09-25 19:07:58 +09:00
scala-steward-bot
b6cb4c865f Update scalatra, scalatra-forms, ... to 2.8.1 (#2885) 2021-09-25 16:00:15 +09:00
kenji yoshida
1fcfd093f7 add jdk 17 test 2021-09-25 10:51:36 +09:00
Scala Steward
3f27c6e733 Update postgresql to 42.2.24 2021-09-23 21:00:09 +09:00
Scala Steward
b6bd9bfc3b Update oauth2-oidc-sdk to 9.17 2021-09-22 07:27:44 +09:00
Scala Steward
6c392f0056 Update oauth2-oidc-sdk to 9.16 2021-09-21 21:18:30 +09:00
Scala Steward
9a38de9a23 Update testcontainers-scala to 0.39.8 2021-09-20 19:57:18 +09:00
Scala Steward
8883600090 Update sbt-scoverage to 1.9.0 2021-09-17 01:27:52 +09:00
Naoki Takezoe
ab822a3c27 Fix Wiki page editing bug when over 100 pages (#2869) 2021-09-13 00:48:38 +09:00
Scala Steward
0e4d64de23 Update org.eclipse.jgit.archive, ... to 5.13.0.202109080827-r 2021-09-12 23:08:48 +09:00
Scala Steward
fbc6bd36bd Update logback-classic to 1.2.6 2021-09-10 13:09:30 +09:00
Naoki Takezoe
ed90ca2dce Bump gitbucket-pages-plugin to 1.10.0 (#2856) 2021-09-10 09:03:51 +09:00
Scala Steward
537ef92149 Update github-api to 1.133 2021-09-09 15:56:16 +09:00
Scala Steward
d51afa7d40 Update java-diff-utils to 4.11 2021-09-08 21:27:57 +09:00
Scala Steward
975cffff48 Update sbt-assembly to 1.1.0 2021-09-01 23:08:42 +09:00
Scala Steward
d92e9c00e8 Update testcontainers-scala to 0.39.7 2021-08-31 14:52:37 +09:00
KN4CK3R
12d72cbb19 Add support for "all" in issue list API (#2859) 2021-08-31 00:38:38 +09:00
Logan McGrath
e7a6f0930b Closes #2818 - Supporting custom SSH URL's when hosting behind a proxy 2021-08-29 16:38:06 -07:00
Scala Steward
d8e03bed1f Update mockito-core to 3.12.4 2021-08-26 08:43:19 +09:00
Scala Steward
f48c087cd8 Update mockito-core to 3.12.3 2021-08-25 08:39:46 +09:00
Scala Steward
917663e0df Update mockito-core to 3.12.2 2021-08-25 06:11:05 +09:00
Scala Steward
556ddbc926 Update tika-core to 2.1.0 2021-08-24 23:07:23 +09:00
Scala Steward
1c6f37b8e8 Update testcontainers-scala to 0.39.6 2021-08-23 15:26:54 +09:00
Scala Steward
720a329a50 Update oauth2-oidc-sdk to 9.15 2021-08-21 07:09:24 +09:00
Scala Steward
220a8f076a Update mockito-core to 3.12.1 2021-08-21 07:09:08 +09:00
Scala Steward
43be8333c7 Update oauth2-oidc-sdk to 9.14 2021-08-20 21:08:36 +09:00
Scala Steward
08706ab4df Update mockito-core to 3.12.0 2021-08-20 08:04:50 +09:00
Naoki Takezoe
b1196657e0 Release GitBucket 4.36.2 2021-08-16 01:11:21 +09:00
Naoki Takezoe
334bd0c919 Escape user name in avatar image tag (#2845) 2021-08-16 01:06:16 +09:00
Scala Steward
cf0f896972 Update mariadb-java-client to 2.7.4 2021-08-12 07:10:24 +09:00
Scala Steward
d21ca3ff8a Update oauth2-oidc-sdk to 9.12 2021-08-11 19:08:07 +09:00
xuwei-k
83f1f16de7 remove == true 2021-08-09 16:56:55 +09:00
xuwei-k
0fa2ccf107 use keys 2021-08-09 16:48:03 +09:00
xuwei-k
18e3dd431b use until instead of to 2021-08-09 16:48:03 +09:00
xuwei-k
f25dee2249 .getOrElse(None) => .flatten 2021-08-09 16:48:03 +09:00
kenji yoshida
575ffa9580 avoid Array.toString 2021-08-09 16:43:09 +09:00
xuwei-k
f17af5aeb0 use forall or exists instead of map(f).getOrElse 2021-08-09 16:40:37 +09:00
xuwei-k
639f153589 use sizeIs 2021-08-09 16:38:16 +09:00
xuwei-k
fb07098c13 map(f).flatten => flatMap 2021-08-09 16:30:40 +09:00
xuwei-k
74fc08b039 use foreach instead of map 2021-08-09 16:28:15 +09:00
xuwei-k
2776e00004 fix typo 2021-08-09 16:25:55 +09:00
xuwei-k
5932fce303 remove semicolons 2021-08-09 16:12:41 +09:00
xuwei-k
39c9fc4261 remove redundant toString 2021-08-09 16:10:23 +09:00
kenji yoshida
89ea4509a3 use SAM conversion 2021-08-09 16:08:07 +09:00
kenji yoshida
d19d838ead getOrElse(null) => orNull 2021-08-09 16:05:22 +09:00
kenji yoshida
633a2699a8 find(f).isDefined => exists(f) 2021-08-09 16:03:22 +09:00
kenji yoshida
e79bca4a3c remove unnecessary local variable initial values 2021-08-09 16:01:02 +09:00
kenji yoshida
f8ab480d20 remove unused import 2021-08-09 15:55:52 +09:00
kenji yoshida
a14129e340 use diamond operator 2021-08-09 15:55:03 +09:00
Scala Steward
5ec39df6e0 Update oauth2-oidc-sdk to 9.11 2021-08-02 07:07:47 +09:00
kenji yoshida
956e0c6321 fix warning in build.sbt
02a0cfa0a6/io/src/main/scala/sbt/io/IO.scala (L611-L630)

```
/home/runner/work/gitbucket/gitbucket/build.sbt:205: warning: method jar in object IO is deprecated (since 1.3.2): Please specify whether to use a static timestamp
  IO jar (contentMappings.map { case (file, path) => (file, path.toString) }, outputFile, manifest)
     ^
```
2021-07-31 16:14:15 +09:00
Scala Steward
0660a9203a Update oauth2-oidc-sdk to 9.10.2 2021-07-27 23:30:19 +09:00
kenji yoshida
1b660272a1 prepare Scala 3 2021-07-27 07:23:01 +09:00
Scala Steward
d9ef9b874d Update logback-classic to 1.2.5 2021-07-27 06:56:21 +09:00
Scala Steward
7824f796ee Update json4s-jackson to 4.0.3 2021-07-27 03:01:31 +09:00
Scala Steward
838a8d4c7b Update scalatra, scalatra-forms, ... to 2.8.0 (#2834)
Co-authored-by: kenji yoshida <6b656e6a69@gmail.com>
2021-07-25 09:30:05 +09:00
Scala Steward
8db9f77f91 Update github-api to 1.132 2021-07-23 10:22:45 +09:00
Naoki Takezoe
4f82a469d9 Release GitBucket 4.36.1 2021-07-22 14:30:44 +09:00
Naoki Takezoe
0f6ae8559b Bump gist-plugin to 4.21.0 (#2832) 2021-07-22 14:27:34 +09:00
Scala Steward
3f4b2eec35 Update logback-classic to 1.2.4 (#2826) 2021-07-21 13:14:38 +09:00
Lilian BENOIT
a020601d3a exclude dependency org.slf4j from HikariCP (#2830) 2021-07-21 09:17:51 +09:00
Scala Steward
d511205588 Update tika-core to 2.0.0 2021-07-21 07:19:21 +09:00
Scala Steward
fc896b2ea1 Update mysql, postgresql to 1.16.0 2021-07-21 07:09:17 +09:00
Pedro Rijo
3819670535 Update sbt-dependency-graph plugin installation (#2828)
* Update sbt-dependency-graph plugin installation

According with https://github.com/sbt/sbt-dependency-graph#usage-instructions, for sbt >= 1.4.x we should install the plugin with `addDependencyTreePlugin` instead of `addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.10.0-RC1")`

* Fix scalafmt
2021-07-20 18:56:07 +09:00
Lilian BENOIT
067669a18c unprotect plugin-assets (#2825) 2021-07-18 18:31:00 +09:00
Naoki Takezoe
bef7cee8db Fix typo 2021-07-17 17:34:51 +09:00
Naoki Takezoe
cb9f56fc22 Release GitBucket 4.36.0 2021-07-17 17:26:18 +09:00
kenji yoshida
719a521f06 Update build.yml 2021-07-17 13:00:44 +09:00
Scala Steward
413754976f Update oauth2-oidc-sdk to 9.10.1 2021-07-15 23:22:47 +09:00
Scala Steward
4530cfc068 Update commons-io to 2.11.0 2021-07-13 23:11:16 +09:00
Scala Steward
fcc7d6da2d Update commons-compress to 1.21 2021-07-13 07:51:46 +09:00
Scala Steward
33e565d029 Update sbt to 1.5.5 2021-07-12 23:08:02 +09:00
Naoki Takezoe
6a1da37e9a Ignore submodules in repository archive downloading (#2817) 2021-07-11 16:55:44 +09:00
Naoki Takezoe
897cc5eea4 Support tags in the branch selector (#2811) 2021-07-11 16:05:41 +09:00
Naoki Takezoe
5cbea281af Add new option to disable XSS protection in image rendering (#2806) 2021-07-10 12:51:25 +09:00
Scala Steward
e74db57b75 Update sbt-scalafmt to 2.4.3 2021-07-09 09:08:26 +09:00
Scala Steward
6b2d124d09 Update jetty-continuation, jetty-http, ... to 9.4.43.v20210629 2021-07-07 11:07:23 +09:00
Scala Steward
7b3c77db8c Update tika-core to 1.27 2021-07-07 07:12:36 +09:00
Scala Steward
1bcab5c2c7 Update postgresql to 42.2.23 2021-07-07 07:11:43 +09:00
Scala Steward
16020e9a7d Update oauth2-oidc-sdk to 9.9.1 2021-07-03 20:05:42 +09:00
Naoki Takezoe
dfd3e22986 Handle slash contained tag name correctly in releases (#2805) 2021-06-27 03:32:18 +09:00
Naoki Takezoe
48d7a20fb7 Keep default merge strategy selection (#2804) 2021-06-27 02:40:17 +09:00
Scala Steward
04108a0a1a Update mockito-core to 3.11.2 2021-06-22 06:16:55 +09:00
Naoki Takezoe
e0bab59846 Fix Repository Contents API issues (#2802) 2021-06-21 00:55:03 +09:00
Naoki Takezoe
1004c83bc4 Drop the use of SyntaxSugars.defining from codebase (#2800) 2021-06-20 09:48:50 +09:00
ShalokShalom
502bb450e3 correct typo (#2801)
Gitter is a name, and since that capitalized
2021-06-20 02:32:21 +09:00
Scala Steward
fb03b0bec7 Update postgresql to 42.2.22 2021-06-17 06:21:51 +09:00
Scala Steward
2c3a1c9c92 Update github-api to 1.131 2021-06-16 08:54:24 +09:00
Scala Steward
73b7ee2d57 Update sbt to 1.5.4 2021-06-14 19:50:44 +09:00
Scala Steward
71df890f39 Update commons-io to 2.10.0 2021-06-14 07:09:47 +09:00
Scala Steward
1fdac404b1 Update mockito-core to 3.11.1 2021-06-12 01:12:18 +09:00
Scala Steward
47be1e60bb Update postgresql to 42.2.21 2021-06-11 07:53:15 +09:00
Scala Steward
1c05f50954 Update jetty-continuation, jetty-http, ... to 9.4.42.v20210604 2021-06-09 09:10:08 +09:00
Scala Steward
ce26a48fd7 Update org.eclipse.jgit.archive, ... to 5.12.0.202106070339-r 2021-06-08 06:53:34 +09:00
Scala Steward
26e0838ae0 Update oauth2-oidc-sdk to 9.9 2021-06-07 21:08:37 +09:00
xuwei-k
6b5b0db20a avoid enum
prepare Scala 3
2021-06-07 20:19:57 +09:00
xuwei-k
5973c8cb85 prepare Scala 3 2021-06-07 20:16:18 +09:00
Scala Steward
f683439c73 Update sbt-assembly to 1.0.0 2021-06-07 19:24:14 +09:00
xuwei-k
625f3655ec prepare Scala 3 2021-06-07 17:35:14 +09:00
Scala Steward
4a5ab1ae71 Update oauth2-oidc-sdk to 9.8.1 2021-06-05 19:25:06 +09:00
kenji yoshida
3bcae2c6d2 Create CODEOWNERS 2021-06-04 11:54:06 +09:00
Scala Steward
4441eff657 Update sbt to 1.5.3 2021-06-04 11:51:42 +09:00
Scala Steward
9fed83e222 Update oauth2-oidc-sdk to 9.8 2021-06-04 11:51:33 +09:00
Scala Steward
42ea9bac65 Update github-api to 1.130 2021-06-04 11:51:27 +09:00
Scala Steward
2aba827772 Update mockito-core to 3.11.0 2021-06-04 11:51:23 +09:00
dependabot[bot]
31e84bd543 Bump actions/cache from 2.1.5 to 2.1.6
Bumps [actions/cache](https://github.com/actions/cache) from 2.1.5 to 2.1.6.
- [Release notes](https://github.com/actions/cache/releases)
- [Commits](https://github.com/actions/cache/compare/v2.1.5...v2.1.6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-04 11:51:11 +09:00
Scala Steward
ba471b8bf7 Update sbt-scoverage to 1.8.2 (#2780) 2021-05-30 08:46:40 +09:00
Scala Steward
90ec95494d Update commons-io to 2.9.0 (#2778) 2021-05-26 16:22:49 +09:00
Scala Steward
e72dbad765 Update testcontainers-scala to 0.39.5 (#2777) 2021-05-26 16:22:35 +09:00
Scala Steward
ed63ea4674 Update github-api to 1.129 (#2776) 2021-05-26 16:22:25 +09:00
Scala Steward
559a52404f Update oauth2-oidc-sdk to 9.7 2021-05-23 09:41:40 +09:00
Scala Steward
5e80848524 Update oauth2-oidc-sdk to 9.5.3 2021-05-22 21:24:38 +09:00
Scala Steward
055a4f0798 Update oauth2-oidc-sdk to 9.5.2 (#2771) 2021-05-21 02:32:29 +09:00
Scala Steward
6f432e537c Update sbt-scoverage to 1.8.1 (#2772) 2021-05-21 02:32:11 +09:00
Scala Steward
a2abbbbd63 Update scala-library to 2.13.6 2021-05-18 11:53:45 +09:00
Scala Steward
16707b99e6 Update jetty-continuation, jetty-http, ... to 9.4.41.v20210516 2021-05-18 11:53:36 +09:00
Scala Steward
80a4ed62cc Update org.eclipse.jgit.archive, ... to 5.11.1.202105131744-r 2021-05-17 07:06:13 +09:00
Scala Steward
b713a334f0 Update mariadb-java-client to 2.7.3 2021-05-14 06:45:53 +09:00
Scala Steward
ec6264c04c Update testcontainers-scala to 0.39.4 (#2765)
Co-authored-by: Naoki Takezoe <takezoe@gmail.com>
2021-05-14 06:45:33 +09:00
Scala Steward
44bb1ab565 Update mockito-core to 3.10.0 (#2766) 2021-05-13 02:35:27 +09:00
Scala Steward
523ee025ac Update sbt-scoverage to 1.8.0 (#2764) 2021-05-11 23:13:31 +09:00
Scala Steward
d193c6399c Update sbt-scoverage to 1.7.3 2021-05-11 12:16:17 +09:00
Scala Steward
8c9715f4d9 Update oauth2-oidc-sdk to 9.5.1 2021-05-11 12:16:13 +09:00
Scala Steward
3bbdcd5f14 Update sbt to 1.5.2 2021-05-11 12:16:05 +09:00
Scala Steward
c183d3b0b5 Update sbt-scoverage to 1.7.2 (#2759) 2021-05-03 20:10:28 +09:00
Scala Steward
b6f0c6bf57 Update oauth2-oidc-sdk to 9.5 2021-05-02 15:08:38 +09:00
Scala Steward
8719e834f3 Update java-diff-utils to 4.10 (#2754) 2021-04-29 10:21:10 +09:00
Scala Steward
f23b8c6d61 Update sbt-scoverage to 1.7.0 (#2753) 2021-04-29 10:21:00 +09:00
Scala Steward
d46be31ea8 Update oauth2-oidc-sdk to 9.4.1 2021-04-27 06:53:40 +09:00
Scala Steward
9bb36e4cb2 Update sbt to 1.5.1 2021-04-27 06:53:29 +09:00
Scala Steward
2780fb9605 Update oauth2-oidc-sdk to 9.4 2021-04-23 08:58:10 +09:00
Scala Steward
b69e2d299a Update github-api to 1.128 2021-04-23 08:57:45 +09:00
Scala Steward
a07bd7b1a4 Update postgresql to 42.2.20 2021-04-23 08:57:33 +09:00
dependabot[bot]
d605455678 Bump actions/cache from v2.1.4 to v2.1.5
Bumps [actions/cache](https://github.com/actions/cache) from v2.1.4 to v2.1.5.
- [Release notes](https://github.com/actions/cache/releases)
- [Commits](https://github.com/actions/cache/compare/v2.1.4...1a9e2138d905efd099035b49d8b7a3888c653ca8)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-20 12:31:14 +09:00
Naoki Takezoe
781b1169a2 Use account name as pusher for commits by WebUI (#2745) 2021-04-17 13:08:42 +09:00
Scala Steward
80112d3437 Update mysql, postgresql to 1.15.3 2021-04-17 12:42:41 +09:00
Scala Steward
131af7f4fa Update jetty-continuation, jetty-http, ... to 9.4.40.v20210413 2021-04-17 12:42:15 +09:00
Naoki Takezoe
1ceac4ef9f Minor fix for WebHook testing UI (#2744) 2021-04-17 12:40:14 +09:00
Jesse Gorzinski
b7aa3e69f4 (bugfix) password msg when editing an LDAP profile (#2732) 2021-04-15 16:55:46 +09:00
Scala Steward
62231c41b2 Update oauth2-oidc-sdk to 9.3.3 2021-04-14 19:07:45 +09:00
dependabot[bot]
6c0a33712b Bump actions/setup-java from v1 to v2 (#2736)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from v1 to v2.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v1...8764a52df183aa0ccea74521dfd9d506ffc7a19a)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: kenji yoshida <6b656e6a69@gmail.com>
2021-04-14 09:50:22 +00:00
Scala Steward
e192624489 Update github-api to 1.127 (#2735) 2021-04-10 12:34:09 +09:00
Scala Steward
68609e44de Update mockito-core to 3.9.0 (#2733) 2021-04-08 11:36:47 +09:00
Naoki Takezoe
c8e3af6072 Abolish the pull request title length limitation (#2731) 2021-04-07 12:49:58 +09:00
Naoki Takezoe
9f7048a19c Eliminate context.loginAccount.get() by adding context.withLoginAccount() (#2729) 2021-04-07 12:19:47 +09:00
Scala Steward
cbdc0528ca Update sbt to 1.5.0 2021-04-06 13:22:29 +09:00
Naoki Takezoe
444facc95b Drop unused syntax sugars (#2728) 2021-04-05 09:54:56 +09:00
Naoki Takezoe
ed40ddaa45 Remove unused import statements (#2726) 2021-04-04 23:35:22 +09:00
Naoki Takezoe
43c629b3e5 Fix repository creation permission check (#2725) 2021-04-04 10:22:34 +09:00
onukura
0d2c156579 Add function to refer issue/PR of other repositories within issue/PR conversations (#2608) 2021-04-03 11:14:36 +09:00
Scala Steward
ab218f8540 Update tika-core to 1.26 2021-03-30 14:51:43 +09:00
Scala Steward
4b8a375890 Update oauth2-oidc-sdk to 9.3 2021-03-30 14:51:34 +09:00
Scala Steward
9c3a5f3f0a Update jetty-continuation, jetty-http, ... to 9.4.39.v20210325 2021-03-30 14:51:19 +09:00
Scala Steward
1bcfbec29c Update oauth2-oidc-sdk to 9.2.4 2021-03-26 12:35:23 +09:00
Scala Steward
781940436a Update github-api to 1.125 (#2717) 2021-03-26 12:09:23 +09:00
kaz-on
7f226a6ab1 Fixed file names passed to syntax highlighter in diff view (#2716) 2021-03-25 07:45:35 +09:00
Shreejan Shrestha
f538950d64 Solve repository deletion error on Windows (#2711)
- Added function to delete given folder recursively.
- Used the function for deleting the folder, while deleting repository.

fixes #2628
2021-03-23 09:06:38 +09:00
Naoki Takezoe
142be65893 Revert workaround for timestamp issue in war file creation (#2714) 2021-03-23 08:56:33 +09:00
Scala Steward
8b01e5bc1f Update github-api to 1.124 2021-03-21 11:08:42 +09:00
Scala Steward
bde0aab207 Update oauth2-oidc-sdk to 9.2.3 2021-03-21 11:08:27 +09:00
Scala Steward
31ecfef1c9 Update sbt-twirl to 1.5.1 2021-03-21 11:07:36 +09:00
Scala Steward
0fdae4c334 Update org.eclipse.jgit.archive, ... to 5.11.0.202103091610-r 2021-03-16 20:13:23 +09:00
xuwei-k
3ea7de974b add explicit types for implicit val
prepare Scala 3
2021-03-14 10:53:50 +09:00
kenji yoshida
34a7990d1d use slash syntax 2021-03-14 10:32:58 +09:00
Scala Steward
709d759d09 Update thumbnailator to 0.4.14 2021-03-14 08:10:51 +09:00
Scala Steward
2d47252b27 Update sbt to 1.4.9 2021-03-14 08:08:45 +09:00
xuwei-k
8586e141fc avoid do-while
prepare Scala 3

https://dotty.epfl.ch/docs/reference/dropped-features/do-while.html
2021-03-10 12:10:30 +09:00
Scala Steward
e3887e6872 Update sbt to 1.4.8 2021-03-10 11:54:48 +09:00
Scala Steward
fd658c35ee Update oauth2-oidc-sdk to 9.2.2 2021-03-07 13:19:34 +09:00
Scala Steward
ca4c1b237f Update HikariCP to 4.0.3 2021-03-07 13:19:00 +09:00
Scala Steward
92b17fd719 Update json4s-jackson to 3.6.11 (#2699) 2021-03-03 15:49:19 +09:00
Naoki Takezoe
b4453531a3 Update auto_update.md 2021-03-02 20:58:58 +09:00
Naoki Takezoe
de72a9223f Update build.md 2021-03-02 20:48:58 +09:00
Scala Steward
ae26e3031e Update jetty-continuation, jetty-http, ... to 9.4.38.v20210224 2021-02-27 13:15:35 +09:00
Scala Steward
c26b41cfc2 Update github-api to 1.123 2021-02-27 13:15:15 +09:00
Scala Steward
2c4ee664ab Update oauth2-oidc-sdk to 9.2.1 2021-02-26 19:16:36 +09:00
kenji yoshida
911cd45965 ignore jetty 11.x and 10.x updates 2021-02-26 07:19:51 +09:00
Scala Steward
320cc58c62 Update testcontainers-scala to 0.39.3 (#2695) 2021-02-25 17:58:01 +09:00
ziggystar
cfa1638e3c Update README.md (#2694)
## Developer information is hard to find

I've been looking for instructions on how to build gitbucket a couple of times, and I've never found them until takzoe pointed me towards `doc/build.md`. After knowing about this, I read through the readme once again, looking for such a link, and then I've found it as the last sentence within a section named `Features`.

What I've been looking for:
 - either a file named `INSTALL.md`, `BUILD.md` in `/`
 - or a section (or a link) in the main readme

I've put the link to `doc/readme.md` into a separate section "Building and Development" and added a short summary. I'm not so sure about the order of the new section and "Plugins".
2021-02-25 06:53:09 +09:00
Scala Steward
27264bf59a Update testcontainers-scala to 0.39.2 (#2693) 2021-02-24 23:25:22 +09:00
Scala Steward
935283272e Update scala-library to 2.13.5 2021-02-23 17:00:52 +09:00
Scala Steward
ea440294b1 Update oauth2-oidc-sdk to 9.2 2021-02-23 10:32:29 +09:00
Scala Steward
df8435bf6a Update jetty-continuation, jetty-http, ... to 9.4.37.v20210219 2021-02-23 10:31:29 +09:00
Scala Steward
4fe019b11f Update mockito-core to 3.8.0 2021-02-23 10:31:12 +09:00
kenji yoshida
538c79705a updates.includeScala = true 2021-02-23 10:26:50 +09:00
onukura
e94d972741 Add create and push webhooks call for branch creation via GUI. (#2624) 2021-02-22 03:38:43 +09:00
Naoki Takezoe
c58a9aca6a Links to files and lines in the diff view (#2685) 2021-02-21 23:33:53 +09:00
solsticesurfer
a6612e8522 Prevent execution of empty SQL strings during database import (#2664)
Co-authored-by: Naoki Takezoe <takezoe@gmail.com>
2021-02-21 17:10:06 +09:00
Naoki Takezoe
bd455b9a0a Update authenticator.md 2021-02-21 14:06:04 +09:00
Naoki Takezoe
122853f1f6 Fix issue / pull request view templates (#2684) 2021-02-21 08:06:11 +09:00
Naoki Takezoe
debbc21bf3 Upgrade java-diff-utils (#2678) 2021-02-20 18:27:15 +09:00
Scala Steward
d991fdcbd9 Update postgresql to 42.2.19 (#2682) 2021-02-20 17:44:35 +09:00
Scala Steward
78cc8f14fd Update commons-net to 3.8.0 (#2683) 2021-02-20 17:44:23 +09:00
Naoki Takezoe
09f1a06267 Update authenticator.md 2021-02-20 17:41:50 +09:00
Naoki Takezoe
6fb910a939 Update authenticator.md 2021-02-20 17:41:13 +09:00
Naoki Takezoe
9e891a0168 Fix typo 2021-02-20 17:40:30 +09:00
Naoki Takezoe
0bc77ce453 Fix typo 2021-02-20 17:40:04 +09:00
kenji yoshida
a46c821673 use slash syntax instead of old "in"
https://github.com/sbt/sbt/pull/6309
2021-02-17 15:29:21 +09:00
Scala Steward
cae843d18f Update sbt-pgp to 2.1.2 (#2681)
Co-authored-by: Naoki Takezoe <takezoe@gmail.com>
2021-02-16 18:55:11 +09:00
Scala Steward
40ffc689b0 Update testcontainers-scala to 0.39.1 2021-02-15 11:15:09 +09:00
Scala Steward
65afbd25b8 Update junit to 4.13.2 2021-02-14 09:31:50 +09:00
Scala Steward
458b30610f Update HikariCP to 4.0.2 2021-02-13 18:22:22 +09:00
Scala Steward
a89608a2e2 Update testcontainers to 1.15.2 2021-02-12 11:22:40 +09:00
dependabot[bot]
0f45311d5e Bump actions/cache from v2 to v2.1.4
Bumps [actions/cache](https://github.com/actions/cache) from v2 to v2.1.4.
- [Release notes](https://github.com/actions/cache/releases)
- [Commits](https://github.com/actions/cache/compare/v2...26968a09c0ea4f3e233fdddbafd1166051a095f6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-09 18:19:41 +09:00
Jason Southwell
447ed6ce88 As is, the unsuspend API cannot unsuspend since it's not searching suspended accounts. This should allow the api to function without getting a 404. (#2660)
Co-authored-by: Jason Southwell <jsouthwell@sivv.com>
2021-02-07 22:55:00 +09:00
Scala Steward
ab358a10bc Update oauth2-oidc-sdk to 9.1 2021-02-06 08:00:20 +09:00
Scala Steward
5148851d49 Update testcontainers-scala to 0.39.0 2021-02-06 08:00:05 +09:00
Scala Steward
068ecd30fe Update testcontainers-scala to 0.38.9 2021-02-06 03:19:26 +09:00
Scala Steward
5396c0ee58 Update sbt to 1.4.7 2021-02-06 03:19:10 +09:00
Scala Steward
2328c1878b Update mariadb-java-client to 2.7.2 2021-01-31 15:49:57 +09:00
Scala Steward
bc9a6f464a Update oauth2-oidc-sdk to 8.36 2021-01-31 15:49:47 +09:00
Scala Steward
4613fd4cb4 Update oauth2-oidc-sdk to 8.35 2021-01-28 21:41:02 +09:00
Scala Steward
ff89c8805c Update ec4j-core to 0.3.0 2021-01-27 19:18:05 +09:00
Scala Steward
95d98c525b Update sbt-scalafmt to 2.4.2 2021-01-27 19:17:41 +09:00
Scala Steward
bc78a38570 Update postgresql to 42.2.18
Co-authored-by: kenji yoshida <6b656e6a69@gmail.com>
2021-01-27 11:28:19 +09:00
Scala Steward
8db2398dd8 Update mariadb-java-client to 2.7.1 2021-01-27 11:03:16 +09:00
Scala Steward
e073ca74da Update oauth2-oidc-sdk to 8.34.2 2021-01-27 11:03:09 +09:00
Scala Steward
50e899bf83 Update ec4j-core to 0.0.4 2021-01-27 11:02:56 +09:00
Scala Steward
5ddd6234e3 Update cache2k-all to 1.6.0.Final 2021-01-27 11:02:36 +09:00
Scala Steward
84fa11b5df Update jetty to 9.4.36.v20210114 2021-01-27 11:01:57 +09:00
Scala Steward
f51db60fc7 Update mockito-core to 3.7.7 2021-01-27 11:01:14 +09:00
Scala Steward
351e72e8c2 Update sbt-scalafmt to 2.0.7 2021-01-27 11:01:00 +09:00
Scala Steward
a0f54b0b8e Update tika-core to 1.25 2021-01-26 20:49:36 +09:00
Scala Steward
4c491e40c7 Update httpclient to 4.5.13 2021-01-26 20:49:19 +09:00
Scala Steward
54440d9cde Update thumbnailator to 0.4.13 2021-01-26 20:48:42 +09:00
Scala Steward
ea8333844c Update org.eclipse.jgit.archive, ... to 5.10.0.202012080955-r 2021-01-26 20:48:24 +09:00
Scala Steward
fc34df7008 Update github-api to 1.122 2021-01-26 20:48:05 +09:00
Scala Steward
d49d62edfc Update junit to 4.13.1 2021-01-26 20:47:47 +09:00
Scala Steward
246e2e2de4 Update sbt-pgp to 2.1.1 2021-01-26 12:28:23 +09:00
Scala Steward
83b48d26ce Update HikariCP to 4.0.1 2021-01-26 12:11:11 +09:00
Scala Steward
3d40094925 Update commons-net to 3.7.2 2021-01-26 12:10:56 +09:00
Scala Steward
2d7e8526ed Update testcontainers-scala to 0.38.8 2021-01-26 11:39:45 +09:00
Scala Steward
7768f4c4dd Update sbt-scalafmt to 2.0.0 (#2631)
Co-authored-by: kenji yoshida <6b656e6a69@gmail.com>
2021-01-26 10:46:32 +09:00
kenji yoshida
e43fabf895 set timeout-minutes 2021-01-26 10:21:43 +09:00
Scala Steward
7cf83979a6 Update oauth2-oidc-sdk to 8.34.1 2021-01-26 10:19:16 +09:00
Scala Steward
1ecc30c570 Update typesafe:config to 1.4.1 2021-01-26 10:18:56 +09:00
Scala Steward
ee0721f40d Update sbt-pgp to 1.1.2-1 2021-01-26 10:18:31 +09:00
kenji yoshida
07836edf84 Create .scala-steward.conf 2021-01-26 07:36:12 +09:00
Naoki Takezoe
bfbe40e027 Release GitBucket 4.35.3 (#2626)
* Update version to 4.35.3
* Update README and CHANGELOG
2021-01-14 11:22:13 +09:00
Naoki Takezoe
842d3b6185 Fix timestamp which is set to create war file to epoch millis (#2627) 2021-01-13 22:45:03 +09:00
Naoki Takezoe
086437ca4f Set time to create war instead of specifying SOURCE_DATE_EPOCH (#2625) 2021-01-13 11:14:28 +09:00
Naoki Takezoe
6e48435f38 Add screenshot to README 2021-01-10 22:01:47 +09:00
Naoki Takezoe
3b8e903cb5 Set SOURCE_DATE_EPOCH to build war file (#2623) 2021-01-08 16:52:02 +09:00
onukura
55308f2deb fix delete wiki page function (#2622) 2021-01-07 22:42:05 +09:00
Naoki Takezoe
a913a95d5b Release 4.35.2 2020-12-30 00:37:53 +09:00
Naoki Takezoe
870a20721c Bump oauth2-oidc-sdk to 8.29.1 (#2617) 2020-12-30 00:36:04 +09:00
Naoki Takezoe
df81f6e364 Upgrade gitbucket-notifications-plugin to 1.10.0 (#2616) 2020-12-29 22:42:23 +09:00
pbg42
d4a892bf7f Update CONTRIBUTING.md (#2613)
corrected just a typo
2020-12-29 11:11:47 +09:00
Naoki Takezoe
41d13db5d4 Release 4.35.1 2020-12-29 00:31:22 +09:00
Naoki Takezoe
c63e20ce7d Resurrect ActivityComponent for binary compatibility in 4.35.1 (#2612) 2020-12-29 00:26:58 +09:00
Naoki Takezoe
736fdafea4 Call webhook when pull request is merged (#2611) 2020-12-28 10:54:48 +09:00
Naoki Takezoe
1dfe76e21c Fix WEB_HOOK table migration issue in 4.35.0 (#2610) 2020-12-27 19:10:07 +09:00
Naoki Takezoe
e7ddfc7ebb Bump to sbt 1.4.6, Scala 2.13.3, and Scalatra 2.7.1 (#2609)
* Bump to sbt 1.4.6, Scala 2.13.3, and Scalatra 2.7.1
* Fix warnings in Scala 2.13
2020-12-27 13:35:57 +09:00
Naoki Takezoe
66be84289d Show commit status at commits tab of pull request (#2606) 2020-12-25 22:34:47 +09:00
Naoki Takezoe
2a3c8e0712 Update README and CHANGELOG for 4.45.0 2020-12-25 22:00:31 +09:00
onukura
d3a29b3ecb fix issue title on milestone page (#2603) 2020-12-16 14:39:43 +09:00
Naoki Takezoe
7a50a15748 Upgrade plugins to GitBucket 4.35.0 compatibles 2020-12-14 03:00:13 +09:00
Naoki Takezoe
9a1b55b992 Upgrade plugins to GitBucket 4.35.0 compatibles 2020-12-14 02:58:22 +09:00
Naoki Takezoe
828b798c0e Tweak GitHub Actions trigger 2020-12-14 02:36:01 +09:00
Naoki Takezoe
8d8845536d Integration test for Web API (#2578) 2020-12-13 21:04:44 +09:00
Naoki Takezoe
f20497e769 Setting version to 4.35.0 2020-12-13 14:38:51 +09:00
Naoki Takezoe
6053d9826e Call ReceiveHook when pull request is merged (#2520) 2020-12-13 03:29:19 +09:00
Naoki Takezoe
85263474a7 Show the commit status in various places (#2601) 2020-12-13 02:30:48 +09:00
onukura
c02a722799 Bump sbt to 1.4.4 (#2602) 2020-12-13 02:10:57 +09:00
Naoki Takezoe
ce4faceccc Update README.md 2020-12-07 04:22:14 +09:00
onukura
04c8f8b864 Improve issue comment management (#2588) 2020-12-07 04:19:30 +09:00
onukura
1b32e13113 Add milestone page (#2586) 2020-12-07 04:17:30 +09:00
Naoki Takezoe
401728d47f Fix branch protection setting API (#2600) 2020-12-07 02:28:49 +09:00
kenji yoshida
31ace89f43 Create dependabot.yml 2020-11-21 01:51:03 +09:00
onukura
995cb86e90 fix typo on UI of commit with new branch (#2594) 2020-11-09 22:17:27 +09:00
Naoki Takezoe
e27623ca29 Bump sbt to 1.4.2 (#2592) 2020-11-04 01:32:20 +09:00
onukura
ea4da561c5 Add syntax highlighter setting per user (#2555) 2020-11-04 00:51:25 +09:00
Mészáros István
2e8f3efafd Implement API for creating, updating and deleting refs. (#2590) 2020-11-04 00:01:16 +09:00
Marcel Berteler
f25cf5781c Use attr() instead of data() to prevent parsing of json (#2548)
* Use attr() instead of data() to prevent parsing of json

A fix for issue #2503 
Valid JSON text will be converted to a JSON object, not text , when using .data(). using .attr() prevents this behavior.

* Update gitbucket.js

Use different methods to get old and head values depending on the object type
2020-11-03 16:25:47 +09:00
onukura
d97f7c6025 Add function to create new branch with commit on UI and start PR (#2571) 2020-11-03 16:04:08 +09:00
SIkebe
e91d903650 Upload images from clipboard (#2518) 2020-11-03 15:35:03 +09:00
Akifumi Tominaga
4f93f06de5 Fix file name of the commit comment (#2581) 2020-11-03 15:31:53 +09:00
SIkebe
08ed3c4171 Bump sbt from 1.4.0 to 1.4.1 (#2585) 2020-10-22 01:05:21 +09:00
onukura
5193d82980 save editor theme to localStorage (#2583) 2020-10-18 17:12:47 +09:00
uli-heller
eab82bf1be json4s-jackson-3.6.10 (#2567)
Co-authored-by: Naoki Takezoe <takezoe@gmail.com>
2020-10-14 22:45:47 +09:00
uli-heller
311d758910 thumbnailator-0.4.12 (#2570) 2020-10-14 15:16:48 +09:00
uli-heller
097a2d32b8 jetty-9.4.32.v20200930 (#2563) 2020-10-14 15:16:13 +09:00
uli-heller
a34766ccfd commons-io-2.8.0 (#2566) 2020-10-14 15:15:21 +09:00
uli-heller
147eef9ee5 commons-net-3.7 (#2565) 2020-10-14 15:15:09 +09:00
uli-heller
49118662b2 mariadb-java-client-2.7.0 (#2564) 2020-10-14 15:14:49 +09:00
onukura
5989f2e2cb fix milestone api (#2580) 2020-10-14 15:08:51 +09:00
onukura
127f034bba update latest activity date when pr merged (#2560) 2020-10-14 15:07:28 +09:00
onukura
e1e00c4b94 Add Get-branch-protection, Delete-branch-protection, Get-all-status-check-contexts API (#2574) 2020-10-14 15:06:23 +09:00
SIkebe
eb64cdd9cd Bump sbt from 1.3.12 to 1.4.0 (#2573) 2020-10-13 07:36:28 +09:00
onukura
1bfa8dffb8 update api repository branches reference lists (#2575) 2020-10-10 03:01:21 +09:00
uli-heller
33361b8015 Updated jgit to 5.9.0.202009080501-r (#2562) 2020-10-02 07:54:52 +09:00
onukura
b5ee074075 add create a milestone and update a milestone api (#2561) 2020-10-02 07:54:29 +09:00
onukura
cbddc34bfa Add update a pull request API (#2557) 2020-10-02 07:42:30 +09:00
onukura
61504ae9e3 add api endpoints for repository collaborator (#2558) 2020-09-29 06:14:19 +09:00
Naoki Takezoe
3049f6010c Update CHANGELOG.md 2020-09-27 23:37:01 +09:00
Naoki Takezoe
d4e01d631f Small refactoring for README handling (#2556) 2020-09-20 03:43:43 +09:00
onukura
a46aa2c61c Add list-milestones and get-a-milestone API (#2546) 2020-09-20 02:26:31 +09:00
onukura
ad147e8dd5 add webhook APIs (#2551) 2020-09-20 01:42:14 +09:00
Yuusuke KOUNOIKE
3555519392 force eol to lf by editorconfig / allow trailing space to *.md (#1965) 2020-09-20 00:53:57 +09:00
onukura
f6a5def638 Add list repository tags API (#2544) 2020-09-20 00:36:54 +09:00
SIkebe
0590cb7048 Improve accessibility (#2552)
* Document language missing
* Missing form label
* Image elements do not have [alt] attributes
* Fix metals specific configuration
* [user-scalable="no"] is used in the <meta name="viewport"> element or the [maximum-scale] attribute is less than 5
* Fix AvatarImageProviderSpec
* [id] attributes on active, focusable elements are not unique
* Links do not have a discernible name
* Links to cross-origin destinations are unsafe
2020-09-14 10:42:50 +09:00
onukura
b35d0792aa fix list commit api sha param (#2550) 2020-09-12 14:56:41 +09:00
James Yoo
0d20bc0173 adding support for GitHub v3 delete comment (#2540) 2020-09-12 14:52:47 +09:00
onukura
851141c2f4 add list-branches-for-head-commit API (#2547) 2020-09-11 06:06:04 +09:00
onukura
31a104a697 Add update-an-issue-comment API (#2538) 2020-09-11 05:38:34 +09:00
onukura
bfc44cff98 Add get-a-repository-README endpoint (#2535) 2020-09-07 04:05:46 +09:00
onukura
0da781c33d Limit the number of characters for user completion and issue completioin (#2534) 2020-09-07 03:55:11 +09:00
onukura
8dbcbb5485 Add get-an-issue-comment API endpoint (#2536) 2020-09-05 23:23:16 +09:00
onukura
7544f64c65 Add "has_issue" boolean to GET /repos/:owner/:repo API (#2542) 2020-09-05 16:45:16 +09:00
James Yoo
73d05aefad adding support for wiki page deletion recording (#2537) 2020-09-05 16:15:43 +09:00
SIkebe
4d70b056ad Show issue/PR title as a tooltip (#2532)
* Show issue/PR title as a tooltip
* Enable title for issue/PR on activities
2020-09-05 15:59:03 +09:00
onukura
b81ce41d51 Add list-public-repositories API (#2529) 2020-08-29 15:32:00 +09:00
James Yoo
a143683d7f refactored ActivityService logging strategy (#2526) 2020-08-29 15:29:42 +09:00
onukura
5ba38057dc add update activity date for commitsfile (#2525) 2020-08-28 00:11:58 +09:00
onukura
07eb2bc41e record wiki activity via http (#2521) 2020-08-23 18:02:01 +09:00
SIkebe
5e4d041295 Merge pull request #2519 from gitbucket/fix-branch-setting
Handle branch contains / properly in branch settings
2020-08-23 01:57:55 +09:00
Naoki Takezoe
4d7fc061a4 Handle branch contains / properly in branch settings 2020-08-23 01:11:33 +09:00
onukura
8db98d7b16 Add issue completion (#2513) 2020-08-21 05:54:31 +09:00
SIkebe
a6063c8aa9 Show userName as link only when a user is not removed (#2516) 2020-08-20 10:44:10 +09:00
onukura
2cc1336e82 Keep the default branch in the forked repositories (#2514) 2020-08-18 20:56:54 +09:00
onukura
308bda2050 add activity record for issue closing by comment (#2510) 2020-08-18 15:27:55 +09:00
Naoki Takezoe
36989c38d4 Max files for getting commit info becomes configurable (#2511) 2020-08-15 06:05:47 +09:00
onukura
29357ae170 Add theme choices for editor (#2501) 2020-08-03 00:50:01 +09:00
SIkebe
36643bcdd0 Show the number of milestones on the sidebar menu (#2500) 2020-08-03 00:48:47 +09:00
Kasan
72e40a0b12 Move the settings link down (#2497) 2020-08-01 15:35:41 +09:00
YHojo
2194ff7625 Add Merge PullRequest API (#2482) 2020-07-31 04:38:05 +09:00
Brosilio
b247864bfe Fixed some grammar and spelling issues, modified sign in form (#2496)
Co-authored-by: SIkebe <SIkebe@users.noreply.github.com>
2020-07-29 20:43:35 +09:00
Naoki Takezoe
b1c3ae4974 Update message for sidebar repo-list setting (#2493) 2020-07-28 22:38:10 +09:00
onukura
81c0e2037f Add function to close multiple issues with one keyword on commits (#2491) 2020-07-27 07:45:40 +09:00
Naoki Takezoe
6224ec2a7b Bump Scalatra to 2.7.0-GA (#2494) 2020-07-27 07:22:19 +09:00
Naoki Takezoe
9ff4507fe2 Fix system settings tab behavior (#2492) 2020-07-26 13:34:57 +09:00
Naoki Takezoe
67667dbff1 Remove activity log configuration no longer used (#2490) 2020-07-26 11:50:48 +09:00
Naoki Takezoe
1a4961c3e1 Merge pull request #2489 from gitbucket/release-4.34.0
GitBucket 4.34.0 release
2020-07-26 02:38:51 +09:00
Naoki Takezoe
561220237f Update README and CHANGELOG 2020-07-26 02:28:47 +09:00
Naoki Takezoe
f45b85aa71 Bump gist-plugin (refs #2443) and notifications-plugin (refs #2433) 2020-07-25 21:04:45 +09:00
Naoki Takezoe
83b3a7983e Update version to 4.34.0 2020-07-25 17:52:33 +09:00
Naoki Takezoe
63d4c5054e Deny empty password (#2488) 2020-07-25 17:49:58 +09:00
Naoki Takezoe
c8f6017be9 Migrate activity log to JSON-based file from database (#2468)
This also includes:
- Record rename and transfer repository in activity log
- Check existence of repsoitory when rendering activity logs
- Drop Akka dependency
2020-07-18 23:28:40 +09:00
Naoki Takezoe
f9fcb54861 Fix coursier cache path (#2481) 2020-07-18 17:17:41 +09:00
364 changed files with 15434 additions and 5112 deletions

View File

@@ -5,6 +5,10 @@ trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 2
end_of_line = lf
[*.md]
trim_trailing_whitespace = false
[*.java]
trim_trailing_whitespace = true

4
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1,4 @@
* @takezoe
build.sbt @xuwei-k
project/* @xuwei-k

View File

@@ -1,6 +1,6 @@
# The guidelines for contributing
- At first, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues and pull requests whether there is a same request in the past.
- 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. If you don't wanna waste your time to make a pull request, ask us about your idea at [gitter room](https://gitter.im/gitbucket/gitbucket) before staring your work.
- 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. If you don't wanna waste your time to make a pull request, ask us about your idea at [gitter room](https://gitter.im/gitbucket/gitbucket) before starting your work.
- You can edit the GitBucket documentation on Wiki if you have a GitHub account. When you find any mistakes or lacks in the documentation, please update it directly.
- All your contributions are handled as [Apache Software License, Version 2.0](https://github.com/gitbucket/gitbucket/blob/master/LICENSE). When you create a pull request or update the documentation, we assume you agreed this clause.

View File

@@ -12,7 +12,7 @@
**Deployment mode**: *explain here how you use GitBucket : standalone app, under webcontainer (which one), with an http frontend (nginx, httpd, ...)*
**Problem description**:
- *be as explicit has you can*
- *be as explicit as you can*
- *describe the problem and its symptoms*
- *explain how to reproduce*
- *attach whatever information that can help understanding the context (screen capture, log files)*

6
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -1,39 +1,39 @@
name: build
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
java: [8, 11]
java: [8, 11, 17]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Cache
uses: actions/cache@v2
uses: actions/cache@v3
env:
cache-name: cache-sbt-libs
with:
path: |
~/.ivy2/cache
~/.sbt
~/.coursier
~/.cache/coursier/v1
key: build-${{ env.cache-name }}-${{ hashFiles('build.sbt') }}
- name: Set up JDK
uses: actions/setup-java@v1
uses: actions/setup-java@v3
with:
java-version: ${{ matrix.java }}
distribution: adopt
- name: Run tests
run: sbt scalafmtSbtCheck scalafmtCheck test:scalafmtCheck test
- name: Scala 3
run: sbt '++ 3.1.2!' update # TODO
- name: Build executable
run: sbt executable
- name: Upload artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: gitbucket-java${{ matrix.java }}-${{ github.sha }}
path: ./target/executable/gitbucket.*

9
.gitignore vendored
View File

@@ -2,6 +2,8 @@
*.log
.ensime
.ensime_cache
.DS_Store
.java-version
# sbt specific
dist/*
@@ -10,6 +12,7 @@ lib_managed/
src_managed/
project/boot/
project/plugins/project/
.bsp/
# Scala-IDE specific
.scala_dependencies
@@ -28,4 +31,8 @@ project/plugins/project/
# Metals specific
.metals
.bloop
project/metals.sbt
**/metals.sbt
# Visual Studio Code specific
.vscode

9
.scala-steward.conf Normal file
View File

@@ -0,0 +1,9 @@
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." }
]

View File

@@ -1,15 +1,102 @@
# Changelog
All changes to the project will be documented in this file.
### 4.33.0 - 31 Dec 2019
## 4.38.3 - 30 Oct 2022
- Fix several issues around multiple assignees in issues and pull requests
- Fix IllegalStateException when returning unknown avatar image
## 4.38.2 - 20 Sep 2022
- Resurrect assignee icons on the issue list
## 4.38.1 - 10 Sep 2022
- Fix comment diff in Chrome 105
- Fix Markdown table CSS
- Fix HTML rendering of multiple asignees
## 4.38.0 - 3 Sep 2022
- Support multiple assignees for Issues and Pull requests
- Custom fields for issues and pull requests
- Reset password by users
- Allow to configure Jetty idle timeout in standalone mode
- Horizontal scroll for too wide tables in Markdown
- Hide header content on signin and register page
- Fix the default charset of the online editor in the repository viewer
- Fix the milestone count
- Some improvements and bugfixes for WebAPI and WebHook
## 4.37.2 - 16 Jan 2022
- Fixed a security issue reported by [Positive Technologies](https://www.ptsecurity.com/ww-en/). Great thanks for their detailed report and close support!
## 4.37.1 - 14 Dec 2021
- Update gist-plugin and notification-plugin
- Fix SSHCommand extension point for apache-sshd 2.x
## 4.37.0 - 11 Dec 2021
- Enhance Git Reference APIs
- Add milestone data to issue list API
- Support "all" in issue list API
- Support EDDSA in signed commit verification
- Support custom SSH url
- Relax max passward length limitation
- Relax max webhook url length limitation
## 4.36.2 - 16 Aug 2021
- Escape user name in avatar image tag
## 4.36.1 - 22 Jul 2021
- Bump gitbucket-gist-plugin to 4.21.0
## 4.36.0 - 17 Jul 2021
- Tag selector in the repository viewer
- Link issues/pull requests of other repositories
- Files and lines can be linked in the diff view
- Option to disable XSS protection
## 4.35.3 - 14 Jan 2021
- Fix a bug that Wiki page cannot be deleted
- Fix a deployment issue on Tomcat
## 4.35.2 - 30 Dec 2020
- Upgrade gitbucket-notifications-plugin to 1.10.0
- Upgrade oauth2-oidc-sdk to 8.29.1 to solve dependency issue
## 4.35.1 - 29 Dec 2020
- Fix database migration issue which happens if webhook is configured
- Call push webhook when pull request is merged
- Show commit status at commits tab of pull request
## 4.35.0 - 25 Dec 2020
- Editor and source viewer color theme
- Auto completion for issues and pull requests
- Upload image from clipboard
- Close multiple issues by commit comment
- Create pull request from online editor
- Milestone overview
- Commit status at various places
- WebAPI coverage improvements
## 4.34.0 - 26 Jul 2020
- Enhancement admin settings UI
- File upload settings
- Restrict repository operations
- User-defined CSS
- Limit the repository list in the sidebar
- Improve MariaDB support
- Improve activity logging
- CLI option to persist session on disk in the standalone mode
- Web API updates
- Add [list commits API](https://developer.github.com/v3/repos/commits/#list-commits)
- Bundled plugins updates
- [gitbucket-gist-plugin](https://github.com/gitbucket/gitbucket-gist-plugin) 4.18.0 -> 4.19.0
- [gitbucket-notifications-plugin](https://github.com/gitbucket/gitbucket-notifications-plugin) 1.8.0 -> 1.9.0
## 4.33.0 - 31 Dec 2019
- All CLI options are configurable by environment variables
- Folding pull request files
- WebHook security options
- Add assignee and assignees properties to some Web APIs' response
### 4.32.0 - 7 Aug 2019
## 4.32.0 - 7 Aug 2019
- Bump to Scala 2.13.0 and Scalatra 2.7.0
- Draft pull request
- Drop network installation of plugins
@@ -17,20 +104,20 @@ All changes to the project will be documented in this file.
- Apply default priority to pull requests
- Focus title after clicking issue / pull request edit button
### 4.31.2 - 7 Apr 2019
## 4.31.2 - 7 Apr 2019
- Bug and security fix
### 4.31.1 - 17 Mar 2019
## 4.31.1 - 17 Mar 2019
- Bug fix
### 4.31.0 - 17 Mar 2019
## 4.31.0 - 17 Mar 2019
- Docker support in CI plugin
- Verify GPG key signed commit
- OAuth2 Token (sent as a parameter) authentication support and new APIs in Web API
- OGP (Open Graph protocol) support
- Username completion with avatars
### 4.30.1 - 22 Dec 2018
## 4.30.1 - 22 Dec 2018
- Bug fix for several WebHooks and Web API
## 4.30.0 - 15 Dec 2018
@@ -117,7 +204,7 @@ All changes to the project will be documented in this file.
- Submodule links to web page
- Clarify close/reopen button
# 4.20.0 - 23 Dec 2017
## 4.20.0 - 23 Dec 2017
- Squash and rebase merge strategy for pull requests
- Quick pull request creation
- Download patch from the diff view

View File

@@ -1,4 +1,4 @@
GitBucket [![Gitter chat](https://badges.gitter.im/gitbucket/gitbucket.svg)](https://gitter.im/gitbucket/gitbucket) [![build](https://github.com/gitbucket/gitbucket/workflows/build/badge.svg?branch=master)](https://github.com/gitbucket/gitbucket/actions?query=workflow%3Abuild+branch%3Amaster) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.gitbucket/gitbucket_2.13/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.gitbucket/gitbucket_2.13) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/gitbucket/gitbucket/blob/master/LICENSE)
GitBucket [![Gitter chat](https://badges.gitter.im/gitbucket/gitbucket.svg)](https://gitter.im/gitbucket/gitbucket) [![build](https://github.com/gitbucket/gitbucket/workflows/build/badge.svg?branch=master)](https://github.com/gitbucket/gitbucket/actions?query=workflow%3Abuild+branch%3Amaster) [![gitbucket Scala version support](https://index.scala-lang.org/gitbucket/gitbucket/gitbucket/latest-by-scala-version.svg)](https://index.scala-lang.org/gitbucket/gitbucket/gitbucket) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/gitbucket/gitbucket/blob/master/LICENSE)
=========
GitBucket is a Git web platform powered by Scala offering:
@@ -8,7 +8,7 @@ GitBucket is a Git web platform powered by Scala offering:
- High extensibility by plugins
- API compatibility with GitHub
You can try an [online demo](https://gitbucket.herokuapp.com/) *(ID: root / Pass: root)* of GitBucket, and also get the latest information at [GitBucket News](https://gitbucket.github.io/gitbucket-news/).
![GitBucket](https://gitbucket.github.io/img/screenshots/screenshot-repository_viewer.png)
Features
--------
@@ -22,8 +22,6 @@ The current version of GitBucket provides many features such as:
- Account and group management with LDAP integration
- a Plug-in system
If you want to try the development version of GitBucket, see the [Developer's Guide](https://github.com/gitbucket/gitbucket/blob/master/doc/readme.md).
Installation
--------
GitBucket requires **Java8**. You have to install it, if it is not already installed.
@@ -31,7 +29,7 @@ GitBucket requires **Java8**. You have to install it, if it is not already insta
1. Download the latest **gitbucket.war** from [the releases page](https://github.com/gitbucket/gitbucket/releases) and run it by `java -jar gitbucket.war`.
2. Go to `http://[hostname]:8080/` and log in with ID: **root** / Pass: **root**.
You can also deploy `gitbucket.war` to a servlet container which supports Servlet 3.0 (like Jetty, Tomcat, JBoss, etc)
You can also deploy `gitbucket.war` to a servlet container which supports Servlet 3.0 (like Jetty, Tomcat, JBoss, etc). Note that GitBucket doesn't support Jakarta EE yet.
For more information about installation on Mac or Windows Server (with IIS), or configuration of Apache or Nginx and also integration with other tools or services such as Jenkins or Slack, see [Wiki](https://github.com/gitbucket/gitbucket/wiki).
@@ -48,20 +46,42 @@ GitBucket has a plug-in system that allows extra functionality. Officially the f
You can find more plugins made by the community at [GitBucket community plugins](https://gitbucket-plugins.github.io/).
Building and Development
-----------
If you want to try the development version of GitBucket, or want to contribute to the project, please see the [Developer's Guide](https://github.com/gitbucket/gitbucket/blob/master/doc/readme.md).
It provides instructions on building from source and on setting up an IDE for debugging.
It also contains documentation of the core concepts used within the project.
Support
--------
- If you have any questions about GitBucket, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues whether there is a same question or request in the past.
- If you can't find same question and report, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
- If you can't find same question and report, send it to our [Gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
- The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles.
What's New in 4.33.x
What's New in 4.38.x
-------------
### 4.33.0 - 31 Dec 2019
## 4.38.3 - 30 Oct 2022
- Fix several issues around multiple assignees in issues and pull requests
- Fix IllegalStateException when returning unknown avatar image
- All CLI options are configurable by environment variables
- Folding pull request files
- WebHook security options
- Add assignee and assignees properties to some Web APIs' response
## 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
See the [change log](CHANGELOG.md) for all of the updates.

132
build.sbt
View File

@@ -1,97 +1,109 @@
import com.typesafe.sbt.license.{DepModuleInfo, LicenseInfo}
import com.typesafe.sbt.pgp.PgpKeys._
import com.jsuereth.sbtpgp.PgpKeys._
val Organization = "io.github.gitbucket"
val Name = "gitbucket"
val GitBucketVersion = "4.33.0"
val ScalatraVersion = "2.7.0-RC1"
val JettyVersion = "9.4.30.v20200611"
val JgitVersion = "5.8.0.202006091008-r"
val GitBucketVersion = "4.38.3"
val ScalatraVersion = "2.8.4"
val JettyVersion = "9.4.49.v20220914"
val JgitVersion = "5.13.1.202206130422-r"
lazy val root = (project in file("."))
.enablePlugins(SbtTwirl, ScalatraPlugin)
.settings(
)
sourcesInBase := false
organization := Organization
name := Name
version := GitBucketVersion
scalaVersion := "2.13.1"
scalaVersion := "2.13.10"
scalafmtOnCompile := true
// scalafmtOnCompile := true
coverageExcludedPackages := ".*\\.html\\..*"
// dependency settings
resolvers ++= Seq(
Classpaths.typesafeReleases,
Resolver.jcenterRepo,
"sonatype-snapshot" at "https://oss.sonatype.org/content/repositories/snapshots/"
)
libraryDependencies ++= Seq(
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % JgitVersion,
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % JgitVersion,
"org.scalatra" %% "scalatra" % ScalatraVersion,
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
"org.scalatra" %% "scalatra-forms" % ScalatraVersion,
"org.json4s" %% "json4s-jackson" % "3.6.9",
"commons-io" % "commons-io" % "2.7",
"io.github.gitbucket" % "solidbase" % "1.0.3",
"io.github.gitbucket" % "markedj" % "1.0.16",
"org.apache.commons" % "commons-compress" % "1.20",
"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",
"io.github.gitbucket" % "solidbase" % "1.0.5",
"io.github.gitbucket" % "markedj" % "1.0.17",
"org.apache.commons" % "commons-compress" % "1.21",
"org.apache.commons" % "commons-email" % "1.5",
"commons-net" % "commons-net" % "3.6",
"org.apache.httpcomponents" % "httpclient" % "4.5.12",
"org.apache.sshd" % "apache-sshd" % "2.1.0" exclude ("org.slf4j", "slf4j-jdk14") exclude ("org.apache.sshd", "sshd-mina") exclude ("org.apache.sshd", "sshd-netty"),
"org.apache.tika" % "tika-core" % "1.24.1",
"com.github.takezoe" %% "blocking-slick-32" % "0.0.12",
"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,
"com.novell.ldap" % "jldap" % "2009-10-07",
"com.h2database" % "h2" % "1.4.199",
"org.mariadb.jdbc" % "mariadb-java-client" % "2.6.0",
"org.postgresql" % "postgresql" % "42.2.6",
"ch.qos.logback" % "logback-classic" % "1.2.3",
"com.zaxxer" % "HikariCP" % "3.4.5",
"com.typesafe" % "config" % "1.4.0",
"com.typesafe.akka" %% "akka-actor" % "2.5.27",
"org.mariadb.jdbc" % "mariadb-java-client" % "3.0.8",
"org.postgresql" % "postgresql" % "42.5.0",
"ch.qos.logback" % "logback-classic" % "1.2.11",
"com.zaxxer" % "HikariCP" % "4.0.3" exclude ("org.slf4j", "slf4j-api"),
"com.typesafe" % "config" % "1.4.2",
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.1.0",
"com.github.bkromhout" % "java-diff-utils" % "2.1.1",
"org.cache2k" % "cache2k-all" % "1.2.4.Final",
"com.enragedginger" %% "akka-quartz-scheduler" % "1.8.1-akka-2.5.x" exclude ("com.mchange", "c3p0") exclude ("com.zaxxer", "HikariCP-java6"),
"net.coobird" % "thumbnailator" % "0.4.11",
"io.github.java-diff-utils" % "java-diff-utils" % "4.12",
"org.cache2k" % "cache2k-all" % "1.6.0.Final",
"net.coobird" % "thumbnailator" % "0.4.18",
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
"com.nimbusds" % "oauth2-oidc-sdk" % "5.64.4",
"com.nimbusds" % "oauth2-oidc-sdk" % "10.1",
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
"junit" % "junit" % "4.13" % "test",
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test",
"org.mockito" % "mockito-core" % "3.3.3" % "test",
"com.dimafeng" %% "testcontainers-scala" % "0.37.0" % "test",
"org.testcontainers" % "mysql" % "1.14.3" % "test",
"org.testcontainers" % "postgresql" % "1.14.3" % "test",
"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",
"net.i2p.crypto" % "eddsa" % "0.3.0",
"is.tagomor.woothee" % "woothee-java" % "1.11.0",
"org.ec4j.core" % "ec4j-core" % "0.0.3"
"org.ec4j.core" % "ec4j-core" % "0.3.0",
"org.kohsuke" % "github-api" % "1.313" % "test"
)
libraryDependencies ~= {
_.map {
case x if x.name == "twirl-api" =>
x cross CrossVersion.for3Use2_13
case x =>
x
}
}
// Compiler settings
scalacOptions := Seq("-deprecation", "-language:postfixOps", "-opt:l:method")
javacOptions in compile ++= Seq("-target", "8", "-source", "8")
javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml"
scalacOptions := Seq(
"-deprecation",
"-language:postfixOps",
"-opt:l:method",
"-feature",
"-Wunused:imports",
"-Wconf:cat=unused&src=twirl/.*:s,cat=unused&src=scala/gitbucket/core/model/[^/]+\\.scala:s"
)
compile / javacOptions ++= Seq("-target", "8", "-source", "8")
Jetty / javaOptions += "-Dlogback.configurationFile=/logback-dev.xml"
// Test settings
//testOptions in Test += Tests.Argument("-l", "ExternalDBTest")
javaOptions in Test += "-Dgitbucket.home=target/gitbucket_home_for_test"
testOptions in Test += Tests.Setup(() => new java.io.File("target/gitbucket_home_for_test").mkdir())
fork in Test := true
Test / javaOptions += "-Dgitbucket.home=target/gitbucket_home_for_test"
Test / testOptions += Tests.Setup(() => new java.io.File("target/gitbucket_home_for_test").mkdir())
Test / fork := true
// Packaging options
packageOptions += Package.MainClass("JettyLauncher")
// Assembly settings
test in assembly := {}
assemblyMergeStrategy in assembly := {
assembly / test := {}
assembly / assemblyMergeStrategy := {
case PathList("META-INF", xs @ _*) =>
(xs map { _.toLowerCase }) match {
case ("manifest.mf" :: Nil) => MergeStrategy.discard
@@ -122,6 +134,12 @@ libraryDependencies ++= Seq(
"org.eclipse.jetty" % "jetty-util" % JettyVersion % "executable"
)
// Run package task before test to generate target/webapp for integration test
Test / test := {
_root_.sbt.Keys.`package`.value
(Test / test).value
}
val executableKey = TaskKey[File]("executable")
executableKey := {
import java.util.jar.Attributes.{Name => AttrName}
@@ -150,7 +168,7 @@ executableKey := {
IO unzip (warFile, temp)
// include launcher classes
val classDir = (Keys.classDirectory in Compile).value
val classDir = (Compile / Keys.classDirectory).value
val launchClasses = Seq("JettyLauncher.class" /*, "HttpsSupportConnector.class" */ )
launchClasses foreach { name =>
IO copyFile (classDir / name, temp / name)
@@ -181,7 +199,7 @@ executableKey := {
manifest.getMainAttributes put (AttrName.MANIFEST_VERSION, "1.0")
manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher")
val outputFile = workDir / warName
IO jar (contentMappings.map { case (file, path) => (file, path.toString) }, outputFile, manifest)
IO jar (contentMappings.map { case (file, path) => (file, path.toString) }, outputFile, manifest, None)
// generate checksums
Seq(
@@ -253,12 +271,7 @@ pomExtra := (
</developers>
)
licenseOverrides := {
case DepModuleInfo("com.github.bkromhout", "java-diff-utils", _) =>
LicenseInfo(LicenseCategory.Apache, "Apache-2.0", "http://www.apache.org/licenses/LICENSE-2.0")
}
testOptions in Test ++= {
Test / testOptions ++= {
if (scala.util.Properties.isWin) {
Seq(
Tests.Exclude(
@@ -271,3 +284,8 @@ testOptions in Test ++= {
Nil
}
}
Jetty / javaOptions ++= Seq(
"-Xdebug",
"-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000"
)

View File

@@ -2,7 +2,7 @@ Authentication in Controller
========
GitBucket provides many [authenticators](https://github.com/gitbucket/gitbucket/blob/master/src/main/scala/gitbucket/core/util/Authenticator.scala) to access controlling in the controller.
For example, in the case of `RepositoryViwerController`,
For example, in the case of `RepositoryViewerController`,
it references three authenticators: `ReadableUsersAuthenticator`, `ReferrerAuthenticator` and `CollaboratorsAuthenticator`.
```scala
@@ -19,13 +19,13 @@ trait RepositoryViewerControllerBase extends ControllerBase {
...
```
Authenticators provides a method to add guard to actions in the controller:
Authenticators provide a method to add guard to actions in the controller:
- `ReadableUsersAuthenticator` provides `readableUsersOnly` method
- `ReferrerAuthenticator` provides `referrersOnly` method
- `CollaboratorsAuthenticator` provides `collaboratorsOnly` method
These methods are available in each actions as below:
These methods are available in each action as below:
```scala
// Allows only the repository owner (or manager for group repository) and administrators.
@@ -38,7 +38,7 @@ get("/:owner/:repository/new/*")(collaboratorsOnly { repository =>
...
})
// Allows only signed in users which can access the repository.
// Allows only signed-in users which can access the repository.
post("/:owner/:repository/commit/:id/comment/new", commentForm)(readableUsersOnly { (form, repository) =>
...
})
@@ -50,11 +50,11 @@ Currently, GitBucket provides below authenticators:
|--------------------------|-----------------|--------------------------------------------------------------------------------------|
|OneselfAuthenticator |oneselfOnly |Allows only oneself and administrators. |
|OwnerAuthenticator |ownerOnly |Allows only the repository owner and administrators. |
|UsersAuthenticator |usersOnly |Allows only signed in users. |
|UsersAuthenticator |usersOnly |Allows only signed-in users. |
|AdminAuthenticator |adminOnly |Allows only administrators. |
|CollaboratorsAuthenticator|collaboratorsOnly|Allows only collaborators and administrators. |
|ReferrerAuthenticator |referrersOnly |Allows only the repository owner (or manager for group repository) and administrators.|
|ReadableUsersAuthenticator|readableUsersOnly|Allows only signed in users which can access the repository. |
|ReadableUsersAuthenticator|readableUsersOnly|Allows only signed-in users which can access the repository. |
|GroupManagerAuthenticator |managersOnly |Allows only the group managers. |
Of course, if you make a new plugin, you can define a your own authenticator according to requirement in your plugin.
Of course, if you make a new plugin, you can implement your own authenticator according to requirement in your plugin.

View File

@@ -2,7 +2,7 @@ Automatic Schema Updating
========
GitBucket updates database schema automatically using [Solidbase](https://github.com/gitbucket/solidbase) in the first run after the upgrading.
To release a new version of GitBucket, add the version definition to the [gitbucket.core.GitBucketCoreModule](https://github.com/gitbucket/gitbucket/blob/master/src/main/scala/gitbucket/core/GitBucketCoreModule.scala) at first.
To release a new version of GitBucket, add the version definition to [gitbucket.core.GitBucketCoreModule](https://github.com/gitbucket/gitbucket/blob/master/src/main/scala/gitbucket/core/GitBucketCoreModule.scala) at first.
```scala
object GitBucketCoreModule extends Module("gitbucket-core",
@@ -17,7 +17,7 @@ object GitBucketCoreModule extends Module("gitbucket-core",
)
```
Next, add a XML file which updates database schema into [/src/main/resources/update/](https://github.com/gitbucket/gitbucket/tree/master/src/main/resources/update) with a filenane defined in `GitBucketCoreModule`.
Next, add a XML file which updates database schema into [/src/main/resources/update/](https://github.com/gitbucket/gitbucket/tree/master/src/main/resources/update) with a filename defined in `GitBucketCoreModule`.
```xml
<?xml version="1.0" encoding="UTF-8"?>
@@ -31,9 +31,9 @@ Next, add a XML file which updates database schema into [/src/main/resources/upd
</changeSet>
```
Solidbase stores the current version to `VERSIONS` table and checks it at start-up. If the stored version differs from the actual version, it executes differences between the stored version and the actual version.
Solidbase stores the current version to `VERSIONS` table and checks it at start-up. If the stored version different from the actual version, it executes differences between the stored version and the actual version.
We can add the SQL file instead of the XML file using `SqlMigration`. It try to load a SQL file from classpath as following order:
We can add the SQL file instead of the XML file using `SqlMigration`. It tries to load a SQL file from classpath in the following order:
1. Specified path (if specified)
2. `${moduleId}_${version}_${database}.sql`
@@ -51,4 +51,4 @@ object GitBucketCoreModule extends Module("gitbucket-core",
)
```
See more details [README of Solidbase](https://github.com/gitbucket/solidbase).
See more details at [README of Solidbase](https://github.com/gitbucket/solidbase).

View File

@@ -18,12 +18,12 @@ $ 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 reloaded 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`.
Build war file
--------
To build war file, run the following command:
To build a war file, run the following command:
```shell
$ sbt package

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 KiB

After

Width:  |  Height:  |  Size: 112 KiB

View File

@@ -1 +1 @@
sbt.version=1.3.12
sbt.version=1.7.2

View File

@@ -1,10 +1,11 @@
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.1")
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.0")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0")
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.4")
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2")
addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0")
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.2")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1")
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")
addDependencyTreePlugin

View File

@@ -1,51 +1,101 @@
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.SecuredRedirectHandler;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.server.session.DefaultSessionCache;
import org.eclipse.jetty.server.session.FileSessionDataStore;
import org.eclipse.jetty.server.session.SessionCache;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.webapp.WebAppContext;
import java.io.File;
import java.net.InetAddress;
import java.net.URL;
import java.net.InetSocketAddress;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toSet;
public class JettyLauncher {
private interface Defaults {
String CONNECTORS = "http";
String HOST = "0.0.0.0";
int HTTP_PORT = 8080;
int HTTPS_PORT = 8443;
boolean REDIRECT_HTTPS = false;
}
private interface Connectors {
String HTTP = "http";
String HTTPS = "https";
}
public static void main(String[] args) throws Exception {
System.setProperty("java.awt.headless", "true");
String host = null;
String port = null;
InetSocketAddress address = null;
String contextPath = "/";
String tmpDirPath="";
boolean forceHttps = false;
String connectors = getEnvironmentVariable("gitbucket.connectors");
String host = getEnvironmentVariable("gitbucket.host");
String port = getEnvironmentVariable("gitbucket.port");
String securePort = getEnvironmentVariable("gitbucket.securePort");
String keyStorePath = getEnvironmentVariable("gitbucket.keyStorePath");
String keyStorePassword = getEnvironmentVariable("gitbucket.keyStorePassword");
String keyManagerPassword = getEnvironmentVariable("gitbucket.keyManagerPassword");
String redirectHttps = getEnvironmentVariable("gitbucket.redirectHttps");
String contextPath = getEnvironmentVariable("gitbucket.prefix");
String tmpDirPath = getEnvironmentVariable("gitbucket.tempDir");
String jettyIdleTimeout = getEnvironmentVariable("gitbucket.jettyIdleTimeout");
boolean saveSessions = false;
host = getEnvironmentVariable("gitbucket.host");
port = getEnvironmentVariable("gitbucket.port");
contextPath = getEnvironmentVariable("gitbucket.prefix");
tmpDirPath = getEnvironmentVariable("gitbucket.tempDir");
for(String arg: args) {
if(arg.equals("--save_sessions")) {
saveSessions = true;
}
if(arg.startsWith("--") && arg.contains("=")) {
String[] dim = arg.split("=");
if(dim.length >= 2) {
String[] dim = arg.split("=", 2);
if(dim.length == 2) {
switch (dim[0]) {
case "--connectors":
connectors = dim[1];
break;
case "--host":
host = dim[1];
break;
case "--port":
port = dim[1];
break;
case "--secure_port":
securePort = dim[1];
break;
case "--key_store_path":
keyStorePath = dim[1];
break;
case "--key_store_password":
keyStorePassword = dim[1];
break;
case "--key_manager_password":
keyManagerPassword = dim[1];
break;
case "--redirect_https":
redirectHttps = dim[1];
break;
case "--prefix":
contextPath = dim[1];
break;
@@ -58,6 +108,9 @@ public class JettyLauncher {
case "--plugin_dir":
System.setProperty("gitbucket.pluginDir", dim[1]);
break;
case "--jetty_idle_timeout":
jettyIdleTimeout = dim[1];
break;
}
}
}
@@ -67,38 +120,74 @@ public class JettyLauncher {
contextPath = "/" + contextPath;
}
if(host != null) {
address = new InetSocketAddress(host, getPort(port));
final String hostName = InetAddress.getByName(fallback(host, Defaults.HOST)).getHostName();
final Server server = new Server();
final Set<String> connectorsSet = Stream.of(fallback(connectors, Defaults.CONNECTORS)
.toLowerCase().split(",")).map(String::trim).collect(toSet());
final List<ServerConnector> connectorInstances = new ArrayList<>();
final HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.setSendServerVersion(false);
if (connectorsSet.contains(Connectors.HTTPS)) {
httpConfig.setSecurePort(fallback(securePort, Defaults.HTTPS_PORT, Integer::parseInt));
}
if (jettyIdleTimeout != null && jettyIdleTimeout.trim().length() != 0) {
httpConfig.setIdleTimeout(Long.parseLong(jettyIdleTimeout.trim()));
} else {
address = new InetSocketAddress(getPort(port));
httpConfig.setIdleTimeout(300000L); // default is 5min
}
Server server = new Server(address);
if (connectorsSet.contains(Connectors.HTTP)) {
final ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
connector.setHost(hostName);
connector.setPort(fallback(port, Defaults.HTTP_PORT, Integer::parseInt));
// SelectChannelConnector connector = new SelectChannelConnector();
// if(host != null) {
// connector.setHost(host);
// }
// connector.setMaxIdleTime(1000 * 60 * 60);
// connector.setSoLingerTime(-1);
// connector.setPort(port);
// server.addConnector(connector);
// Disabling Server header
for (Connector connector : server.getConnectors()) {
for (ConnectionFactory factory : connector.getConnectionFactories()) {
if (factory instanceof HttpConnectionFactory) {
((HttpConnectionFactory) factory).getHttpConfiguration().setSendServerVersion(false);
}
}
connectorInstances.add(connector);
}
if (connectorsSet.contains(Connectors.HTTPS)) {
final SslContextFactory 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" +
" or GITBUCKET_KEYSTOREPATH environment variable."));
sslContextFactory.setKeyStorePassword(requireNonNull(keyStorePassword,
"You must specify a an SSL keystore password via the --key_store_password argument" +
" or GITBUCKET_KEYSTOREPASSWORD environment variable."));
sslContextFactory.setKeyManagerPassword(requireNonNull(keyManagerPassword,
"You must specify a key manager password via the --key_manager_password' argument" +
" or GITBUCKET_KEYMANAGERPASSWORD environment variable."));
final HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
httpsConfig.addCustomizer(new SecureRequestCustomizer());
final ServerConnector connector = new ServerConnector(server,
new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
new HttpConnectionFactory(httpsConfig));
connector.setHost(hostName);
connector.setPort(fallback(securePort, Defaults.HTTPS_PORT, Integer::parseInt));
connectorInstances.add(connector);
}
require(!connectorInstances.isEmpty(),
"No server connectors could be configured, please check your --connectors command line argument" +
" or GITBUCKET_CONNECTORS environment variable.");
server.setConnectors(connectorInstances.toArray(new ServerConnector[0]));
WebAppContext context = new WebAppContext();
if(saveSessions) {
File sessDir = new File(getGitBucketHome(), "sessions");
if(!sessDir.exists()){
sessDir.mkdirs();
mkdir(sessDir);
}
SessionHandler sessions = context.getSessionHandler();
SessionCache cache = new DefaultSessionCache(sessions);
@@ -112,7 +201,7 @@ public class JettyLauncher {
if(tmpDirPath == null || tmpDirPath.equals("")){
tmpDir = new File(getGitBucketHome(), "tmp");
if(!tmpDir.exists()){
tmpDir.mkdirs();
mkdir(tmpDir);
}
} else {
tmpDir = new File(tmpDirPath);
@@ -136,13 +225,16 @@ public class JettyLauncher {
context.setDescriptor(location.toExternalForm() + "/WEB-INF/web.xml");
context.setServer(server);
context.setWar(location.toExternalForm());
if (forceHttps) {
context.setInitParameter("org.scalatra.ForceHttps", "true");
final HandlerList handlers = new HandlerList();
if (fallback(redirectHttps, Defaults.REDIRECT_HTTPS, Boolean::parseBoolean)) {
handlers.addHandler(new SecuredRedirectHandler());
}
Handler handler = addStatisticsHandler(context);
handlers.addHandler(addStatisticsHandler(context));
server.setHandler(handler);
server.setHandler(handlers);
server.setStopAtShutdown(true);
server.setStopTimeout(7_000);
server.start();
@@ -170,11 +262,28 @@ public class JettyLauncher {
}
}
private static int getPort(String port){
if(port == null) {
return 8080;
} else {
return Integer.parseInt(port);
private static <T, R> T fallback(R value, T defaultValue, Function<R, T> converter) {
return value == null ? defaultValue : converter.apply(value);
}
private static <T> T fallback(T value, T defaultValue) {
return fallback(value, defaultValue, identity());
}
private static void require(boolean condition, String message) {
if (!condition) {
throw new IllegalArgumentException(message);
}
}
private static <T> T requireNonNull(T value, String message) {
require(value != null, message);
return value;
}
private static void mkdir(File dir) {
if (!dir.mkdirs()) {
throw new RuntimeException("Unable to create directory: " + dir);
}
}

View File

@@ -20,15 +20,15 @@ public class PatchUtil {
public static String apply(String source, String patch, FileHeader fh)
throws IOException, PatchApplyException {
RawText rt = new RawText(source.getBytes("UTF-8"));
List<String> oldLines = new ArrayList<String>(rt.size());
List<String> oldLines = new ArrayList<>(rt.size());
for (int i = 0; i < rt.size(); i++)
oldLines.add(rt.getString(i));
List<String> newLines = new ArrayList<String>(oldLines);
List<String> newLines = new ArrayList<>(oldLines);
for (HunkHeader hh : fh.getHunks()) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(patch.getBytes("UTF-8"), hh.getStartOffset(), hh.getEndOffset() - hh.getStartOffset());
RawText hrt = new RawText(out.toByteArray());
List<String> hunkLines = new ArrayList<String>(hrt.size());
List<String> hunkLines = new ArrayList<>(hrt.size());
for (int i = 0; i < hrt.size(); i++)
hunkLines.add(hrt.getString(i));
int pos = 0;

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<changeSet>
<dropTable tableName="ACTIVITY" />
</changeSet>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<changeSet>
<!--================================================================================================-->
<!-- WEB_HOOK -->
<!--================================================================================================-->
<dropForeignKeyConstraint constraintName="IDX_WEB_HOOK_EVENT_FK0" baseTableName="WEB_HOOK_EVENT"/>
<dropForeignKeyConstraint constraintName="IDX_WEB_HOOK_FK0" baseTableName="WEB_HOOK"/>
<dropPrimaryKey constraintName="IDX_WEB_HOOK_PK" tableName="WEB_HOOK"/>
<createTable tableName="WEB_HOOK_2">
<column name="HOOK_ID" type="int" nullable="true" autoIncrement="true" unique="false" primaryKeyName="IDX_WEB_HOOK_PK" primaryKey="true" />
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
<column name="URL" type="varchar(200)" nullable="false"/>
<column name="TOKEN" type="varchar(100)" nullable="true"/>
<column name="CTYPE" type="varchar(10)" nullable="true"/>
</createTable>
<sql>
INSERT INTO WEB_HOOK_2 (USER_NAME, REPOSITORY_NAME, URL, TOKEN, CTYPE) SELECT USER_NAME, REPOSITORY_NAME, URL, TOKEN, CTYPE FROM WEB_HOOK
</sql>
<renameTable newTableName="WEB_HOOK_BK" oldTableName="WEB_HOOK"/>
<renameTable newTableName="WEB_HOOK" oldTableName="WEB_HOOK_2"/>
<addUniqueConstraint constraintName="IDX_WEB_HOOK_1" tableName="WEB_HOOK" columnNames="USER_NAME, REPOSITORY_NAME, URL"/>
<addForeignKeyConstraint constraintName="IDX_WEB_HOOK_FK0" baseTableName="WEB_HOOK" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
<addForeignKeyConstraint constraintName="IDX_WEB_HOOK_EVENT_FK0" baseTableName="WEB_HOOK_EVENT" baseColumnNames="USER_NAME, REPOSITORY_NAME, URL" referencedTableName="WEB_HOOK" referencedColumnNames="USER_NAME, REPOSITORY_NAME, URL" onDelete="CASCADE" onUpdate="CASCADE"/>
<!--================================================================================================-->
<!-- ACCOUNT_PREFERENCE -->
<!--================================================================================================-->
<createTable tableName="ACCOUNT_PREFERENCE">
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
<column name="HIGHLIGHTER_THEME" type="varchar(100)" nullable="false" defaultValue="prettify"/>
</createTable>
<addPrimaryKey constraintName="IDX_ACCOUNT_PREFERENCE_PK" tableName="ACCOUNT_PREFERENCE" columnNames="USER_NAME"/>
<addForeignKeyConstraint constraintName="IDX_ACCOUNT_PREFERENCE_FK0" baseTableName="ACCOUNT_PREFERENCE" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME" onDelete="CASCADE" onUpdate="CASCADE"/>
</changeSet>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<changeSet>
<addColumn tableName="REPOSITORY">
<column name="SAFE_MODE" type="boolean" nullable="false" defaultValue="true"/>
</addColumn>
</changeSet>

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,6 @@ import java.util.EnumSet
import javax.servlet._
import gitbucket.core.controller.{ReleaseController, _}
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service.SystemSettingsService
import gitbucket.core.servlet._
import gitbucket.core.util.Directory

View File

@@ -1,16 +1,26 @@
package gitbucket.core
import io.github.gitbucket.solidbase.migration.{SqlMigration, LiquibaseMigration}
import io.github.gitbucket.solidbase.model.{Version, Module}
import java.io.FileOutputStream
import java.nio.charset.StandardCharsets
import java.sql.Connection
import java.util.UUID
import gitbucket.core.model.Activity
import gitbucket.core.util.Directory.ActivityLog
import gitbucket.core.util.JDBCUtil
import io.github.gitbucket.solidbase.Solidbase
import io.github.gitbucket.solidbase.migration.{LiquibaseMigration, Migration}
import io.github.gitbucket.solidbase.model.{Module, Version}
import org.json4s.{Formats, NoTypeHints}
import org.json4s.jackson.Serialization
import org.json4s.jackson.Serialization.write
import scala.util.Using
object GitBucketCoreModule
extends Module(
"gitbucket-core",
new Version(
"4.0.0",
new LiquibaseMigration("update/gitbucket-core_4.0.xml"),
new SqlMigration("update/gitbucket-core_4.0.sql")
),
new Version("4.0.0", new LiquibaseMigration("update/gitbucket-core_4.0.xml")),
new Version("4.1.0"),
new Version("4.2.0", new LiquibaseMigration("update/gitbucket-core_4.2.xml")),
new Version("4.2.1"),
@@ -18,11 +28,7 @@ object GitBucketCoreModule
new Version("4.4.0"),
new Version("4.5.0"),
new Version("4.6.0", new LiquibaseMigration("update/gitbucket-core_4.6.xml")),
new Version(
"4.7.0",
new LiquibaseMigration("update/gitbucket-core_4.7.xml"),
new SqlMigration("update/gitbucket-core_4.7.sql")
),
new Version("4.7.0", new LiquibaseMigration("update/gitbucket-core_4.7.xml")),
new Version("4.7.1"),
new Version("4.8"),
new Version("4.9.0", new LiquibaseMigration("update/gitbucket-core_4.9.xml")),
@@ -31,11 +37,7 @@ object GitBucketCoreModule
new Version("4.12.0"),
new Version("4.12.1"),
new Version("4.13.0"),
new Version(
"4.14.0",
new LiquibaseMigration("update/gitbucket-core_4.14.xml"),
new SqlMigration("update/gitbucket-core_4.14.sql")
),
new Version("4.14.0", new LiquibaseMigration("update/gitbucket-core_4.14.xml")),
new Version("4.14.1"),
new Version("4.15.0"),
new Version("4.16.0"),
@@ -65,5 +67,52 @@ object GitBucketCoreModule
new Version("4.31.1"),
new Version("4.31.2"),
new Version("4.32.0", new LiquibaseMigration("update/gitbucket-core_4.32.xml")),
new Version("4.33.0")
new Version("4.33.0"),
new Version(
"4.34.0",
new Migration() {
override def migrate(moduleId: String, version: String, context: java.util.Map[String, AnyRef]): Unit = {
implicit val formats: Formats = Serialization.formats(NoTypeHints)
import JDBCUtil._
val conn = context.get(Solidbase.CONNECTION).asInstanceOf[Connection]
val list = conn.select("SELECT * FROM ACTIVITY ORDER BY ACTIVITY_ID") {
rs =>
Activity(
activityId = UUID.randomUUID().toString,
userName = rs.getString("USER_NAME"),
repositoryName = rs.getString("REPOSITORY_NAME"),
activityUserName = rs.getString("ACTIVITY_USER_NAME"),
activityType = rs.getString("ACTIVITY_TYPE"),
message = rs.getString("MESSAGE"),
additionalInfo = {
val additionalInfo = rs.getString("ADDITIONAL_INFO")
if (rs.wasNull()) None else Some(additionalInfo)
},
activityDate = rs.getTimestamp("ACTIVITY_DATE")
)
}
Using.resource(new FileOutputStream(ActivityLog, true)) { out =>
list.foreach { activity =>
out.write((write(activity) + "\n").getBytes(StandardCharsets.UTF_8))
}
}
}
},
new LiquibaseMigration("update/gitbucket-core_4.34.xml")
),
new Version("4.35.0", new LiquibaseMigration("update/gitbucket-core_4.35.xml")),
new Version("4.35.1"),
new Version("4.35.2"),
new Version("4.35.3"),
new Version("4.36.0", new LiquibaseMigration("update/gitbucket-core_4.36.xml")),
new Version("4.36.1"),
new Version("4.36.2"),
new Version("4.37.0", new LiquibaseMigration("update/gitbucket-core_4.37.xml")),
new Version("4.37.1"),
new Version("4.37.2"),
new Version("4.38.0", new LiquibaseMigration("update/gitbucket-core_4.38.xml")),
new Version("4.38.1"),
new Version("4.38.2"),
new Version("4.38.3")
)

View File

@@ -22,3 +22,12 @@ case class ApiBranchForList(
name: String,
commit: ApiBranchCommit
)
/**
* https://docs.github.com/en/rest/reference/repos#list-branches-for-head-commit
*/
case class ApiBranchForHeadCommit(
name: String,
commit: ApiBranchCommit,
`protected`: Boolean
)

View File

@@ -4,7 +4,11 @@ import gitbucket.core.service.ProtectedBranchService
import org.json4s._
/** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */
case class ApiBranchProtection(enabled: Boolean, required_status_checks: Option[ApiBranchProtection.Status]) {
case class ApiBranchProtection(
url: Option[ApiPath], // for output
enabled: Boolean,
required_status_checks: Option[ApiBranchProtection.Status]
) {
def status: ApiBranchProtection.Status = required_status_checks.getOrElse(ApiBranchProtection.statusNone)
}
@@ -15,13 +19,36 @@ object ApiBranchProtection {
def apply(info: ProtectedBranchService.ProtectedBranchInfo): ApiBranchProtection =
ApiBranchProtection(
url = Some(
ApiPath(
s"/api/v3/repos/${info.owner}/${info.repository}/branches/${info.branch}/protection"
)
),
enabled = info.enabled,
required_status_checks = Some(
Status(EnforcementLevel(info.enabled && info.contexts.nonEmpty, info.includeAdministrators), info.contexts)
Status(
Some(
ApiPath(
s"/api/v3/repos/${info.owner}/${info.repository}/branches/${info.branch}/protection/required_status_checks"
)
),
EnforcementLevel(info.enabled && info.contexts.nonEmpty, info.includeAdministrators),
info.contexts,
Some(
ApiPath(
s"/api/v3/repos/${info.owner}/${info.repository}/branches/${info.branch}/protection/required_status_checks/contexts"
)
)
)
)
)
val statusNone = Status(Off, Seq.empty)
case class Status(enforcement_level: EnforcementLevel, contexts: Seq[String])
val statusNone = Status(None, Off, Seq.empty, None)
case class Status(
url: Option[ApiPath], // for output
enforcement_level: EnforcementLevel,
contexts: Seq[String],
contexts_url: Option[ApiPath] // for output
)
sealed class EnforcementLevel(val name: String)
case object Off extends EnforcementLevel("off")
case object NonAdmins extends EnforcementLevel("non_admins")
@@ -39,7 +66,7 @@ object ApiBranchProtection {
}
}
implicit val enforcementLevelSerializer = new CustomSerializer[EnforcementLevel](
implicit val enforcementLevelSerializer: CustomSerializer[EnforcementLevel] = new CustomSerializer[EnforcementLevel](
format =>
(
{

View File

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

View File

@@ -28,7 +28,7 @@ object ApiContents {
"file",
fileInfo.name,
fileInfo.path,
fileInfo.commitId,
fileInfo.id.getName,
Some(Base64.getEncoder.encodeToString(arr)),
Some("base64")
)(repositoryName)

View File

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

View File

@@ -0,0 +1,49 @@
package gitbucket.core.api
import gitbucket.core.model.{Milestone, Repository}
import gitbucket.core.util.RepositoryName
import java.util.Date
/**
* https://docs.github.com/en/rest/reference/issues#milestones
*/
case class ApiMilestone(
url: ApiPath,
html_url: ApiPath,
// label_url: ApiPath,
id: Int,
number: Int,
state: String,
title: String,
description: String,
// creator: ApiUser, // MILESTONE table does not have created user column
open_issues: Int,
closed_issues: Int,
// created_at: Option[Date],
// updated_at: Option[Date],
closed_at: Option[Date],
due_on: Option[Date]
)
object ApiMilestone {
def apply(
repository: Repository,
milestone: Milestone,
open_issue_count: Int = 0,
closed_issue_count: Int = 0
): ApiMilestone =
ApiMilestone(
url = ApiPath(s"/api/v3/repos/${RepositoryName(repository).fullName}/milestones/${milestone.milestoneId}"),
html_url = ApiPath(s"/${RepositoryName(repository).fullName}/milestone/${milestone.milestoneId}"),
// label_url = ApiPath(s"/api/v3/repos/${RepositoryName(repository).fullName}/milestones/${milestone_number}/labels"),
id = milestone.milestoneId,
number = milestone.milestoneId, // use milestoneId as number
state = if (milestone.closedDate.isDefined) "closed" else "open",
title = milestone.title,
description = milestone.description.getOrElse(""),
open_issues = open_issue_count,
closed_issues = closed_issue_count,
closed_at = milestone.closedDate,
due_on = milestone.dueDate
)
}

View File

@@ -1,6 +1,6 @@
package gitbucket.core.api
import gitbucket.core.plugin.{PluginRegistry, PluginInfo}
import gitbucket.core.plugin.PluginInfo
case class ApiPlugin(
id: String,

View File

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

View File

@@ -1,5 +1,55 @@
package gitbucket.core.api
case class ApiObject(sha: String)
import gitbucket.core.util.JGitUtil.TagInfo
import gitbucket.core.util.RepositoryName
import org.eclipse.jgit.lib.Ref
case class ApiRef(ref: String, `object`: ApiObject)
case class ApiRefCommit(
sha: String,
`type`: String,
url: ApiPath
)
case class ApiRef(
ref: String,
node_id: String = "",
url: ApiPath,
`object`: ApiRefCommit,
)
object ApiRef {
def fromRef(
repositoryName: RepositoryName,
ref: Ref
): ApiRef =
ApiRef(
ref = ref.getName,
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/${ref.getName}"),
`object` = ApiRefCommit(
sha = ref.getObjectId.getName,
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${ref.getObjectId.getName}"),
`type` = "commit"
)
)
def fromTag(
repositoryName: RepositoryName,
tagInfo: TagInfo
): ApiRef =
ApiRef(
ref = s"refs/tags/${tagInfo.name}",
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/refs/tags/${tagInfo.name}"),
//the GH api distinguishes between "releases" and plain git tags
//for "releases", the api returns a reference to the release object (with type `tag`)
//this would be something like s"/api/v3/repos/${repositoryName.fullName}/git/tags/<hash-of-tag>"
//with a hash for the tag, which I do not fully understand
//since this is not yet implemented in GB, we always return a link to the plain `commit` object,
//which GH does for tags that are not annotated
`object` = ApiRefCommit(
sha = tagInfo.objectId,
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${tagInfo.objectId}"),
`type` = "commit"
)
)
}

View File

@@ -12,16 +12,16 @@ case class ApiRepository(
forks: Int,
`private`: Boolean,
default_branch: String,
owner: ApiUser
owner: ApiUser,
has_issues: Boolean
) {
val id = 0 // dummy id
val forks_count = forks
val watchers_count = watchers
val url = ApiPath(s"/api/v3/repos/${full_name}")
val http_url = ApiPath(s"/git/${full_name}.git")
val clone_url = ApiPath(s"/git/${full_name}.git")
val html_url = ApiPath(s"/${full_name}")
val ssh_url = Some(SshPath(s":${full_name}.git"))
val ssh_url = Some(SshPath(""))
}
object ApiRepository {
@@ -39,11 +39,16 @@ object ApiRepository {
forks = forkedCount,
`private` = repository.isPrivate,
default_branch = repository.defaultBranch,
owner = owner
owner = owner,
has_issues = if (repository.options.issuesOption == "DISABLE") false else true
)
def apply(repositoryInfo: RepositoryInfo, owner: ApiUser): ApiRepository =
ApiRepository(repositoryInfo.repository, owner, forkedCount = repositoryInfo.forkedCount)
ApiRepository(
repositoryInfo.repository,
owner,
forkedCount = repositoryInfo.forkedCount
)
def apply(repositoryInfo: RepositoryInfo, owner: Account): ApiRepository =
this(repositoryInfo, ApiUser(owner))
@@ -57,6 +62,7 @@ object ApiRepository {
forks = 0,
`private` = false,
default_branch = "master",
owner = owner
owner = owner,
has_issues = true
)
}

View File

@@ -0,0 +1,6 @@
package gitbucket.core.api
case class ApiRepositoryCollaborator(
permission: String,
user: ApiUser
)

View File

@@ -0,0 +1,44 @@
package gitbucket.core.api
import gitbucket.core.model.{RepositoryWebHook, WebHook}
/**
* https://docs.github.com/en/rest/reference/repos#webhooks
*/
case class ApiWebhookConfig(
content_type: String,
// insecure_ssl: String,
url: String
)
case class ApiWebhook(
`type`: String,
id: Int,
name: String,
active: Boolean,
events: List[String],
config: ApiWebhookConfig,
// updated_at: Option[Date],
// created_at: Option[Date],
url: ApiPath,
// test_url: ApiPath,
// ping_url: ApiPath,
// last_response: ...
)
object ApiWebhook {
def apply(
_type: String,
hook: RepositoryWebHook,
hookEvents: Set[WebHook.Event]
): ApiWebhook =
ApiWebhook(
`type` = _type,
id = hook.hookId,
name = "web", // dummy
active = true, // dummy
events = hookEvents.toList.map(_.name),
config = ApiWebhookConfig(hook.ctype.code, hook.url),
url = ApiPath(s"/api/v3/${hook.userName}/${hook.repositoryName}/hooks/${hook.hookId}")
)
}

View File

@@ -0,0 +1,14 @@
package gitbucket.core.api
import java.util.Date
case class CreateAMilestone(
title: String,
state: String = "open",
description: Option[String],
due_on: Option[Date]
) {
def isValid: Boolean = {
title.length <= 100 && title.matches("[a-zA-Z0-9\\-\\+_.]+")
}
}

View File

@@ -15,3 +15,11 @@ case class CreateAPullRequestAlt(
base: String,
maintainer_can_modify: Option[Boolean]
)
case class UpdateAPullRequest(
title: Option[String],
body: Option[String],
state: Option[String],
base: Option[String],
maintainer_can_modify: Option[Boolean],
)

View File

@@ -0,0 +1,7 @@
package gitbucket.core.api
/**
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#create-a-reference
* api form
*/
case class CreateARef(ref: String, sha: String)

View File

@@ -0,0 +1,35 @@
package gitbucket.core.api
case class CreateARepositoryWebhookConfig(
url: String,
content_type: String = "form",
insecure_ssl: String = "0",
secret: Option[String]
)
/**
* https://docs.github.com/en/rest/reference/repos#create-a-repository-webhook
*/
case class CreateARepositoryWebhook(
name: String = "web",
config: CreateARepositoryWebhookConfig,
events: List[String] = List("push"),
active: Boolean = true
) {
def isValid: Boolean = {
config.content_type == "form" || config.content_type == "json"
}
}
case class UpdateARepositoryWebhook(
name: String = "web",
config: CreateARepositoryWebhookConfig,
events: List[String] = List("push"),
add_events: List[String] = List(),
remove_events: List[String] = List(),
active: Boolean = true
) {
def isValid: Boolean = {
config.content_type == "form" || config.content_type == "json"
}
}

View File

@@ -0,0 +1,23 @@
package gitbucket.core.api
/**
* https://docs.github.com/en/rest/reference/pulls#merge-a-pull-request
*/
case class MergeAPullRequest(
commit_title: Option[String],
commit_message: Option[String],
/* TODO: Not Implemented
sha: Option[String],*/
merge_method: Option[String]
)
case class SuccessToMergePrResponse(
sha: String,
merged: Boolean,
message: String
)
case class FailToMergePrResponse(
documentation_url: String,
message: String
)

View File

@@ -0,0 +1,7 @@
package gitbucket.core.api
/**
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#update-a-reference
* api form
*/
case class UpdateARef(sha: String, force: Boolean)

View File

@@ -35,6 +35,7 @@ class AccountController
with WebHookService
with PrioritiesService
with RepositoryCreationService
with RequestCache
trait AccountControllerBase extends AccountManagementControllerBase {
self: AccountService
@@ -81,9 +82,11 @@ trait AccountControllerBase extends AccountManagementControllerBase {
case class PersonalTokenForm(note: String)
case class SyntaxHighlighterThemeForm(theme: String)
val newForm = mapping(
"userName" -> trim(label("User name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
"password" -> trim(label("Password", text(required, maxlength(20)))),
"password" -> trim(label("Password", text(required, maxlength(40)))),
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))),
"extraMailAddresses" -> list(
@@ -95,7 +98,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
)(AccountNewForm.apply)
val editForm = mapping(
"password" -> trim(label("Password", optional(text(maxlength(20))))),
"password" -> trim(label("Password", optional(text(maxlength(40))))),
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))),
"extraMailAddresses" -> list(
@@ -121,6 +124,19 @@ trait AccountControllerBase extends AccountManagementControllerBase {
"note" -> trim(label("Token", text(required, maxlength(100))))
)(PersonalTokenForm.apply)
val syntaxHighlighterThemeForm = mapping(
"highlighterTheme" -> trim(label("Theme", text(required)))
)(SyntaxHighlighterThemeForm.apply)
val resetPasswordEmailForm = mapping(
"mailAddress" -> trim(label("Email", text(required)))
)(ResetPasswordEmailForm.apply)
val resetPasswordForm = mapping(
"token" -> trim(label("Token", text(required))),
"password" -> trim(label("Password", text(required, maxlength(40))))
)(ResetPasswordForm.apply)
case class NewGroupForm(
groupName: String,
description: Option[String],
@@ -136,6 +152,13 @@ trait AccountControllerBase extends AccountManagementControllerBase {
members: String,
clearImage: Boolean
)
case class ResetPasswordEmailForm(
mailAddress: String
)
case class ResetPasswordForm(
token: String,
password: String
)
val newGroupForm = mapping(
"groupName" -> trim(label("Group name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
@@ -427,7 +450,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
post("/:userName/_personalToken", personalTokenForm)(oneselfOnly { form =>
val userName = params("userName")
getAccountByUserName(userName).map { x =>
getAccountByUserName(userName).foreach { x =>
val (tokenId, token) = generateAccessToken(userName, form.note)
flash.update("generatedToken", (tokenId, token))
}
@@ -441,6 +464,29 @@ trait AccountControllerBase extends AccountManagementControllerBase {
redirect(s"/${userName}/_application")
})
/**
* Display the user preference settings page
*/
get("/:userName/_preferences")(oneselfOnly {
val userName = params("userName")
val currentTheme = getAccountPreference(userName) match {
case Some(accountHighlighter) => accountHighlighter.highlighterTheme
case _ => "github-v2"
}
getAccountByUserName(userName).map { x =>
html.preferences(x, currentTheme)
} getOrElse NotFound()
})
/**
* Update the syntax highlighter setting of user
*/
post("/:userName/_preferences/highlighter", syntaxHighlighterThemeForm)(oneselfOnly { form =>
val userName = params("userName")
addOrUpdateAccountPreference(userName, form.theme)
redirect(s"/${userName}/_preferences")
})
get("/:userName/_hooks")(managersOnly {
val userName = params("userName")
getAccountByUserName(userName).map { account =>
@@ -521,7 +567,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
val url = params("url")
val token = Some(params("token"))
val ctype = WebHookContentType.valueOf(params("ctype"))
val dummyWebHookInfo = RepositoryWebHook(userName, "dummy", url, ctype, token)
val dummyWebHookInfo =
RepositoryWebHook(userName = userName, repositoryName = "dummy", url = url, ctype = ctype, token = token)
val dummyPayload = {
val ownerAccount = getAccountByUserName(userName).get
WebHookPushPayload.createDummyPayload(ownerAccount)
@@ -571,7 +618,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
})
get("/register") {
if (context.settings.allowAccountRegistration) {
if (context.settings.basicBehavior.allowAccountRegistration) {
if (context.loginAccount.isDefined) {
redirect("/")
} else {
@@ -581,7 +628,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
}
post("/register", newForm) { form =>
if (context.settings.allowAccountRegistration) {
if (context.settings.basicBehavior.allowAccountRegistration) {
createAccount(
form.userName,
pbkdf2_sha256(form.password),
@@ -597,8 +644,67 @@ trait AccountControllerBase extends AccountManagementControllerBase {
} else NotFound()
}
get("/reset") {
if (context.settings.basicBehavior.allowResetPassword) {
html.reset()
} else NotFound()
}
post("/reset", resetPasswordEmailForm) { form =>
if (context.settings.basicBehavior.allowResetPassword) {
getAccountByMailAddress(form.mailAddress).foreach { account =>
val token = generateResetPasswordToken(form.mailAddress)
val mailer = new Mailer(context.settings)
mailer.send(
form.mailAddress,
"Reset password",
s"""Hello, ${account.fullName}!
|
|You requested to reset the password for your GitBucket account.
|If you are not sure about the request, you can ignore this email.
|Otherwise, click the following link to set the new password:
|${context.baseUrl}/reset/form/${token}
|""".stripMargin
)
}
redirect("/reset/sent")
} else NotFound()
}
get("/reset/sent") {
if (context.settings.basicBehavior.allowResetPassword) {
html.resetsent()
} else NotFound()
}
get("/reset/form/:token") {
if (context.settings.basicBehavior.allowResetPassword) {
val token = params("token")
decodeResetPasswordToken(token)
.map { _ =>
html.resetform(token)
}
.getOrElse(NotFound())
} else NotFound()
}
post("/reset/form", resetPasswordForm) { form =>
if (context.settings.basicBehavior.allowResetPassword) {
decodeResetPasswordToken(form.token)
.flatMap { mailAddress =>
getAccountByMailAddress(mailAddress).map { account =>
updateAccount(account.copy(password = pbkdf2_sha256(form.password)))
html.resetcomplete()
}
}
.getOrElse(NotFound())
} else NotFound()
}
get("/groups/new")(usersOnly {
html.creategroup(List(GroupMember("", context.loginAccount.get.userName, true)))
context.withLoginAccount { loginAccount =>
html.creategroup(List(GroupMember("", loginAccount.userName, true)))
}
})
post("/groups/new", newGroupForm)(usersOnly { form =>
@@ -619,22 +725,20 @@ trait AccountControllerBase extends AccountManagementControllerBase {
})
get("/:groupName/_editgroup")(managersOnly {
defining(params("groupName")) { groupName =>
getAccountByUserName(groupName, true).map { account =>
html.editgroup(account, getGroupMembers(groupName), flash.get("info"))
} getOrElse NotFound()
}
val groupName = params("groupName")
getAccountByUserName(groupName, true).map { account =>
html.editgroup(account, getGroupMembers(groupName), flash.get("info"))
} getOrElse NotFound()
})
get("/:groupName/_deletegroup")(managersOnly {
defining(params("groupName")) {
groupName =>
// Remove from GROUP_MEMBER
updateGroupMembers(groupName, Nil)
// Disable group
getAccountByUserName(groupName, false).foreach { account =>
updateGroup(groupName, account.description, account.url, true)
}
val groupName = params("groupName")
// Remove from GROUP_MEMBER
updateGroupMembers(groupName, Nil)
// Disable group
getAccountByUserName(groupName, false).foreach { account =>
updateGroup(groupName, account.description, account.url, true)
}
// // Remove repositories
// getRepositoryNamesOfUser(groupName).foreach { repositoryName =>
// deleteRepository(groupName, repositoryName)
@@ -642,28 +746,25 @@ trait AccountControllerBase extends AccountManagementControllerBase {
// FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName))
// FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName))
// }
}
redirect("/")
})
post("/:groupName/_editgroup", editGroupForm)(managersOnly { form =>
defining(
params("groupName"),
form.members
.split(",")
.map {
_.split(":") match {
case Array(userName, isManager) => (userName, isManager.toBoolean)
}
val groupName = params("groupName")
val members = form.members
.split(",")
.map {
_.split(":") match {
case Array(userName, isManager) => (userName, isManager.toBoolean)
}
.toList
) {
case (groupName, members) =>
getAccountByUserName(groupName, true).map { account =>
updateGroup(groupName, form.description, form.url, false)
}
.toList
// Update GROUP_MEMBER
updateGroupMembers(form.groupName, members)
getAccountByUserName(groupName, true).map { account =>
updateGroup(groupName, form.description, form.url, false)
// Update GROUP_MEMBER
updateGroupMembers(form.groupName, members)
// // Update COLLABORATOR for group repositories
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
// removeCollaborators(form.groupName, repositoryName)
@@ -672,87 +773,104 @@ trait AccountControllerBase extends AccountManagementControllerBase {
// }
// }
updateImage(form.groupName, form.fileId, form.clearImage)
updateImage(form.groupName, form.fileId, form.clearImage)
flash.update("info", "Account information has been updated.")
redirect(s"/${groupName}/_editgroup")
flash.update("info", "Account information has been updated.")
redirect(s"/${groupName}/_editgroup")
} getOrElse NotFound()
}
} getOrElse NotFound()
})
/**
* Show the new repository form.
*/
get("/new")(usersOnly {
html.newrepo(getGroupsByUserName(context.loginAccount.get.userName), context.settings.isCreateRepoOptionPublic)
context.withLoginAccount { loginAccount =>
html.newrepo(getGroupsByUserName(loginAccount.userName), context.settings.basicBehavior.isCreateRepoOptionPublic)
}
})
/**
* Create new repository.
*/
post("/new", newRepositoryForm)(usersOnly { form =>
if (context.settings.repositoryOperation.create || context.loginAccount.get.isAdmin) {
LockUtil.lock(s"${form.owner}/${form.name}") {
if (getRepository(form.owner, form.name).isEmpty) {
createRepository(
context.loginAccount.get,
form.owner,
form.name,
form.description,
form.isPrivate,
form.initOption,
form.sourceUrl
)
}
}
// redirect to the repository
redirect(s"/${form.owner}/${form.name}")
} else Forbidden()
context.withLoginAccount {
loginAccount =>
if (context.settings.basicBehavior.repositoryOperation.create || loginAccount.isAdmin) {
LockUtil.lock(s"${form.owner}/${form.name}") {
if (getRepository(form.owner, form.name).isDefined) {
// redirect to the repository if repository already exists
redirect(s"/${form.owner}/${form.name}")
} else if (!canCreateRepository(form.owner, loginAccount)) {
// Permission error
Forbidden()
} else {
// create repository asynchronously
createRepository(
loginAccount,
form.owner,
form.name,
form.description,
form.isPrivate,
form.initOption,
form.sourceUrl
)
// redirect to the repository
redirect(s"/${form.owner}/${form.name}")
}
}
} else Forbidden()
}
})
get("/:owner/:repository/fork")(readableUsersOnly { repository =>
val loginAccount = context.loginAccount.get
if (repository.repository.options.allowFork && (context.settings.repositoryOperation.fork || loginAccount.isAdmin)) {
val loginUserName = loginAccount.userName
val groups = getGroupsByUserName(loginUserName)
groups match {
case _: List[String] =>
val managerPermissions = groups.map { group =>
val members = getGroupMembers(group)
context.loginAccount.exists(
x =>
members.exists { member =>
member.userName == x.userName && member.isManager
context.withLoginAccount {
loginAccount =>
if (repository.repository.options.allowFork && (context.settings.basicBehavior.repositoryOperation.fork || loginAccount.isAdmin)) {
val loginUserName = loginAccount.userName
val groups = getGroupsByUserName(loginUserName)
groups match {
case _: List[String] =>
val managerPermissions = groups.map { group =>
val members = getGroupMembers(group)
context.loginAccount.exists(
x =>
members.exists { member =>
member.userName == x.userName && member.isManager
}
)
}
)
helper.html.forkrepository(
repository,
(groups zip managerPermissions).sortBy(_._1)
)
case _ => redirect(s"/${loginUserName}")
}
helper.html.forkrepository(
repository,
(groups zip managerPermissions).sortBy(_._1)
)
case _ => redirect(s"/${loginUserName}")
}
} else BadRequest()
} else BadRequest()
}
})
post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) =>
val loginAccount = context.loginAccount.get
if (repository.repository.options.allowFork && (context.settings.repositoryOperation.fork || loginAccount.isAdmin)) {
val loginUserName = loginAccount.userName
val accountName = form.accountName
context.withLoginAccount {
loginAccount =>
if (repository.repository.options.allowFork && (context.settings.basicBehavior.repositoryOperation.fork || loginAccount.isAdmin)) {
val loginUserName = loginAccount.userName
val accountName = form.accountName
if (getRepository(accountName, repository.name).isDefined ||
(accountName != loginUserName && !getGroupsByUserName(loginUserName).contains(accountName))) {
// redirect to the repository if repository already exists
redirect(s"/${accountName}/${repository.name}")
} else {
// fork repository asynchronously
forkRepository(accountName, repository, loginUserName)
// redirect to the repository
redirect(s"/${accountName}/${repository.name}")
}
} else Forbidden()
if (getRepository(accountName, repository.name).isDefined) {
// redirect to the repository if repository already exists
redirect(s"/${accountName}/${repository.name}")
} else if (!canCreateRepository(accountName, loginAccount)) {
// Permission error
Forbidden()
} else {
// fork repository asynchronously
forkRepository(accountName, repository, loginUserName)
// redirect to the repository
redirect(s"/${accountName}/${repository.name}")
}
} else Forbidden()
}
})
private def existsAccount: Constraint = new Constraint() {

View File

@@ -13,6 +13,7 @@ class ApiController
with ApiIssueCommentControllerBase
with ApiIssueControllerBase
with ApiIssueLabelControllerBase
with ApiIssueMilestoneControllerBase
with ApiOrganizationControllerBase
with ApiPullRequestControllerBase
with ApiReleaseControllerBase
@@ -22,6 +23,7 @@ class ApiController
with ApiRepositoryContentsControllerBase
with ApiRepositoryControllerBase
with ApiRepositoryStatusControllerBase
with ApiRepositoryWebhookControllerBase
with ApiUserControllerBase
with RepositoryService
with AccountService
@@ -52,6 +54,7 @@ class ApiController
with ReferrerAuthenticator
with ReadableUsersAuthenticator
with WritableUsersAuthenticator
with RequestCache
trait ApiControllerBase extends ControllerBase {
@@ -97,4 +100,16 @@ trait ApiControllerBase extends ControllerBase {
get("/api/v3/gitbucket/plugins") {
PluginRegistry().getPlugins().map { ApiPlugin(_) }
}
/**
* https://docs.github.com/en/enterprise-server@2.21/rest/reference/meta#get-github-enterprise-server-meta-information
*/
get("/api/v3/meta") {
JsonFormat(
Map(
"https://api.github.com/meta" -> context.loginAccount.isDefined,
"installed_version" -> "2.21.0"
)
)
}
}

View File

@@ -28,6 +28,7 @@ import org.eclipse.jgit.revwalk.RevCommit
import org.eclipse.jgit.treewalk._
import org.apache.commons.io.IOUtils
import org.slf4j.LoggerFactory
import org.json4s.Formats
/**
* Provides generic features for controller implementations.
@@ -43,7 +44,7 @@ abstract class ControllerBase
private val logger = LoggerFactory.getLogger(getClass)
implicit val jsonFormats = gitbucket.core.api.JsonFormat.jsonFormats
implicit val jsonFormats: Formats = gitbucket.core.api.JsonFormat.jsonFormats
before("/api/v3/*") {
contentType = formats("json")
@@ -157,11 +158,8 @@ abstract class ControllerBase
org.scalatra.Unauthorized(
redirect(
"/signin?redirect=" + StringUtil.urlEncode(
defining(request.getQueryString) { queryString =>
request.getRequestURI.substring(request.getContextPath.length) + (if (queryString != null)
"?" + queryString
else "")
}
request.getRequestURI
.substring(request.getContextPath.length) + Option(request.getQueryString).map("?" + _).getOrElse("")
)
)
)
@@ -255,7 +253,7 @@ abstract class ControllerBase
repository: RepositoryService.RepositoryInfo
): Unit = {
JGitUtil.getObjectLoaderFromId(git, objectId) { loader =>
contentType = FileUtil.getSafeMimeType(path)
contentType = FileUtil.getSafeMimeType(path, repository.repository.options.safeMode)
if (loader.isLarge) {
response.setContentLength(loader.getSize.toInt)
@@ -302,20 +300,27 @@ case class Context(
}
val sidebarCollapse = request.getSession.getAttribute("sidebar-collapse") != null
def withLoginAccount(f: Account => Any): Any = {
loginAccount match {
case Some(loginAccount) => f(loginAccount)
case None => Unauthorized()
}
}
/**
* Get object from cache.
*
* If object has not been cached with the specified key then retrieves by given action.
* Cached object are available during a request.
*/
def cache[A](key: String)(action: => A): A =
defining(Keys.Request.Cache(key)) { cacheKey =>
Option(request.getAttribute(cacheKey).asInstanceOf[A]).getOrElse {
val newObject = action
request.setAttribute(cacheKey, newObject)
newObject
}
def cache[A](key: String)(action: => A): A = {
val cacheKey = Keys.Request.Cache(key)
Option(request.getAttribute(cacheKey).asInstanceOf[A]).getOrElse {
val newObject = action
request.setAttribute(cacheKey, newObject)
newObject
}
}
}
@@ -421,7 +426,7 @@ trait AccountManagementControllerBase extends ControllerBase {
"new"
)
protected def reservedNames(): Constraint = new Constraint() {
protected def reservedNames: Constraint = new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] =
if (allReservedNames.contains(value.toLowerCase)) {
Some(s"${value} is reserved")

View File

@@ -1,6 +1,7 @@
package gitbucket.core.controller
import gitbucket.core.dashboard.html
import gitbucket.core.model.Account
import gitbucket.core.service._
import gitbucket.core.util.{Keys, UsersAuthenticator}
import gitbucket.core.util.Implicits._
@@ -21,51 +22,76 @@ class DashboardController
with WebHookPullRequestService
with WebHookPullRequestReviewCommentService
with MilestonesService
with CommitStatusService
with UsersAuthenticator
with RequestCache
trait DashboardControllerBase extends ControllerBase {
self: IssuesService with PullRequestService with RepositoryService with AccountService with UsersAuthenticator =>
self: IssuesService
with PullRequestService
with RepositoryService
with AccountService
with CommitStatusService
with UsersAuthenticator =>
get("/dashboard/repos")(usersOnly {
val repos = getVisibleRepositories(
context.loginAccount,
None,
withoutPhysicalInfo = true,
limit = context.settings.limitVisibleRepositories
)
html.repos(getGroupNames(context.loginAccount.get.userName), repos, repos)
context.withLoginAccount { loginAccount =>
val repos = getVisibleRepositories(
context.loginAccount,
None,
withoutPhysicalInfo = true,
limit = context.settings.basicBehavior.limitVisibleRepositories
)
html.repos(getGroupNames(loginAccount.userName), repos, repos)
}
})
get("/dashboard/issues")(usersOnly {
searchIssues("created_by")
context.withLoginAccount { loginAccount =>
searchIssues(loginAccount, "created_by")
}
})
get("/dashboard/issues/assigned")(usersOnly {
searchIssues("assigned")
context.withLoginAccount { loginAccount =>
searchIssues(loginAccount, "assigned")
}
})
get("/dashboard/issues/created_by")(usersOnly {
searchIssues("created_by")
context.withLoginAccount { loginAccount =>
searchIssues(loginAccount, "created_by")
}
})
get("/dashboard/issues/mentioned")(usersOnly {
searchIssues("mentioned")
context.withLoginAccount { loginAccount =>
searchIssues(loginAccount, "mentioned")
}
})
get("/dashboard/pulls")(usersOnly {
searchPullRequests("created_by")
context.withLoginAccount { loginAccount =>
searchPullRequests(loginAccount, "created_by")
}
})
get("/dashboard/pulls/created_by")(usersOnly {
searchPullRequests("created_by")
context.withLoginAccount { loginAccount =>
searchPullRequests(loginAccount, "created_by")
}
})
get("/dashboard/pulls/assigned")(usersOnly {
searchPullRequests("assigned")
context.withLoginAccount { loginAccount =>
searchPullRequests(loginAccount, "assigned")
}
})
get("/dashboard/pulls/mentioned")(usersOnly {
searchPullRequests("mentioned")
context.withLoginAccount { loginAccount =>
searchPullRequests(loginAccount, "mentioned")
}
})
private def getOrCreateCondition(key: String, filter: String, userName: String) = {
@@ -78,19 +104,20 @@ trait DashboardControllerBase extends ControllerBase {
}
}
private def searchIssues(filter: String) = {
private def searchIssues(loginAccount: Account, filter: String) = {
import IssuesService._
val userName = context.loginAccount.get.userName
val userName = loginAccount.userName
val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName)
val userRepos = getUserRepositories(userName, true).map(repo => repo.owner -> repo.name)
val page = IssueSearchCondition.page(request)
val issues = searchIssue(condition, IssueSearchOption.Issues, (page - 1) * IssueLimit, IssueLimit, userRepos: _*)
html.issues(
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, userRepos: _*),
issues.map(issue => (issue, None)),
page,
countIssue(condition.copy(state = "open"), false, userRepos: _*),
countIssue(condition.copy(state = "closed"), false, userRepos: _*),
countIssue(condition.copy(state = "open"), IssueSearchOption.Issues, userRepos: _*),
countIssue(condition.copy(state = "closed"), IssueSearchOption.Issues, userRepos: _*),
filter match {
case "assigned" => condition.copy(assigned = Some(Some(userName)))
case "mentioned" => condition.copy(mentioned = Some(userName))
@@ -102,25 +129,37 @@ trait DashboardControllerBase extends ControllerBase {
context.loginAccount,
None,
withoutPhysicalInfo = true,
limit = context.settings.limitVisibleRepositories
limit = context.settings.basicBehavior.limitVisibleRepositories
)
)
}
private def searchPullRequests(filter: String) = {
private def searchPullRequests(loginAccount: Account, filter: String) = {
import IssuesService._
import PullRequestService._
val userName = context.loginAccount.get.userName
val userName = loginAccount.userName
val condition = getOrCreateCondition(Keys.Session.DashboardPulls, filter, userName)
val allRepos = getAllRepositories(userName)
val page = IssueSearchCondition.page(request)
val issues = searchIssue(
condition,
IssueSearchOption.PullRequests,
(page - 1) * PullRequestLimit,
PullRequestLimit,
allRepos: _*
)
val status = issues.map { issue =>
issue.commitId.flatMap { commitId =>
getCommitStatusWithSummary(issue.issue.userName, issue.issue.repositoryName, commitId)
}
}
html.pulls(
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, allRepos: _*),
issues.zip(status),
page,
countIssue(condition.copy(state = "open"), true, allRepos: _*),
countIssue(condition.copy(state = "closed"), true, allRepos: _*),
countIssue(condition.copy(state = "open"), IssueSearchOption.PullRequests, allRepos: _*),
countIssue(condition.copy(state = "closed"), IssueSearchOption.PullRequests, allRepos: _*),
filter match {
case "assigned" => condition.copy(assigned = Some(Some(userName)))
case "mentioned" => condition.copy(mentioned = Some(userName))
@@ -132,7 +171,7 @@ trait DashboardControllerBase extends ControllerBase {
context.loginAccount,
None,
withoutPhysicalInfo = true,
limit = context.settings.limitVisibleRepositories
limit = context.settings.basicBehavior.limitVisibleRepositories
)
)
}

View File

@@ -6,7 +6,6 @@ import gitbucket.core.model.Account
import gitbucket.core.service.{AccountService, ReleaseService, RepositoryService}
import gitbucket.core.servlet.Database
import gitbucket.core.util._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.Directory._
import gitbucket.core.util.Implicits._
import org.eclipse.jgit.api.Git
@@ -18,6 +17,7 @@ import org.apache.commons.io.{FileUtils, IOUtils}
import scala.util.Using
import gitbucket.core.service.SystemSettingsService
import slick.jdbc.JdbcBackend.Session
/**
* Provides Ajax based file upload functionality.
@@ -37,10 +37,10 @@ class FileUploadController
execute(
{ (file, fileId) =>
FileUtils
.writeByteArrayToFile(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(fileId)), file.get)
.writeByteArrayToFile(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(fileId)), file.get())
session += Keys.Session.Upload(fileId) -> file.name
},
FileUtil.isImage
FileUtil.isImage(_)
)
}
@@ -49,7 +49,7 @@ class FileUploadController
execute(
{ (file, fileId) =>
FileUtils
.writeByteArrayToFile(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(fileId)), file.get)
.writeByteArrayToFile(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(fileId)), file.get())
session += Keys.Session.Upload(fileId) -> file.name
},
_ => true
@@ -65,7 +65,7 @@ class FileUploadController
getAttachedDir(params("owner"), params("repository")),
FileUtil.checkFilename(fileId + "." + FileUtil.getExtension(file.getName))
),
file.get
file.get()
)
},
_ => true
@@ -131,7 +131,7 @@ class FileUploadController
} getOrElse BadRequest()
}
post("/release/:owner/:repository/:tag") {
post("/release/:owner/:repository/*") {
setMultipartConfigForLargeFile()
session
.get(Keys.Session.LoginAccount)
@@ -139,12 +139,12 @@ class FileUploadController
case _: Account =>
val owner = params("owner")
val repository = params("repository")
val tag = params("tag")
val tag = multiParams("splat").head
execute(
{ (file, fileId) =>
FileUtils.writeByteArrayToFile(
new File(getReleaseFilesDir(owner, repository), FileUtil.checkFilename(tag + "/" + fileId)),
file.get
file.get()
)
},
_ => true
@@ -178,7 +178,7 @@ class FileUploadController
}
private def onlyWikiEditable(owner: String, repository: String, loginAccount: Account)(action: => Any): Any = {
implicit val session = Database.getSession(request)
implicit val session: Session = Database.getSession(request)
getRepository(owner, repository) match {
case Some(x) =>
x.repository.options.wikiOption match {
@@ -191,14 +191,13 @@ class FileUploadController
}
}
private def execute(f: (FileItem, String) => Unit, mimeTypeChcker: (String) => Boolean) =
private def execute(f: (FileItem, String) => Unit, mimeTypeChecker: (String) => Boolean) =
fileParams.get("file") match {
case Some(file) if (mimeTypeChcker(file.name)) =>
defining(FileUtil.generateFileId) { fileId =>
f(file, fileId)
contentType = "text/plain"
Ok(fileId)
}
case Some(file) if mimeTypeChecker(file.name) =>
val fileId = FileUtil.generateFileId
f(file, fileId)
contentType = "text/plain"
Ok(fileId)
case _ => BadRequest()
}

View File

@@ -8,7 +8,6 @@ import gitbucket.core.helper.xml
import gitbucket.core.model.Account
import gitbucket.core.service._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util._
import gitbucket.core.view.helpers._
import org.scalatra.Ok
@@ -29,6 +28,7 @@ class IndexController
with AccessTokenService
with AccountFederationService
with OpenIDConnectService
with RequestCache
trait IndexControllerBase extends ControllerBase {
self: RepositoryService
@@ -69,7 +69,7 @@ trait IndexControllerBase extends ControllerBase {
Some(account),
None,
withoutPhysicalInfo = true,
limit = context.settings.limitVisibleRepositories
limit = context.settings.basicBehavior.limitVisibleRepositories
),
showBannerToCreatePersonalAccessToken = hasAccountFederation(account.userName) && !hasAccessToken(
account.userName
@@ -78,7 +78,7 @@ trait IndexControllerBase extends ControllerBase {
}
.getOrElse {
gitbucket.core.html.index(
getRecentActivities(),
getRecentPublicActivities(),
getVisibleRepositories(None, withoutPhysicalInfo = true),
showBannerToCreatePersonalAccessToken = false
)
@@ -161,7 +161,7 @@ trait IndexControllerBase extends ControllerBase {
get("/activities.atom") {
contentType = "application/atom+xml; type=feed"
xml.feed(getRecentActivities())
xml.feed(getRecentPublicActivities())
}
post("/sidebar-collapse") {
@@ -212,8 +212,10 @@ trait IndexControllerBase extends ControllerBase {
}
.map { t =>
Map(
"label" -> s"${avatar(t.userName, 16)}<b>@${StringUtil.escapeHtml(t.userName)}</b> ${StringUtil
.escapeHtml(t.fullName)}",
"label" -> s"${avatar(t.userName, 16)}<b>@${StringUtil.escapeHtml(
StringUtil.cutTail(t.userName, 25, "...")
)}</b> ${StringUtil
.escapeHtml(StringUtil.cutTail(t.fullName, 25, "..."))}",
"value" -> t.userName
)
}
@@ -234,50 +236,49 @@ trait IndexControllerBase extends ControllerBase {
// TODO Move to RepositoryViewrController?
get("/:owner/:repository/search")(referrersOnly { repository =>
defining(params.getOrElse("q", "").trim, params.getOrElse("type", "code")) {
case (query, target) =>
val page = try {
val i = params.getOrElse("page", "1").toInt
if (i <= 0) 1 else i
} catch {
case e: NumberFormatException => 1
}
val query = params.getOrElse("q", "").trim
val target = params.getOrElse("type", "code")
val page = try {
val i = params.getOrElse("page", "1").toInt
if (i <= 0) 1 else i
} catch {
case _: NumberFormatException => 1
}
target.toLowerCase match {
case "issues" =>
gitbucket.core.search.html.issues(
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, false) else Nil,
false,
query,
page,
repository
)
target.toLowerCase match {
case "issues" =>
gitbucket.core.search.html.issues(
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, false) else Nil,
false,
query,
page,
repository
)
case "pulls" =>
gitbucket.core.search.html.issues(
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, true) else Nil,
true,
query,
page,
repository
)
case "pulls" =>
gitbucket.core.search.html.issues(
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, true) else Nil,
true,
query,
page,
repository
)
case "wiki" =>
gitbucket.core.search.html.wiki(
if (query.nonEmpty) searchWikiPages(repository.owner, repository.name, query) else Nil,
query,
page,
repository
)
case "wiki" =>
gitbucket.core.search.html.wiki(
if (query.nonEmpty) searchWikiPages(repository.owner, repository.name, query) else Nil,
query,
page,
repository
)
case _ =>
gitbucket.core.search.html.code(
if (query.nonEmpty) searchFiles(repository.owner, repository.name, query) else Nil,
query,
page,
repository
)
}
case _ =>
gitbucket.core.search.html.code(
if (query.nonEmpty) searchFiles(repository.owner, repository.name, query) else Nil,
query,
page,
repository
)
}
})
@@ -288,11 +289,11 @@ trait IndexControllerBase extends ControllerBase {
context.loginAccount,
None,
withoutPhysicalInfo = true,
limit = context.settings.limitVisibleRepositories
limit = context.settings.basicBehavior.limitVisibleRepositories
)
val repositories = {
context.settings.limitVisibleRepositories match {
context.settings.basicBehavior.limitVisibleRepositories match {
case true =>
getVisibleRepositories(
context.loginAccount,

View File

@@ -1,9 +1,9 @@
package gitbucket.core.controller
import gitbucket.core.issues.html
import gitbucket.core.model.{Account, CustomFieldBehavior}
import gitbucket.core.service.IssuesService._
import gitbucket.core.service._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.Implicits._
import gitbucket.core.util._
import gitbucket.core.view
@@ -21,6 +21,7 @@ class IssuesController
with ActivityService
with HandleCommentService
with IssueCreationService
with CustomFieldsService
with ReadableUsersAuthenticator
with ReferrerAuthenticator
with WritableUsersAuthenticator
@@ -30,6 +31,7 @@ class IssuesController
with WebHookPullRequestReviewCommentService
with CommitsService
with PrioritiesService
with RequestCache
trait IssuesControllerBase extends ControllerBase {
self: IssuesService
@@ -40,6 +42,7 @@ trait IssuesControllerBase extends ControllerBase {
with ActivityService
with HandleCommentService
with IssueCreationService
with CustomFieldsService
with ReadableUsersAuthenticator
with ReferrerAuthenticator
with WritableUsersAuthenticator
@@ -50,7 +53,7 @@ trait IssuesControllerBase extends ControllerBase {
case class IssueCreateForm(
title: String,
content: Option[String],
assignedUserName: Option[String],
assigneeUserNames: Option[String],
milestoneId: Option[Int],
priorityId: Option[Int],
labelNames: Option[String]
@@ -61,7 +64,7 @@ trait IssuesControllerBase extends ControllerBase {
val issueCreateForm = mapping(
"title" -> trim(label("Title", text(required))),
"content" -> trim(optional(text())),
"assignedUserName" -> trim(optional(text())),
"assigneeUserNames" -> trim(optional(text())),
"milestoneId" -> trim(optional(number())),
"priorityId" -> trim(optional(number())),
"labelNames" -> trim(optional(text()))
@@ -94,211 +97,246 @@ trait IssuesControllerBase extends ControllerBase {
})
get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
defining(repository.owner, repository.name, params("id")) {
case (owner, name, issueId) =>
getIssue(owner, name, issueId) map {
issue =>
if (issue.isPullRequest) {
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
} else {
html.issue(
issue,
getComments(owner, name, issueId.toInt),
getIssueLabels(owner, name, issueId.toInt),
getAssignableUserNames(owner, name),
getMilestonesWithIssueCount(owner, name),
getPriorities(owner, name),
getLabels(owner, name),
isIssueEditable(repository),
isIssueManageable(repository),
repository
)
}
} getOrElse NotFound()
}
val issueId = params("id")
getIssue(repository.owner, repository.name, issueId) map {
issue =>
if (issue.isPullRequest) {
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
} else {
html.issue(
issue,
getComments(repository.owner, repository.name, issueId.toInt),
getIssueLabels(repository.owner, repository.name, issueId.toInt),
getIssueAssignees(repository.owner, repository.name, issueId.toInt),
getAssignableUserNames(repository.owner, repository.name),
getMilestonesWithIssueCount(repository.owner, repository.name),
getPriorities(repository.owner, repository.name),
getLabels(repository.owner, repository.name),
getCustomFieldsWithValue(repository.owner, repository.name, issueId.toInt).filter(_._1.enableForIssues),
isIssueEditable(repository),
isIssueManageable(repository),
isIssueCommentManageable(repository),
repository
)
}
} getOrElse NotFound()
})
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator?
defining(repository.owner, repository.name) {
case (owner, name) =>
html.create(
getAssignableUserNames(owner, name),
getMilestones(owner, name),
getPriorities(owner, name),
getDefaultPriority(owner, name),
getLabels(owner, name),
isIssueManageable(repository),
getContentTemplate(repository, "ISSUE_TEMPLATE"),
repository
)
}
html.create(
getAssignableUserNames(repository.owner, repository.name),
getMilestones(repository.owner, repository.name),
getPriorities(repository.owner, repository.name),
getDefaultPriority(repository.owner, repository.name),
getLabels(repository.owner, repository.name),
getCustomFields(repository.owner, repository.name).filter(_.enableForIssues),
isIssueManageable(repository),
getContentTemplate(repository, "ISSUE_TEMPLATE"),
repository
)
} else Unauthorized()
})
post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) =>
if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator?
val issue = createIssue(
repository,
form.title,
form.content,
form.assignedUserName,
form.milestoneId,
form.priorityId,
form.labelNames.toSeq.flatMap(_.split(",")),
context.loginAccount.get
)
context.withLoginAccount {
loginAccount =>
if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator?
val issue = createIssue(
repository,
form.title,
form.content,
form.assigneeUserNames.toSeq.flatMap(_.split(",")),
form.milestoneId,
form.priorityId,
form.labelNames.toSeq.flatMap(_.split(",")),
loginAccount
)
redirect(s"/${issue.userName}/${issue.repositoryName}/issues/${issue.issueId}")
} else Unauthorized()
// Insert custom field values
params.toMap.foreach {
case (key, value) =>
if (key.startsWith("custom-field-")) {
getCustomField(
repository.owner,
repository.name,
key.replaceFirst("^custom-field-", "").toInt
).foreach { field =>
CustomFieldBehavior.validate(field, value, messages) match {
case None =>
insertOrUpdateCustomFieldValue(field, repository.owner, repository.name, issue.issueId, value)
case Some(_) => halt(400)
}
}
}
}
redirect(s"/${issue.userName}/${issue.repositoryName}/issues/${issue.issueId}")
} else Unauthorized()
}
})
ajaxPost("/:owner/:repository/issues/edit_title/:id", issueTitleEditForm)(readableUsersOnly { (title, repository) =>
defining(repository.owner, repository.name) {
case (owner, name) =>
getIssue(owner, name, params("id")).map {
context.withLoginAccount {
loginAccount =>
getIssue(repository.owner, repository.name, params("id")).map {
issue =>
if (isEditableContent(owner, name, issue.openedUserName)) {
if (isEditableContent(repository.owner, repository.name, issue.openedUserName, loginAccount)) {
if (issue.title != title) {
// update issue
updateIssue(owner, name, issue.issueId, title, issue.content)
updateIssue(repository.owner, repository.name, issue.issueId, title, issue.content)
// extract references and create refer comment
createReferComment(owner, name, issue.copy(title = title), title, context.loginAccount.get)
createReferComment(repository.owner, repository.name, issue.copy(title = title), title, loginAccount)
createComment(
owner,
name,
context.loginAccount.get.userName,
repository.owner,
repository.name,
loginAccount.userName,
issue.issueId,
issue.title + "\r\n" + title,
"change_title"
)
}
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
redirect(s"/${repository.owner}/${repository.name}/issues/_data/${issue.issueId}")
} else Unauthorized()
} getOrElse NotFound()
}
})
ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (content, repository) =>
defining(repository.owner, repository.name) {
case (owner, name) =>
getIssue(owner, name, params("id")).map { issue =>
if (isEditableContent(owner, name, issue.openedUserName)) {
context.withLoginAccount {
loginAccount =>
getIssue(repository.owner, repository.name, params("id")).map { issue =>
if (isEditableContent(repository.owner, repository.name, issue.openedUserName, loginAccount)) {
// update issue
updateIssue(owner, name, issue.issueId, issue.title, content)
updateIssue(repository.owner, repository.name, issue.issueId, issue.title, content)
// extract references and create refer comment
createReferComment(owner, name, issue, content.getOrElse(""), context.loginAccount.get)
createReferComment(repository.owner, repository.name, issue, content.getOrElse(""), loginAccount)
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
redirect(s"/${repository.owner}/${repository.name}/issues/_data/${issue.issueId}")
} else Unauthorized()
} getOrElse NotFound()
}
})
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
val actionOpt =
params.get("action").filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName))
handleComment(issue, Some(form.content), repository, actionOpt) map {
case (issue, id) =>
redirect(
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}"
)
}
} getOrElse NotFound()
context.withLoginAccount {
loginAccount =>
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
val actionOpt =
params
.get("action")
.filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName, loginAccount))
handleComment(issue, Some(form.content), repository, actionOpt) map {
case (issue, id) =>
redirect(
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}"
)
}
} getOrElse NotFound()
}
})
post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
val actionOpt =
params.get("action").filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName))
handleComment(issue, form.content, repository, actionOpt) map {
case (issue, id) =>
redirect(
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}"
)
}
} getOrElse NotFound()
context.withLoginAccount {
loginAccount =>
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
val actionOpt =
params
.get("action")
.filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName, loginAccount))
handleComment(issue, form.content, repository, actionOpt) map {
case (issue, id) =>
redirect(
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}"
)
}
} getOrElse NotFound()
}
})
ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
defining(repository.owner, repository.name) {
case (owner, name) =>
getComment(owner, name, params("id")).map { comment =>
if (isEditableContent(owner, name, comment.commentedUserName)) {
updateComment(comment.issueId, comment.commentId, form.content)
redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}")
context.withLoginAccount {
loginAccount =>
getComment(repository.owner, repository.name, params("id")).map { comment =>
if (isEditableContent(repository.owner, repository.name, comment.commentedUserName, loginAccount)) {
updateComment(repository.owner, repository.name, comment.issueId, comment.commentId, form.content)
redirect(s"/${repository.owner}/${repository.name}/issue_comments/_data/${comment.commentId}")
} else Unauthorized()
} getOrElse NotFound()
}
})
ajaxPost("/:owner/:repository/issue_comments/delete/:id")(readableUsersOnly { repository =>
defining(repository.owner, repository.name) {
case (owner, name) =>
getComment(owner, name, params("id")).map { comment =>
if (isEditableContent(owner, name, comment.commentedUserName)) {
Ok(deleteComment(comment.issueId, comment.commentId))
} else Unauthorized()
} getOrElse NotFound()
context.withLoginAccount { loginAccount =>
getComment(repository.owner, repository.name, params("id")).map { comment =>
if (isDeletableComment(repository.owner, repository.name, comment.commentedUserName, loginAccount)) {
Ok(deleteComment(repository.owner, repository.name, comment.issueId, comment.commentId))
} else Unauthorized()
} getOrElse NotFound()
}
})
ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository =>
getIssue(repository.owner, repository.name, params("id")) map {
x =>
if (isEditableContent(x.userName, x.repositoryName, x.openedUserName)) {
params.get("dataType") collect {
case t if t == "html" => html.editissue(x.content, x.issueId, repository)
} getOrElse {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map(
"title" -> x.title,
"content" -> Markdown.toHtml(
markdown = x.content getOrElse "No description given.",
repository = repository,
branch = repository.repository.defaultBranch,
enableWikiLink = false,
enableRefsLink = true,
enableAnchor = true,
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = true
context.withLoginAccount {
loginAccount =>
getIssue(repository.owner, repository.name, params("id")) map {
x =>
if (isEditableContent(x.userName, x.repositoryName, x.openedUserName, loginAccount)) {
params.get("dataType") collect {
case t if t == "html" => html.editissue(x.content, x.issueId, repository)
} getOrElse {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map(
"title" -> x.title,
"content" -> Markdown.toHtml(
markdown = x.content getOrElse "No description given.",
repository = repository,
branch = repository.repository.defaultBranch,
enableWikiLink = false,
enableRefsLink = true,
enableAnchor = true,
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = true
)
)
)
)
)
}
} else Unauthorized()
} getOrElse NotFound()
}
} else Unauthorized()
} getOrElse NotFound()
}
})
ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository =>
getComment(repository.owner, repository.name, params("id")) map {
x =>
if (isEditableContent(x.userName, x.repositoryName, x.commentedUserName)) {
params.get("dataType") collect {
case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
} getOrElse {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map(
"content" -> view.Markdown.toHtml(
markdown = x.content,
repository = repository,
branch = repository.repository.defaultBranch,
enableWikiLink = false,
enableRefsLink = true,
enableAnchor = true,
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = true
context.withLoginAccount {
loginAccount =>
getComment(repository.owner, repository.name, params("id")) map {
x =>
if (isEditableContent(x.userName, x.repositoryName, x.commentedUserName, loginAccount)) {
params.get("dataType") collect {
case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
} getOrElse {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map(
"content" -> view.Markdown.toHtml(
markdown = x.content,
repository = repository,
branch = repository.repository.defaultBranch,
enableWikiLink = false,
enableRefsLink = true,
enableAnchor = true,
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = true
)
)
)
)
)
}
} else Unauthorized()
} getOrElse NotFound()
}
} else Unauthorized()
} getOrElse NotFound()
}
})
ajaxPost("/:owner/:repository/issues/new/label")(writableUsersOnly { repository =>
@@ -308,28 +346,27 @@ trait IssuesControllerBase extends ControllerBase {
})
ajaxPost("/:owner/:repository/issues/:id/label/new")(writableUsersOnly { repository =>
defining(params("id").toInt) { issueId =>
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, true)
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
}
val issueId = params("id").toInt
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, true)
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
})
ajaxPost("/:owner/:repository/issues/:id/label/delete")(writableUsersOnly { repository =>
defining(params("id").toInt) { issueId =>
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, true)
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
}
val issueId = params("id").toInt
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, true)
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
})
ajaxPost("/:owner/:repository/issues/:id/assign")(writableUsersOnly { repository =>
updateAssignedUserName(
repository.owner,
repository.name,
params("id").toInt,
assignedUserName("assignedUserName"),
true
)
Ok("updated")
ajaxPost("/:owner/:repository/issues/:id/assignee/new")(writableUsersOnly { repository =>
val issueId = params("id").toInt
registerIssueAssignee(repository.owner, repository.name, issueId, params("assigneeUserName"), true)
Ok()
})
ajaxPost("/:owner/:repository/issues/:id/assignee/delete")(writableUsersOnly { repository =>
val issueId = params("id").toInt
deleteIssueAssignee(repository.owner, repository.name, issueId, params("assigneeUserName"), true)
Ok()
})
ajaxPost("/:owner/:repository/issues/:id/milestone")(writableUsersOnly { repository =>
@@ -350,24 +387,57 @@ trait IssuesControllerBase extends ControllerBase {
Ok("updated")
})
post("/:owner/:repository/issues/batchedit/state")(writableUsersOnly { repository =>
defining(params.get("value")) {
action =>
action match {
case Some("open") =>
executeBatch(repository) { issueId =>
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
handleComment(issue, None, repository, Some("reopen"))
}
}
case Some("close") =>
executeBatch(repository) { issueId =>
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
handleComment(issue, None, repository, Some("close"))
}
}
case _ => BadRequest()
ajaxPost("/:owner/:repository/issues/customfield_validation/:fieldId")(writableUsersOnly { repository =>
val fieldId = params("fieldId").toInt
val value = params("value")
getCustomField(repository.owner, repository.name, fieldId)
.flatMap { field =>
CustomFieldBehavior.validate(field, value, messages).map { error =>
Ok(error)
}
}
.getOrElse(Ok())
})
ajaxPost("/:owner/:repository/issues/:id/customfield/:fieldId")(writableUsersOnly { repository =>
val issueId = params("id").toInt
val fieldId = params("fieldId").toInt
val value = params("value")
for {
_ <- getIssue(repository.owner, repository.name, issueId.toString)
field <- getCustomField(repository.owner, repository.name, fieldId)
} {
CustomFieldBehavior.validate(field, value, messages) match {
case None => insertOrUpdateCustomFieldValue(field, repository.owner, repository.name, issueId, value)
case Some(_) => halt(400)
}
}
Ok(value)
})
post("/:owner/:repository/issues/batchedit/state")(writableUsersOnly { repository =>
val action = params.get("value")
action match {
case Some("open") =>
executeBatch(repository) { issueId =>
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
handleComment(issue, None, repository, Some("reopen"))
}
}
if (params("uri").nonEmpty) {
redirect(params("uri"))
}
case Some("close") =>
executeBatch(repository) { issueId =>
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
handleComment(issue, None, repository, Some("close"))
}
}
if (params("uri").nonEmpty) {
redirect(params("uri"))
}
case _ => BadRequest()
}
})
@@ -376,32 +446,41 @@ trait IssuesControllerBase extends ControllerBase {
executeBatch(repository) { issueId =>
getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse {
registerIssueLabel(repository.owner, repository.name, issueId, labelId, true)
if (params("uri").nonEmpty) {
redirect(params("uri"))
}
}
}
} getOrElse NotFound()
})
post("/:owner/:repository/issues/batchedit/assign")(writableUsersOnly { repository =>
defining(assignedUserName("value")) { value =>
executeBatch(repository) {
updateAssignedUserName(repository.owner, repository.name, _, value, true)
val value = assignedUserName("value")
executeBatch(repository) {
//updateAssignedUserName(repository.owner, repository.name, _, value, true)
value match {
case Some(assignedUserName) =>
registerIssueAssignee(repository.owner, repository.name, _, assignedUserName, true)
case None =>
deleteAllIssueAssignees(repository.owner, repository.name, _, true)
}
}
if (params("uri").nonEmpty) {
redirect(params("uri"))
}
})
post("/:owner/:repository/issues/batchedit/milestone")(writableUsersOnly { repository =>
defining(milestoneId("value")) { value =>
executeBatch(repository) {
updateMilestoneId(repository.owner, repository.name, _, value, true)
}
val value = milestoneId("value")
executeBatch(repository) {
updateMilestoneId(repository.owner, repository.name, _, value, true)
}
})
post("/:owner/:repository/issues/batchedit/priority")(writableUsersOnly { repository =>
defining(priorityId("value")) { value =>
executeBatch(repository) {
updatePriorityId(repository.owner, repository.name, _, value, true)
}
val value = priorityId("value")
executeBatch(repository) {
updatePriorityId(repository.owner, repository.name, _, value, true)
}
})
@@ -416,6 +495,29 @@ trait IssuesControllerBase extends ControllerBase {
}) getOrElse NotFound()
})
/**
* JSON API for issue and PR completion.
*/
ajaxGet("/:owner/:repository/_issue/proposals")(writableUsersOnly { repository =>
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map(
"options" -> (
getOpenIssues(repository.owner, repository.name)
.map { t =>
Map(
"label" -> s"""${if (t.isPullRequest) "<i class='octicon octicon-git-pull-request'></i>"
else "<i class='octicon octicon-issue-opened'></i>"}<b> #${StringUtil
.escapeHtml(t.issueId.toString)} ${StringUtil
.escapeHtml(StringUtil.cutTail(t.title, 50, "..."))}</b>""",
"value" -> t.issueId.toString
)
}
)
)
)
})
val assignedUserName = (key: String) => params.get(key) filter (_.trim != "")
val milestoneId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
val priorityId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
@@ -425,41 +527,56 @@ trait IssuesControllerBase extends ControllerBase {
params("from") match {
case "issues" => redirect(s"/${repository.owner}/${repository.name}/issues")
case "pulls" => redirect(s"/${repository.owner}/${repository.name}/pulls")
case _ =>
}
}
private def searchIssues(repository: RepositoryService.RepositoryInfo) = {
defining(repository.owner, repository.name) {
case (owner, repoName) =>
val page = IssueSearchCondition.page(request)
val page = IssueSearchCondition.page(request)
// retrieve search condition
val condition = IssueSearchCondition(request)
// search issues
val issues =
searchIssue(
condition,
IssueSearchOption.Issues,
(page - 1) * IssueLimit,
IssueLimit,
repository.owner -> repository.name
)
// retrieve search condition
val condition = IssueSearchCondition(request)
html.list(
"issues",
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
page,
getAssignableUserNames(owner, repoName),
getMilestones(owner, repoName),
getPriorities(owner, repoName),
getLabels(owner, repoName),
countIssue(condition.copy(state = "open"), false, owner -> repoName),
countIssue(condition.copy(state = "closed"), false, owner -> repoName),
condition,
repository,
isIssueEditable(repository),
isIssueManageable(repository)
)
}
html.list(
"issues",
issues.map(issue => (issue, None)),
page,
getAssignableUserNames(repository.owner, repository.name),
getMilestones(repository.owner, repository.name),
getPriorities(repository.owner, repository.name),
getLabels(repository.owner, repository.name),
countIssue(condition.copy(state = "open"), IssueSearchOption.Issues, repository.owner -> repository.name),
countIssue(condition.copy(state = "closed"), IssueSearchOption.Issues, repository.owner -> repository.name),
condition,
repository,
isIssueEditable(repository),
isIssueManageable(repository)
)
}
/**
* Tests whether an issue or a comment is editable by a logged-in user.
*/
private def isEditableContent(owner: String, repository: String, author: String)(
private def isEditableContent(owner: String, repository: String, author: String, loginAccount: Account)(
implicit context: Context
): Boolean = {
hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
hasDeveloperRole(owner, repository, context.loginAccount) || author == loginAccount.userName
}
/**
* Tests whether an issue comment is deletable by a logged-in user.
*/
private def isDeletableComment(owner: String, repository: String, author: String, loginAccount: Account)(
implicit context: Context
): Boolean = {
hasOwnerRole(owner, repository, context.loginAccount) || author == loginAccount.userName
}
}

View File

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

View File

@@ -1,10 +1,18 @@
package gitbucket.core.controller
import gitbucket.core.issues.milestones.html
import gitbucket.core.service.{AccountService, MilestonesService, RepositoryService}
import gitbucket.core.service.IssuesService.{IssueLimit, IssueSearchCondition}
import gitbucket.core.service.{
AccountService,
CommitStatusService,
IssueSearchOption,
MilestonesService,
RepositoryService
}
import gitbucket.core.util.Implicits._
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.view.helpers.{getAssignableUserNames, getLabels, getPriorities, searchIssue}
import org.scalatra.forms._
import org.scalatra.i18n.Messages
@@ -13,11 +21,16 @@ class MilestonesController
with MilestonesService
with RepositoryService
with AccountService
with CommitStatusService
with ReferrerAuthenticator
with WritableUsersAuthenticator
trait MilestonesControllerBase extends ControllerBase {
self: MilestonesService with RepositoryService with ReferrerAuthenticator with WritableUsersAuthenticator =>
self: MilestonesService
with RepositoryService
with CommitStatusService
with ReferrerAuthenticator
with WritableUsersAuthenticator =>
case class MilestoneForm(title: String, description: Option[String], dueDate: Option[java.util.Date])
@@ -36,6 +49,41 @@ trait MilestonesControllerBase extends ControllerBase {
)
})
get("/:owner/:repository/milestone/:id")(referrersOnly { repository =>
val milestone = getMilestone(repository.owner, repository.name, params("id").toInt)
val page = IssueSearchCondition.page(request)
val condition = IssueSearchCondition(
request,
milestone.get.title
)
val issues = searchIssue(
condition,
IssueSearchOption.Both,
(page - 1) * IssueLimit,
IssueLimit,
repository.owner -> repository.name
)
val status = issues.map { issue =>
issue.commitId.flatMap { commitId =>
getCommitStatusWithSummary(issue.issue.userName, issue.issue.repositoryName, commitId)
}
}
html.milestone(
condition.state,
issues.zip(status),
page,
getAssignableUserNames(repository.owner, repository.name),
getPriorities(repository.owner, repository.name),
getLabels(repository.owner, repository.name),
condition,
getMilestonesWithIssueCount(repository.owner, repository.name)
.filter(p => p._1.milestoneId == milestone.get.milestoneId),
repository,
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
)
})
get("/:owner/:repository/issues/milestones/new")(writableUsersOnly {
html.edit(None, _)
})

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
package gitbucket.core.controller
import gitbucket.core.model.activity.DeleteBranchInfo
import gitbucket.core.pulls.html
import gitbucket.core.service.CommitStatusService
import gitbucket.core.service.MergeService
@@ -7,7 +8,6 @@ import gitbucket.core.service.IssuesService._
import gitbucket.core.service.PullRequestService._
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.service._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.Directory._
import gitbucket.core.util.Implicits._
import gitbucket.core.util._
@@ -25,6 +25,7 @@ class PullRequestsController
with PullRequestService
with MilestonesService
with LabelsService
with CustomFieldsService
with CommitsService
with ActivityService
with WebHookPullRequestService
@@ -36,6 +37,7 @@ class PullRequestsController
with MergeService
with ProtectedBranchService
with PrioritiesService
with RequestCache
trait PullRequestsControllerBase extends ControllerBase {
self: RepositoryService
@@ -43,6 +45,7 @@ trait PullRequestsControllerBase extends ControllerBase {
with IssuesService
with MilestonesService
with LabelsService
with CustomFieldsService
with CommitsService
with ActivityService
with PullRequestService
@@ -56,7 +59,7 @@ trait PullRequestsControllerBase extends ControllerBase {
with PrioritiesService =>
val pullRequestForm = mapping(
"title" -> trim(label("Title", text(required, maxlength(100)))),
"title" -> trim(label("Title", text(required))),
"content" -> trim(label("Content", optional(text()))),
"targetUserName" -> trim(text(required, maxlength(100))),
"targetBranch" -> trim(text(required, maxlength(100))),
@@ -66,7 +69,7 @@ trait PullRequestsControllerBase extends ControllerBase {
"commitIdFrom" -> trim(text(required, maxlength(40))),
"commitIdTo" -> trim(text(required, maxlength(40))),
"isDraft" -> trim(boolean(required)),
"assignedUserName" -> trim(optional(text())),
"assigneeUserNames" -> trim(optional(text())),
"milestoneId" -> trim(optional(number())),
"priorityId" -> trim(optional(number())),
"labelNames" -> trim(optional(text()))
@@ -89,7 +92,7 @@ trait PullRequestsControllerBase extends ControllerBase {
commitIdFrom: String,
commitIdTo: String,
isDraft: Boolean,
assignedUserName: Option[String],
assigneeUserNames: Option[String],
milestoneId: Option[Int],
priorityId: Option[Int],
labelNames: Option[String]
@@ -109,24 +112,31 @@ trait PullRequestsControllerBase extends ControllerBase {
get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
params("id").toIntOpt.flatMap {
issueId =>
val owner = repository.owner
val name = repository.name
getPullRequest(owner, name, issueId) map {
getPullRequest(repository.owner, repository.name, issueId) map {
case (issue, pullreq) =>
val (commits, diffs) =
getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo)
getRequestCompareInfo(
repository.owner,
repository.name,
pullreq.commitIdFrom,
repository.owner,
repository.name,
pullreq.commitIdTo
)
html.conversation(
issue,
pullreq,
commits.flatten,
getPullRequestComments(owner, name, issue.issueId, commits.flatten),
getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
diffs.size,
getIssueLabels(owner, name, issueId),
getAssignableUserNames(owner, name),
getMilestonesWithIssueCount(owner, name),
getPriorities(owner, name),
getLabels(owner, name),
getIssueLabels(repository.owner, repository.name, issueId),
getIssueAssignees(repository.owner, repository.name, issueId),
getAssignableUserNames(repository.owner, repository.name),
getMilestonesWithIssueCount(repository.owner, repository.name),
getPriorities(repository.owner, repository.name),
getLabels(repository.owner, repository.name),
getCustomFieldsWithValue(repository.owner, repository.name, issueId).filter(_._1.enableForPullRequests),
isEditable(repository),
isManageable(repository),
hasDeveloperRole(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount),
@@ -134,25 +144,6 @@ trait PullRequestsControllerBase extends ControllerBase {
getRepository(pullreq.requestUserName, pullreq.requestRepositoryName),
flash.iterator.map(f => f._1 -> f._2.toString).toMap
)
// html.pullreq(
// issue,
// pullreq,
// comments,
// getIssueLabels(owner, name, issueId),
// getAssignableUserNames(owner, name),
// getMilestonesWithIssueCount(owner, name),
// getPriorities(owner, name),
// getLabels(owner, name),
// commits,
// diffs,
// isEditable(repository),
// isManageable(repository),
// hasDeveloperRole(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount),
// repository,
// getRepository(pullreq.requestUserName, pullreq.requestRepositoryName),
// flash.toMap.map(f => f._1 -> f._2.toString)
// )
}
} getOrElse NotFound()
})
@@ -160,18 +151,29 @@ trait PullRequestsControllerBase extends ControllerBase {
get("/:owner/:repository/pull/:id/commits")(referrersOnly { repository =>
params("id").toIntOpt.flatMap {
issueId =>
val owner = repository.owner
val name = repository.name
getPullRequest(owner, name, issueId) map {
getPullRequest(repository.owner, repository.name, issueId) map {
case (issue, pullreq) =>
val (commits, diffs) =
getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo)
getRequestCompareInfo(
repository.owner,
repository.name,
pullreq.commitIdFrom,
repository.owner,
repository.name,
pullreq.commitIdTo
)
val commitsWithStatus = commits.map { day =>
day.map { commit =>
(commit, getCommitStatusWithSummary(repository.owner, repository.name, commit.id))
}
}
html.commits(
issue,
pullreq,
commits,
getPullRequestComments(owner, name, issue.issueId, commits.flatten),
commitsWithStatus,
getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
diffs.size,
isManageable(repository),
repository
@@ -183,19 +185,24 @@ trait PullRequestsControllerBase extends ControllerBase {
get("/:owner/:repository/pull/:id/files")(referrersOnly { repository =>
params("id").toIntOpt.flatMap {
issueId =>
val owner = repository.owner
val name = repository.name
getPullRequest(owner, name, issueId) map {
getPullRequest(repository.owner, repository.name, issueId) map {
case (issue, pullreq) =>
val (commits, diffs) =
getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo)
getRequestCompareInfo(
repository.owner,
repository.name,
pullreq.commitIdFrom,
repository.owner,
repository.name,
pullreq.commitIdTo
)
html.files(
issue,
pullreq,
diffs,
commits.flatten,
getPullRequestComments(owner, name, issue.issueId, commits.flatten),
getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
isManageable(repository),
repository
)
@@ -206,39 +213,35 @@ trait PullRequestsControllerBase extends ControllerBase {
ajaxGet("/:owner/:repository/pull/:id/mergeguide")(referrersOnly { repository =>
params("id").toIntOpt.flatMap {
issueId =>
val owner = repository.owner
val name = repository.name
getPullRequest(owner, name, issueId) map {
getPullRequest(repository.owner, repository.name, issueId) map {
case (issue, pullreq) =>
val conflictMessage = LockUtil.lock(s"${owner}/${name}") {
checkConflict(owner, name, pullreq.branch, issueId)
val conflictMessage = LockUtil.lock(s"${repository.owner}/${repository.name}") {
checkConflict(repository.owner, repository.name, pullreq.branch, issueId)
}
val hasMergePermission = hasDeveloperRole(owner, name, context.loginAccount)
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.branch)
val hasMergePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
val branchProtection = getProtectedBranchInfo(repository.owner, repository.name, pullreq.branch)
val mergeStatus = PullRequestService.MergeStatus(
conflictMessage = conflictMessage,
commitStatues = getCommitStatues(owner, name, pullreq.commitIdTo),
commitStatuses = getCommitStatuses(repository.owner, repository.name, pullreq.commitIdTo),
branchProtection = branchProtection,
branchIsOutOfDate = JGitUtil.getShaByRef(owner, name, pullreq.branch) != Some(pullreq.commitIdFrom),
needStatusCheck = context.loginAccount
.map { u =>
branchProtection.needStatusCheck(u.userName)
}
.getOrElse(true),
branchIsOutOfDate = JGitUtil.getShaByRef(repository.owner, repository.name, pullreq.branch) != Some(
pullreq.commitIdFrom
),
needStatusCheck = context.loginAccount.forall { u =>
branchProtection.needStatusCheck(u.userName)
},
hasUpdatePermission = hasDeveloperRole(
pullreq.requestUserName,
pullreq.requestRepositoryName,
context.loginAccount
) &&
context.loginAccount
.map { u =>
!getProtectedBranchInfo(
pullreq.requestUserName,
pullreq.requestRepositoryName,
pullreq.requestBranch
).needStatusCheck(u.userName)
}
.getOrElse(false),
context.loginAccount.exists { u =>
!getProtectedBranchInfo(
pullreq.requestUserName,
pullreq.requestRepositoryName,
pullreq.requestBranch
).needStatusCheck(u.userName)
},
hasMergePermission = hasMergePermission,
commitIdTo = pullreq.commitIdTo
)
@@ -271,7 +274,8 @@ trait PullRequestsControllerBase extends ControllerBase {
val userName = context.loginAccount.get.userName
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
git.branchDelete().setForce(true).setBranchNames(pullreq.requestBranch).call()
recordDeleteBranchActivity(repository.owner, repository.name, userName, pullreq.requestBranch)
val deleteBranchInfo = DeleteBranchInfo(repository.owner, repository.name, userName, pullreq.requestBranch)
recordActivity(deleteBranchInfo)
}
createComment(
baseRepository.owner,
@@ -354,29 +358,28 @@ trait PullRequestsControllerBase extends ControllerBase {
})
post("/:owner/:repository/pull/:id/merge", mergeForm)(writableUsersOnly { (form, repository) =>
params("id").toIntOpt.flatMap { issueId =>
val owner = repository.owner
val name = repository.name
mergePullRequest(
repository,
issueId,
context.loginAccount.get,
form.message,
form.strategy,
form.isDraft,
context.settings
) match {
case Right(objectId) => redirect(s"/${owner}/${name}/pull/${issueId}")
case Left(message) => Some(BadRequest(message))
}
} getOrElse NotFound()
context.withLoginAccount { loginAccount =>
params("id").toIntOpt.flatMap { issueId =>
mergePullRequest(
repository,
issueId,
loginAccount,
form.message,
form.strategy,
form.isDraft,
context.settings
) match {
case Right(objectId) => redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
case Left(message) => Some(BadRequest(message))
}
} getOrElse NotFound()
}
})
get("/:owner/:repository/compare")(referrersOnly { forkedRepository =>
val headBranch: Option[String] = params.get("head")
val headBranch = params.get("head")
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
case (Some(originUserName), Some(originRepositoryName)) => {
case (Some(originUserName), Some(originRepositoryName)) =>
getRepository(originUserName, originRepositoryName).map {
originRepository =>
Using.resources(
@@ -393,8 +396,7 @@ trait PullRequestsControllerBase extends ControllerBase {
)
}
} getOrElse NotFound()
}
case _ => {
case _ =>
Using.resource(Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))) { git =>
JGitUtil.getDefaultBranch(git, forkedRepository).map {
case (_, defaultBranch) =>
@@ -405,41 +407,48 @@ trait PullRequestsControllerBase extends ControllerBase {
redirect(s"/${forkedRepository.owner}/${forkedRepository.name}")
}
}
}
}
})
private def getOriginRepositoryName(
originOwner: String,
forkedOwner: String,
forkedRepository: RepositoryInfo
): Option[String] = {
if (originOwner == forkedOwner) {
// Self repository
Some(forkedRepository.name)
} else if (forkedRepository.repository.originUserName.isEmpty) {
// when ForkedRepository is the original repository
getForkedRepositories(forkedRepository.owner, forkedRepository.name)
.find(_.userName == originOwner)
.map(_.repositoryName)
} else if (Some(originOwner) == forkedRepository.repository.originUserName) {
// Original repository
forkedRepository.repository.originRepositoryName
} else {
// Sibling repository
getUserRepositories(originOwner)
.find { x =>
x.repository.originUserName == forkedRepository.repository.originUserName &&
x.repository.originRepositoryName == forkedRepository.repository.originRepositoryName
}
.map(_.repository.repositoryName)
}
}
get("/:owner/:repository/compare/*...*")(referrersOnly { forkedRepository =>
val Seq(origin, forked) = multiParams("splat")
val (originOwner, originId) = parseCompareIdentifier(origin, forkedRepository.owner)
val (forkedOwner, forkedId) = parseCompareIdentifier(forked, forkedRepository.owner)
(for (originRepositoryName <- if (originOwner == forkedOwner) {
// Self repository
Some(forkedRepository.name)
} else if (forkedRepository.repository.originUserName.isEmpty) {
// when ForkedRepository is the original repository
getForkedRepositories(forkedRepository.owner, forkedRepository.name)
.find(_.userName == originOwner)
.map(_.repositoryName)
} else if (Some(originOwner) == forkedRepository.repository.originUserName) {
// Original repository
forkedRepository.repository.originRepositoryName
} else {
// Sibling repository
getUserRepositories(originOwner)
.find { x =>
x.repository.originUserName == forkedRepository.repository.originUserName &&
x.repository.originRepositoryName == forkedRepository.repository.originRepositoryName
}
.map(_.repository.repositoryName)
};
(for (originRepositoryName <- getOriginRepositoryName(originOwner, forkedOwner, forkedRepository);
originRepository <- getRepository(originOwner, originRepositoryName)) yield {
val (oldId, newId) =
getPullRequestCommitFromTo(originRepository, forkedRepository, originId, forkedId)
(oldId, newId) match {
case (Some(oldId), Some(newId)) => {
case (Some(oldId), Some(newId)) =>
val (commits, diffs) = getRequestCompareInfo(
originRepository.owner,
originRepository.name,
@@ -472,8 +481,7 @@ trait PullRequestsControllerBase extends ControllerBase {
(repository.userName, repository.repositoryName, repository.defaultBranch)
},
commits.flatten
.map(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false))
.flatten
.flatMap(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false))
.toList,
originId,
forkedId,
@@ -488,9 +496,9 @@ trait PullRequestsControllerBase extends ControllerBase {
getMilestones(originRepository.owner, originRepository.name),
getPriorities(originRepository.owner, originRepository.name),
getDefaultPriority(originRepository.owner, originRepository.name),
getLabels(originRepository.owner, originRepository.name)
getLabels(originRepository.owner, originRepository.name),
getCustomFields(originRepository.owner, originRepository.name).filter(_.enableForPullRequests)
)
}
case (oldId, newId) =>
redirect(
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/" +
@@ -502,6 +510,54 @@ trait PullRequestsControllerBase extends ControllerBase {
}) getOrElse NotFound()
})
ajaxGet("/:owner/:repository/diff/:id")(referrersOnly { repository =>
(for {
commitId <- params.get("id")
path <- params.get("path")
diff <- getSingleDiff(repository.owner, repository.name, commitId, path)
} yield {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map(
"oldContent" -> diff.oldContent,
"newContent" -> diff.newContent
)
)
}) getOrElse NotFound()
})
ajaxGet("/:owner/:repository/diff/*...*")(referrersOnly { forkedRepository =>
val Seq(origin, forked) = multiParams("splat")
val (originOwner, originId) = parseCompareIdentifier(origin, forkedRepository.owner)
val (forkedOwner, forkedId) = parseCompareIdentifier(forked, forkedRepository.owner)
(for {
path <- params.get("path")
originRepositoryName <- getOriginRepositoryName(originOwner, forkedOwner, forkedRepository)
originRepository <- getRepository(originOwner, originRepositoryName)
(oldId, newId) = getPullRequestCommitFromTo(originRepository, forkedRepository, originId, forkedId)
oldId <- oldId
newId <- newId
diff <- getSingleDiff(
originRepository.owner,
originRepository.name,
oldId.getName,
forkedRepository.owner,
forkedRepository.name,
newId.getName,
path
)
} yield {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map(
"oldContent" -> diff.oldContent,
"newContent" -> diff.newContent
)
)
}) getOrElse NotFound()
})
ajaxGet("/:owner/:repository/compare/*...*/mergecheck")(readableUsersOnly { forkedRepository =>
val Seq(origin, forked) = multiParams("splat")
val (originOwner, tmpOriginBranch) = parseCompareIdentifier(origin, forkedRepository.owner)
@@ -540,18 +596,16 @@ trait PullRequestsControllerBase extends ControllerBase {
})
post("/:owner/:repository/pulls/new", pullRequestForm)(readableUsersOnly { (form, repository) =>
defining(repository.owner, repository.name) {
case (owner, name) =>
context.withLoginAccount {
loginAccount =>
val manageable = isManageable(repository)
val loginUserName = context.loginAccount.get.userName
val issueId = insertIssue(
owner = repository.owner,
repository = repository.name,
loginUser = loginUserName,
loginUser = loginAccount.userName,
title = form.title,
content = form.content,
assignedUserName = if (manageable) form.assignedUserName else None,
milestoneId = if (manageable) form.milestoneId else None,
priorityId = if (manageable) form.priorityId else None,
isPullRequest = true
@@ -567,14 +621,20 @@ trait PullRequestsControllerBase extends ControllerBase {
commitIdFrom = form.commitIdFrom,
commitIdTo = form.commitIdTo,
isDraft = form.isDraft,
loginAccount = context.loginAccount.get,
loginAccount = loginAccount,
settings = context.settings
)
// insert labels
if (manageable) {
// insert assignees
form.assigneeUserNames.foreach { value =>
value.split(",").foreach { userName =>
registerIssueAssignee(repository.owner, repository.name, issueId, userName)
}
}
// insert labels
form.labelNames.foreach { value =>
val labels = getLabels(owner, name)
val labels = getLabels(repository.owner, repository.name)
value.split(",").foreach { labelName =>
labels.find(_.labelName == labelName).map { label =>
registerIssueLabel(repository.owner, repository.name, issueId, label.labelId)
@@ -583,7 +643,7 @@ trait PullRequestsControllerBase extends ControllerBase {
}
}
redirect(s"/${owner}/${name}/pull/${issueId}")
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
}
})
@@ -630,31 +690,42 @@ trait PullRequestsControllerBase extends ControllerBase {
html.proposals(proposedBranches, targetRepository, repository)
})
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) =
defining(repository.owner, repository.name) {
case (owner, repoName) =>
val page = IssueSearchCondition.page(request)
// retrieve search condition
val condition = IssueSearchCondition(request)
gitbucket.core.issues.html.list(
"pulls",
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
page,
getAssignableUserNames(owner, repoName),
getMilestones(owner, repoName),
getPriorities(owner, repoName),
getLabels(owner, repoName),
countIssue(condition.copy(state = "open"), true, owner -> repoName),
countIssue(condition.copy(state = "closed"), true, owner -> repoName),
condition,
repository,
isEditable(repository),
isManageable(repository)
)
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) = {
val page = IssueSearchCondition.page(request)
// retrieve search condition
val condition = IssueSearchCondition(request)
// search issues
val issues = searchIssue(
condition,
IssueSearchOption.PullRequests,
(page - 1) * PullRequestLimit,
PullRequestLimit,
repository.owner -> repository.name
)
// commit status
val status = issues.map { issue =>
issue.commitId.flatMap { commitId =>
getCommitStatusWithSummary(repository.owner, repository.name, commitId)
}
}
gitbucket.core.issues.html.list(
"pulls",
issues.zip(status),
page,
getAssignableUserNames(repository.owner, repository.name),
getMilestones(repository.owner, repository.name),
getPriorities(repository.owner, repository.name),
getLabels(repository.owner, repository.name),
countIssue(condition.copy(state = "open"), IssueSearchOption.PullRequests, repository.owner -> repository.name),
countIssue(condition.copy(state = "closed"), IssueSearchOption.PullRequests, repository.owner -> repository.name),
condition,
repository,
isEditable(repository),
isManageable(repository)
)
}
/**
* Tests whether an logged-in user can manage pull requests.
*/

View File

@@ -2,7 +2,15 @@ package gitbucket.core.controller
import java.io.File
import gitbucket.core.service.{AccountService, ActivityService, PaginationHelper, ReleaseService, RepositoryService}
import gitbucket.core.model.activity.ReleaseInfo
import gitbucket.core.service.{
AccountService,
ActivityService,
PaginationHelper,
ReleaseService,
RepositoryService,
RequestCache
}
import gitbucket.core.util._
import gitbucket.core.util.Directory._
import gitbucket.core.util.Implicits._
@@ -22,6 +30,7 @@ class ReleaseController
with ReadableUsersAuthenticator
with ReferrerAuthenticator
with WritableUsersAuthenticator
with RequestCache
trait ReleaseControllerBase extends ControllerBase {
self: RepositoryService
@@ -54,8 +63,8 @@ trait ReleaseControllerBase extends ControllerBase {
)
})
get("/:owner/:repository/releases/:tag")(referrersOnly { repository =>
val tagName = params("tag")
get("/:owner/:repository/releases/*")(referrersOnly { repository =>
val tagName = multiParams("splat").head
getRelease(repository.owner, repository.name, tagName)
.map { release =>
html.release(
@@ -68,8 +77,8 @@ trait ReleaseControllerBase extends ControllerBase {
.getOrElse(NotFound())
})
get("/:owner/:repository/releases/:tag/assets/:fileId")(referrersOnly { repository =>
val tagName = params("tag")
get("/:owner/:repository/releases/*/assets/:fileId")(referrersOnly { repository =>
val tagName = multiParams("splat").head
val fileId = params("fileId")
(for {
_ <- repository.tags.find(_.name == tagName)
@@ -84,8 +93,8 @@ trait ReleaseControllerBase extends ControllerBase {
}).getOrElse(NotFound())
})
get("/:owner/:repository/releases/:tag/create")(writableUsersOnly { repository =>
val tagName = params("tag")
get("/:owner/:repository/releases/*/create")(writableUsersOnly { repository =>
val tagName = multiParams("splat").head
val previousTags = repository.tags.takeWhile(_.name != tagName).reverse
repository.tags
@@ -96,37 +105,40 @@ trait ReleaseControllerBase extends ControllerBase {
.getOrElse(NotFound())
})
post("/:owner/:repository/releases/:tag/create", releaseForm)(writableUsersOnly { (form, repository) =>
val tagName = params("tag")
val loginAccount = context.loginAccount.get
post("/:owner/:repository/releases/*/create", releaseForm)(writableUsersOnly { (form, repository) =>
context.withLoginAccount {
loginAccount =>
val tagName = multiParams("splat").head
// Insert into RELEASE
createRelease(repository.owner, repository.name, form.name, form.content, tagName, loginAccount)
// Insert into RELEASE
createRelease(repository.owner, repository.name, form.name, form.content, tagName, loginAccount)
// Insert into RELEASE_ASSET
val files = params.toMap.collect {
case (name, value) if name.startsWith("file:") =>
val Array(_, fileId) = name.split(":")
(fileId, value)
// Insert into RELEASE_ASSET
val files = params.toMap.collect {
case (name, value) if name.startsWith("file:") =>
val Array(_, fileId) = name.split(":")
(fileId, value)
}
files.foreach {
case (fileId, fileName) =>
val size =
new File(
getReleaseFilesDir(repository.owner, repository.name),
FileUtil.checkFilename(tagName + "/" + fileId)
).length
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
}
val releaseInfo = ReleaseInfo(repository.owner, repository.name, loginAccount.userName, form.name, tagName)
recordActivity(releaseInfo)
redirect(s"/${repository.owner}/${repository.name}/releases/${tagName}")
}
files.foreach {
case (fileId, fileName) =>
val size =
new File(
getReleaseFilesDir(repository.owner, repository.name),
FileUtil.checkFilename(tagName + "/" + fileId)
).length
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
}
recordReleaseActivity(repository.owner, repository.name, loginAccount.userName, form.name, tagName)
redirect(s"/${repository.owner}/${repository.name}/releases/${tagName}")
})
get("/:owner/:repository/changelog/*...*")(writableUsersOnly { repository =>
val Seq(previousTag, currentTag) = multiParams("splat")
val previousTagId = repository.tags.collectFirst { case x if x.name == previousTag => x.id }.getOrElse("")
val previousTagId = repository.tags.collectFirst { case x if x.name == previousTag => x.commitId }.getOrElse("")
val commitLog = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
val commits = JGitUtil.getCommitLog(git, previousTagId, currentTag).reverse
@@ -140,8 +152,8 @@ trait ReleaseControllerBase extends ControllerBase {
commitLog
})
get("/:owner/:repository/releases/:tag/edit")(writableUsersOnly { repository =>
val tagName = params("tag")
get("/:owner/:repository/releases/*/edit")(writableUsersOnly { repository =>
val tagName = multiParams("splat").head
val previousTags = repository.tags.takeWhile(_.name != tagName).reverse
(for {
@@ -158,52 +170,54 @@ trait ReleaseControllerBase extends ControllerBase {
}).getOrElse(NotFound())
})
post("/:owner/:repository/releases/:tag/edit", releaseForm)(writableUsersOnly {
(form, repository) =>
val tagName = params("tag")
val loginAccount = context.loginAccount.get
post("/:owner/:repository/releases/*/edit", releaseForm)(writableUsersOnly { (form, repository) =>
context.withLoginAccount {
loginAccount =>
val tagName = multiParams("splat").head
getRelease(repository.owner, repository.name, tagName)
.map { release =>
// Update RELEASE
updateRelease(repository.owner, repository.name, tagName, form.name, form.content)
getRelease(repository.owner, repository.name, tagName)
.map {
release =>
// Update RELEASE
updateRelease(repository.owner, repository.name, tagName, form.name, form.content)
// Delete and Insert RELEASE_ASSET
val assets = getReleaseAssets(repository.owner, repository.name, tagName)
deleteReleaseAssets(repository.owner, repository.name, tagName)
// Delete and Insert RELEASE_ASSET
val assets = getReleaseAssets(repository.owner, repository.name, tagName)
deleteReleaseAssets(repository.owner, repository.name, tagName)
val files = params.toMap.collect {
case (name, value) if name.startsWith("file:") =>
val Array(_, fileId) = name.split(":")
(fileId, value)
val files = params.toMap.collect {
case (name, value) if name.startsWith("file:") =>
val Array(_, fileId) = name.split(":")
(fileId, value)
}
files.foreach {
case (fileId, fileName) =>
val size =
new File(
getReleaseFilesDir(repository.owner, repository.name),
FileUtil.checkFilename(tagName + "/" + fileId)
).length
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
}
assets.foreach { asset =>
if (!files.exists { case (fileId, _) => fileId == asset.fileName }) {
val file = new File(
getReleaseFilesDir(repository.owner, repository.name),
FileUtil.checkFilename(release.tag + "/" + asset.fileName)
)
FileUtils.forceDelete(file)
}
}
redirect(s"/${release.userName}/${release.repositoryName}/releases/${tagName}")
}
files.foreach {
case (fileId, fileName) =>
val size =
new File(
getReleaseFilesDir(repository.owner, repository.name),
FileUtil.checkFilename(tagName + "/" + fileId)
).length
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
}
assets.foreach { asset =>
if (!files.exists { case (fileId, _) => fileId == asset.fileName }) {
val file = new File(
getReleaseFilesDir(repository.owner, repository.name),
FileUtil.checkFilename(release.tag + "/" + asset.fileName)
)
FileUtils.forceDelete(file)
}
}
redirect(s"/${release.userName}/${release.repositoryName}/releases/${tagName}")
}
.getOrElse(NotFound())
.getOrElse(NotFound())
}
})
post("/:owner/:repository/releases/:tag/delete")(writableUsersOnly { repository =>
val tagName = params("tag")
post("/:owner/:repository/releases/*/delete")(writableUsersOnly { repository =>
val tagName = multiParams("splat").head
getRelease(repository.owner, repository.name, tagName).foreach { release =>
FileUtils.deleteDirectory(
new File(getReleaseFilesDir(repository.owner, repository.name), FileUtil.checkFilename(release.tag))
@@ -214,7 +228,6 @@ trait ReleaseControllerBase extends ControllerBase {
})
private def fetchReleases(repository: RepositoryService.RepositoryInfo, page: Int) = {
import gitbucket.core.service.ReleaseService._
val (offset, limit) = ((page - 1) * ReleaseLimit, ReleaseLimit)

View File

@@ -1,8 +1,7 @@
package gitbucket.core.controller
import java.time.{LocalDateTime, ZoneId, ZoneOffset}
import java.time.{LocalDateTime, ZoneOffset}
import java.util.Date
import gitbucket.core.settings.html
import gitbucket.core.model.{RepositoryWebHook, WebHook}
import gitbucket.core.service._
@@ -13,6 +12,7 @@ import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.Directory._
import gitbucket.core.model.WebHookContentType
import gitbucket.core.model.activity.RenameRepositoryInfo
import org.scalatra.forms._
import org.scalatra.i18n.Messages
import org.eclipse.jgit.api.Git
@@ -20,7 +20,7 @@ import org.eclipse.jgit.lib.Constants
import org.eclipse.jgit.lib.ObjectId
import scala.util.Using
import org.scalatra.Forbidden
import org.scalatra.{Forbidden, Ok}
class RepositorySettingsController
extends RepositorySettingsControllerBase
@@ -30,8 +30,11 @@ class RepositorySettingsController
with ProtectedBranchService
with CommitStatusService
with DeployKeyService
with CustomFieldsService
with ActivityService
with OwnerAuthenticator
with UsersAuthenticator
with RequestCache
trait RepositorySettingsControllerBase extends ControllerBase {
self: RepositoryService
@@ -40,6 +43,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
with ProtectedBranchService
with CommitStatusService
with DeployKeyService
with CustomFieldsService
with ActivityService
with OwnerAuthenticator
with UsersAuthenticator =>
@@ -53,7 +58,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
externalWikiUrl: Option[String],
allowFork: Boolean,
mergeOptions: Seq[String],
defaultMergeOption: String
defaultMergeOption: String,
safeMode: Boolean
)
val optionsForm = mapping(
@@ -65,7 +71,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
"externalWikiUrl" -> trim(label("External Wiki URL", optional(text(maxlength(200))))),
"allowFork" -> trim(label("Allow Forking", boolean())),
"mergeOptions" -> mergeOptions,
"defaultMergeOption" -> trim(label("Default merge strategy", text(required)))
"defaultMergeOption" -> trim(label("Default merge strategy", text(required))),
"safeMode" -> trim(label("XSS protection", boolean()))
)(OptionsForm.apply).verifying { form =>
if (!form.mergeOptions.contains(form.defaultMergeOption)) {
Seq("defaultMergeOption" -> s"This merge strategy isn't enabled.")
@@ -97,9 +104,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
"events" -> webhookEvents,
"ctype" -> label("ctype", text()),
"token" -> optional(trim(label("token", text(maxlength(100)))))
)(
(url, events, ctype, token) => WebHookForm(url, events, WebHookContentType.valueOf(ctype), token)
)
)((url, events, ctype, token) => WebHookForm(url, events, WebHookContentType.valueOf(ctype), token))
// for rename repository
case class RenameRepositoryForm(repositoryName: String)
@@ -117,6 +122,21 @@ trait RepositorySettingsControllerBase extends ControllerBase {
"newOwner" -> trim(label("New owner", text(required, transferUser)))
)(TransferOwnerShipForm.apply)
// for custom field
case class CustomFieldForm(
fieldName: String,
fieldType: String,
enableForIssues: Boolean,
enableForPullRequests: Boolean
)
val customFieldForm = mapping(
"fieldName" -> trim(label("Field name", text(required, maxlength(100)))),
"fieldType" -> trim(label("Field type", text(required))),
"enableForIssues" -> trim(label("Enable for issues", boolean(required))),
"enableForPullRequests" -> trim(label("Enable for pull requests", boolean(required))),
)(CustomFieldForm.apply)
/**
* Redirect to the Options page.
*/
@@ -148,7 +168,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
form.externalWikiUrl,
form.allowFork,
form.mergeOptions,
form.defaultMergeOption
form.defaultMergeOption,
form.safeMode
)
flash.update("info", "Repository settings has been updated.")
redirect(s"/${repository.owner}/${repository.name}/settings/options")
@@ -176,14 +197,15 @@ trait RepositorySettingsControllerBase extends ControllerBase {
})
/** Branch protection for branch */
get("/:owner/:repository/settings/branches/:branch")(ownerOnly { repository =>
get("/:owner/:repository/settings/branches/*")(ownerOnly { repository =>
import gitbucket.core.api._
val branch = params("branch")
val branch = params("splat")
if (!repository.branchList.contains(branch)) {
redirect(s"/${repository.owner}/${repository.name}/settings/branches")
} else {
val protection = ApiBranchProtection(getProtectedBranchInfo(repository.owner, repository.name, branch))
val lastWeeks = getRecentStatuesContexts(
val lastWeeks = getRecentStatusContexts(
repository.owner,
repository.name,
Date.from(LocalDateTime.now.minusWeeks(1).toInstant(ZoneOffset.UTC))
@@ -225,7 +247,13 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Display the web hook edit page.
*/
get("/:owner/:repository/settings/hooks/new")(ownerOnly { repository =>
val webhook = RepositoryWebHook(repository.owner, repository.name, "", WebHookContentType.FORM, None)
val webhook = RepositoryWebHook(
userName = repository.owner,
repositoryName = repository.name,
url = "",
ctype = WebHookContentType.FORM,
token = None
)
html.edithook(webhook, Set(WebHook.Push), repository, true)
})
@@ -251,9 +279,10 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Send the test request to registered web hook URLs.
*/
ajaxPost("/:owner/:repository/settings/hooks/test")(ownerOnly { repository =>
def _headers(h: Array[org.apache.http.Header]): Array[Array[String]] = h.map { h =>
Array(h.getName, h.getValue)
}
def _headers(h: Array[org.apache.http.Header]): Array[Array[String]] =
h.map { h =>
Array(h.getName, h.getValue)
}
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
git =>
@@ -267,7 +296,13 @@ trait RepositorySettingsControllerBase extends ControllerBase {
val url = params("url")
val token = Some(params("token"))
val ctype = WebHookContentType.valueOf(params("ctype"))
val dummyWebHookInfo = RepositoryWebHook(repository.owner, repository.name, url, ctype, token)
val dummyWebHookInfo = RepositoryWebHook(
userName = repository.owner,
repositoryName = repository.name,
url = url,
ctype = ctype,
token = token
)
val dummyPayload = {
val ownerAccount = getAccountByUserName(repository.owner).get
val commits =
@@ -326,7 +361,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
.map(
res =>
Map(
"status" -> res.getStatusLine(),
"status" -> res.getStatusLine.getStatusCode,
"body" -> EntityUtils.toString(res.getEntity()),
"headers" -> _headers(res.getAllHeaders())
)
@@ -369,36 +404,62 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Rename repository.
*/
post("/:owner/:repository/settings/rename", renameForm)(ownerOnly { (form, repository) =>
if (context.settings.repositoryOperation.rename || context.loginAccount.get.isAdmin) {
if (repository.name != form.repositoryName) {
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
}
redirect(s"/${repository.owner}/${form.repositoryName}")
} else Forbidden()
context.withLoginAccount {
loginAccount =>
if (context.settings.basicBehavior.repositoryOperation.rename || loginAccount.isAdmin) {
if (repository.name != form.repositoryName) {
// Update database and move git repository
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
// Record activity log
val renameInfo = RenameRepositoryInfo(
repository.owner,
form.repositoryName,
loginAccount.userName,
repository.name
)
recordActivity(renameInfo)
}
redirect(s"/${repository.owner}/${form.repositoryName}")
} else Forbidden()
}
})
/**
* Transfer repository ownership.
*/
post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) =>
if (context.settings.repositoryOperation.transfer || context.loginAccount.get.isAdmin) {
// Change repository owner
if (repository.owner != form.newOwner) {
renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
}
redirect(s"/${form.newOwner}/${repository.name}")
} else Forbidden()
context.withLoginAccount {
loginAccount =>
if (context.settings.basicBehavior.repositoryOperation.transfer || loginAccount.isAdmin) {
// Change repository owner
if (repository.owner != form.newOwner) {
// Update database and move git repository
renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
// Record activity log
val renameInfo = RenameRepositoryInfo(
form.newOwner,
repository.name,
loginAccount.userName,
repository.owner
)
recordActivity(renameInfo)
}
redirect(s"/${form.newOwner}/${repository.name}")
} else Forbidden()
}
})
/**
* Delete the repository.
*/
post("/:owner/:repository/settings/delete")(ownerOnly { repository =>
if (context.settings.repositoryOperation.delete || context.loginAccount.get.isAdmin) {
// Delete the repository and related files
deleteRepository(repository.repository)
redirect(s"/${repository.owner}")
} else Forbidden()
context.withLoginAccount { loginAccount =>
if (context.settings.basicBehavior.repositoryOperation.delete || loginAccount.isAdmin) {
// Delete the repository and related files
deleteRepository(repository.repository)
redirect(s"/${repository.owner}")
} else Forbidden()
}
})
/**
@@ -432,35 +493,89 @@ trait RepositorySettingsControllerBase extends ControllerBase {
redirect(s"/${repository.owner}/${repository.name}/settings/deploykey")
})
/** Custom fields for issues and pull requests */
get("/:owner/:repository/settings/issues")(ownerOnly { repository =>
val customFields = getCustomFields(repository.owner, repository.name)
html.issues(customFields, repository)
})
/** New custom field form */
get("/:owner/:repository/settings/issues/fields/new")(ownerOnly { repository =>
html.issuesfieldform(None, repository)
})
/** Add custom field */
ajaxPost("/:owner/:repository/settings/issues/fields/new", customFieldForm)(ownerOnly { (form, repository) =>
val fieldId = createCustomField(
repository.owner,
repository.name,
form.fieldName,
form.fieldType,
form.enableForIssues,
form.enableForPullRequests
)
html.issuesfield(getCustomField(repository.owner, repository.name, fieldId).get)
})
/** Edit custom field form */
ajaxGet("/:owner/:repository/settings/issues/fields/:fieldId/edit")(ownerOnly { repository =>
getCustomField(repository.owner, repository.name, params("fieldId").toInt).map { customField =>
html.issuesfieldform(Some(customField), repository)
} getOrElse NotFound()
})
/** Update custom field */
ajaxPost("/:owner/:repository/settings/issues/fields/:fieldId/edit", customFieldForm)(ownerOnly {
(form, repository) =>
updateCustomField(
repository.owner,
repository.name,
params("fieldId").toInt,
form.fieldName,
form.fieldType,
form.enableForIssues,
form.enableForPullRequests
)
html.issuesfield(getCustomField(repository.owner, repository.name, params("fieldId").toInt).get)
})
/** Delete custom field */
ajaxPost("/:owner/:repository/settings/issues/fields/:fieldId/delete")(ownerOnly { repository =>
deleteCustomField(repository.owner, repository.name, params("fieldId").toInt)
Ok()
})
/**
* Provides duplication check for web hook url.
*/
private def webHook(needExists: Boolean): Constraint = new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] =
if (getWebHook(params("owner"), params("repository"), value).isDefined != needExists) {
Some(if (needExists) {
"URL had not been registered yet."
private def webHook(needExists: Boolean): Constraint =
new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] =
if (getWebHook(params("owner"), params("repository"), value).isDefined != needExists) {
Some(if (needExists) {
"URL had not been registered yet."
} else {
"URL had been registered already."
})
} else {
"URL had been registered already."
})
} else {
None
}
}
private def webhookEvents = new ValueType[Set[WebHook.Event]] {
def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Set[WebHook.Event] = {
WebHook.Event.values.flatMap { t =>
params.get(name + "." + t.name).map(_ => t)
}.toSet
None
}
}
def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] =
if (convert(name, params, messages).isEmpty) {
Seq(name -> messages("error.required").format(name))
} else {
Nil
private def webhookEvents =
new ValueType[Set[WebHook.Event]] {
def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Set[WebHook.Event] = {
WebHook.Event.values.flatMap { t =>
params.get(name + "." + t.name).map(_ => t)
}.toSet
}
}
def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] =
if (convert(name, params, messages).isEmpty) {
Seq(name -> messages("error.required").format(name))
} else {
Nil
}
}
// /**
// * Provides Constraint to validate the collaborator name.
@@ -480,70 +595,77 @@ trait RepositorySettingsControllerBase extends ControllerBase {
/**
* Duplicate check for the rename repository name.
*/
private def renameRepositoryName: Constraint = new Constraint() {
override def validate(
name: String,
value: String,
params: Map[String, Seq[String]],
messages: Messages
): Option[String] = {
for {
repoName <- params.optionValue("repository") if repoName != value
userName <- params.optionValue("owner")
_ <- getRepositoryNamesOfUser(userName).find(_ == value)
} yield {
"Repository already exists."
private def renameRepositoryName: Constraint =
new Constraint() {
override def validate(
name: String,
value: String,
params: Map[String, Seq[String]],
messages: Messages
): Option[String] = {
for {
repoName <- params.optionValue("repository") if repoName != value
userName <- params.optionValue("owner")
_ <- getRepositoryNamesOfUser(userName).find(_ == value)
} yield {
"Repository already exists."
}
}
}
}
/**
*
*/
private def featureOption: Constraint = new Constraint() {
override def validate(
name: String,
value: String,
params: Map[String, Seq[String]],
messages: Messages
): Option[String] =
if (Seq("DISABLE", "PRIVATE", "PUBLIC", "ALL").contains(value)) None else Some("Option is invalid.")
}
private def featureOption: Constraint =
new Constraint() {
override def validate(
name: String,
value: String,
params: Map[String, Seq[String]],
messages: Messages
): Option[String] =
if (Seq("DISABLE", "PRIVATE", "PUBLIC", "ALL").contains(value)) None else Some("Option is invalid.")
}
/**
* Provides Constraint to validate the repository transfer user.
*/
private def transferUser: Constraint = new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] =
getAccountByUserName(value) match {
case None => Some("User does not exist.")
case Some(x) =>
if (x.userName == params("owner")) {
Some("This is current repository owner.")
} else {
params.get("repository").flatMap { repositoryName =>
getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map { _ =>
"User already has same repository."
private def transferUser: Constraint =
new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] =
getAccountByUserName(value) match {
case None => Some("User does not exist.")
case Some(x) =>
if (x.userName == params("owner")) {
Some("This is current repository owner.")
} else {
params.get("repository").flatMap { repositoryName =>
getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map { _ =>
"User already has same repository."
}
}
}
}
}
}
}
}
private def mergeOptions = new ValueType[Seq[String]] {
override def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[String] = {
params.getOrElse("mergeOptions", Nil)
}
override def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] = {
val mergeOptions = params.getOrElse("mergeOptions", Nil)
if (mergeOptions.isEmpty) {
Seq("mergeOptions" -> "At least one option must be enabled.")
} else if (!mergeOptions.forall(x => Seq("merge-commit", "squash", "rebase").contains(x))) {
Seq("mergeOptions" -> "mergeOptions are invalid.")
} else {
Nil
private def mergeOptions =
new ValueType[Seq[String]] {
override def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[String] = {
params.getOrElse("mergeOptions", Nil)
}
override def validate(
name: String,
params: Map[String, Seq[String]],
messages: Messages
): Seq[(String, String)] = {
val mergeOptions = params.getOrElse("mergeOptions", Nil)
if (mergeOptions.isEmpty) {
Seq("mergeOptions" -> "At least one option must be enabled.")
} else if (!mergeOptions.forall(x => Seq("merge-commit", "squash", "rebase").contains(x))) {
Seq("mergeOptions" -> "mergeOptions are invalid.")
} else {
Nil
}
}
}
}
}

View File

@@ -1,7 +1,6 @@
package gitbucket.core.controller
import java.io.FileInputStream
import gitbucket.core.admin.html
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service.SystemSettingsService._
@@ -9,7 +8,6 @@ import gitbucket.core.service.{AccountService, RepositoryService}
import gitbucket.core.ssh.SshServer
import gitbucket.core.util.Implicits._
import gitbucket.core.util.StringUtil._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.{AdminAuthenticator, Mailer}
import org.apache.commons.io.IOUtils
import org.apache.commons.mail.EmailException
@@ -36,24 +34,38 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
private val form = mapping(
"baseUrl" -> trim(label("Base URL", optional(text()))),
"information" -> trim(label("Information", optional(text()))),
"allowAccountRegistration" -> trim(label("Account registration", boolean())),
"allowAnonymousAccess" -> trim(label("Anonymous access", boolean())),
"isCreateRepoOptionPublic" -> trim(label("Default visibility of new repository", boolean())),
"repositoryOperation" -> mapping(
"create" -> trim(label("Allow all users to create repository", boolean())),
"delete" -> trim(label("Allow all users to delete repository", boolean())),
"rename" -> trim(label("Allow all users to rename repository", boolean())),
"transfer" -> trim(label("Allow all users to transfer repository", boolean())),
"fork" -> trim(label("Allow all users to fork repository", boolean()))
)(RepositoryOperation.apply),
"gravatar" -> trim(label("Gravatar", boolean())),
"notification" -> trim(label("Notification", boolean())),
"activityLogLimit" -> trim(label("Limit of activity logs", optional(number()))),
"limitVisibleRepositories" -> trim(label("limitVisibleRepositories", boolean())),
"basicBehavior" -> mapping(
"allowAccountRegistration" -> trim(label("Account registration", boolean())),
"allowResetPassword" -> trim(label("Reset password", boolean())),
"allowAnonymousAccess" -> trim(label("Anonymous access", boolean())),
"isCreateRepoOptionPublic" -> trim(label("Default visibility of new repository", boolean())),
"repositoryOperation" -> mapping(
"create" -> trim(label("Allow all users to create repository", boolean())),
"delete" -> trim(label("Allow all users to delete repository", boolean())),
"rename" -> trim(label("Allow all users to rename repository", boolean())),
"transfer" -> trim(label("Allow all users to transfer repository", boolean())),
"fork" -> trim(label("Allow all users to fork repository", boolean()))
)(RepositoryOperation.apply),
"gravatar" -> trim(label("Gravatar", boolean())),
"notification" -> trim(label("Notification", boolean())),
"limitVisibleRepositories" -> trim(label("limitVisibleRepositories", boolean())),
)(BasicBehavior.apply),
"ssh" -> mapping(
"enabled" -> trim(label("SSH access", boolean())),
"host" -> trim(label("SSH host", optional(text()))),
"port" -> trim(label("SSH port", optional(number())))
"bindAddress" -> mapping(
"host" -> trim(label("Bind SSH host", optional(text()))),
"port" -> trim(label("Bind SSH port", optional(number()))),
)(
(hostOption, portOption) =>
hostOption.map(h => SshAddress(h, portOption.getOrElse(DefaultSshPort), GenericSshUser))
),
"publicAddress" -> mapping(
"host" -> trim(label("Public SSH host", optional(text()))),
"port" -> trim(label("Public SSH port", optional(number()))),
)(
(hostOption, portOption) =>
hostOption.map(h => SshAddress(h, portOption.getOrElse(PublicSshPort), GenericSshUser))
),
)(Ssh.apply),
"useSMTP" -> trim(label("SMTP", boolean())),
"smtp" -> optionalIfNotChecked(
@@ -109,14 +121,17 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
"timeout" -> trim(label("Timeout", long(required))),
"largeMaxFileSize" -> trim(label("Max file size for large file", long(required))),
"largeTimeout" -> trim(label("Timeout for large file", long(required)))
)(Upload.apply)
)(Upload.apply),
"repositoryViewer" -> mapping(
"maxFiles" -> trim(label("Max files", number(required)))
)(RepositoryViewerSettings.apply)
)(SystemSettings.apply).verifying { settings =>
Vector(
if (settings.ssh.enabled && settings.baseUrl.isEmpty) {
Some("baseUrl" -> "Base URL is required if SSH access is enabled.")
} else None,
if (settings.ssh.enabled && settings.ssh.sshHost.isEmpty) {
Some("sshHost" -> "SSH host is required if SSH access is enabled.")
if (settings.ssh.enabled && settings.ssh.bindAddress.isEmpty) {
Some("ssh.bindAddress.host" -> "SSH bind host is required if SSH access is enabled.")
} else None
).flatten
}
@@ -185,7 +200,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
val newUserForm = mapping(
"userName" -> trim(label("Username", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
"password" -> trim(label("Password", text(required, maxlength(20)))),
"password" -> trim(label("Password", text(required, maxlength(40)))),
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))),
"extraMailAddresses" -> list(
@@ -199,7 +214,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
val editUserForm = mapping(
"userName" -> trim(label("Username", text(required, maxlength(100), identifier))),
"password" -> trim(label("Password", optional(text(maxlength(20))))),
"password" -> trim(label("Password", optional(text(maxlength(40))))),
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))),
"extraMailAddresses" -> list(
@@ -307,12 +322,13 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
post("/admin/system", form)(adminOnly { form =>
saveSystemSettings(form)
if (form.sshAddress != context.settings.sshAddress) {
if (form.ssh.bindAddress != context.settings.sshBindAddress || form.ssh.publicAddress != context.settings.sshPublicAddress) {
SshServer.stop()
for {
sshAddress <- form.sshAddress
bindAddress <- form.ssh.bindAddress
publicAddress <- form.ssh.publicAddress.orElse(form.ssh.bindAddress)
baseUrl <- form.baseUrl
} SshServer.start(sshAddress, baseUrl)
} SshServer.start(bindAddress, publicAddress, baseUrl)
}
flash.update("info", "System settings has been updated.")
@@ -321,7 +337,12 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
post("/admin/system/sendmail", sendMailForm)(adminOnly { form =>
try {
new Mailer(context.settings.copy(smtp = Some(form.smtp), notification = true)).send(
new Mailer(
context.settings.copy(
smtp = Some(form.smtp),
basicBehavior = context.settings.basicBehavior.copy(notification = true)
)
).send(
to = form.testAddress,
subject = "Test message from GitBucket",
textMsg = "This is a test message from GitBucket.",
@@ -360,8 +381,8 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
})
get("/admin/users")(adminOnly {
val includeRemoved = params.get("includeRemoved").map(_.toBoolean).getOrElse(false)
val includeGroups = params.get("includeGroups").map(_.toBoolean).getOrElse(false)
val includeRemoved = params.get("includeRemoved").exists(_.toBoolean)
val includeGroups = params.get("includeGroups").exists(_.toBoolean)
val users = getAllUsers(includeRemoved, includeGroups)
val members = users.collect {
case account if (account.isGroupAccount) =>
@@ -461,31 +482,28 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
})
get("/admin/users/:groupName/_editgroup")(adminOnly {
defining(params("groupName")) { groupName =>
html.usergroup(getAccountByUserName(groupName, true), getGroupMembers(groupName))
}
val groupName = params("groupName")
html.usergroup(getAccountByUserName(groupName, true), getGroupMembers(groupName))
})
post("/admin/users/:groupName/_editgroup", editGroupForm)(adminOnly { form =>
defining(
params("groupName"),
form.members
.split(",")
.map {
_.split(":") match {
case Array(userName, isManager) => (userName, isManager.toBoolean)
}
val groupName = params("groupName")
val members = form.members
.split(",")
.map {
_.split(":") match {
case Array(userName, isManager) => (userName, isManager.toBoolean)
}
.toList
) {
case (groupName, members) =>
getAccountByUserName(groupName, true).map {
account =>
updateGroup(groupName, form.description, form.url, form.isRemoved)
}
.toList
if (form.isRemoved) {
// Remove from GROUP_MEMBER
updateGroupMembers(form.groupName, Nil)
getAccountByUserName(groupName, true).map {
account =>
updateGroup(groupName, form.description, form.url, form.isRemoved)
if (form.isRemoved) {
// Remove from GROUP_MEMBER
updateGroupMembers(form.groupName, Nil)
// // Remove repositories
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
// deleteRepository(groupName, repositoryName)
@@ -493,9 +511,9 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
// FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName))
// FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName))
// }
} else {
// Update GROUP_MEMBER
updateGroupMembers(form.groupName, members)
} else {
// Update GROUP_MEMBER
updateGroupMembers(form.groupName, members)
// // Update COLLABORATOR for group repositories
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
// removeCollaborators(form.groupName, repositoryName)
@@ -503,13 +521,12 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
// addCollaborator(form.groupName, repositoryName, userName)
// }
// }
}
}
updateImage(form.groupName, form.fileId, form.clearImage)
redirect("/admin/users")
updateImage(form.groupName, form.fileId, form.clearImage)
redirect("/admin/users")
} getOrElse NotFound()
}
} getOrElse NotFound()
})
get("/admin/data")(adminOnly {
@@ -557,12 +574,11 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
protected def disableByNotYourself(paramName: String): Constraint =
new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] = {
params.get(paramName).flatMap { userName =>
if (userName == context.loginAccount.get.userName && params.get("removed") == Some("true"))
Some("You can't disable your account yourself")
else
None
}
for {
userName <- params.get(paramName)
loginAccount <- context.loginAccount
if userName == loginAccount.userName && params.get("removed") == Some("true")
} yield "You can't disable your account yourself"
}
}

View File

@@ -1,6 +1,7 @@
package gitbucket.core.controller
import gitbucket.core.model.WebHook
import gitbucket.core.model.activity.{CreateWikiPageInfo, DeleteWikiInfo, EditWikiPageInfo}
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.service.WebHookService.WebHookGollumPayload
import gitbucket.core.wiki.html
@@ -13,6 +14,7 @@ import gitbucket.core.util.Directory._
import org.scalatra.forms._
import org.eclipse.jgit.api.Git
import org.scalatra.i18n.Messages
import scala.util.Using
class WikiController
@@ -24,6 +26,7 @@ class WikiController
with WebHookService
with ReadableUsersAuthenticator
with ReferrerAuthenticator
with RequestCache
trait WikiControllerBase extends ControllerBase {
self: WikiService
@@ -133,32 +136,38 @@ trait WikiControllerBase extends ControllerBase {
})
get("/:owner/:repository/wiki/:page/_revert/:commitId")(readableUsersOnly { repository =>
if (isEditable(repository)) {
val pageName = StringUtil.urlDecode(params("page"))
val Array(from, to) = params("commitId").split("\\.\\.\\.")
context.withLoginAccount {
loginAccount =>
if (isEditable(repository)) {
val pageName = StringUtil.urlDecode(params("page"))
val Array(from, to) = params("commitId").split("\\.\\.\\.")
if (revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, Some(pageName))) {
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}")
} else {
flash.update("info", "This patch was not able to be reversed.")
redirect(
s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}"
)
}
} else Unauthorized()
if (revertWikiPage(repository.owner, repository.name, from, to, loginAccount, Some(pageName))) {
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}")
} else {
flash.update("info", "This patch was not able to be reversed.")
redirect(
s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}"
)
}
} else Unauthorized()
}
})
get("/:owner/:repository/wiki/_revert/:commitId")(readableUsersOnly { repository =>
if (isEditable(repository)) {
val Array(from, to) = params("commitId").split("\\.\\.\\.")
context.withLoginAccount {
loginAccount =>
if (isEditable(repository)) {
val Array(from, to) = params("commitId").split("\\.\\.\\.")
if (revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, None)) {
redirect(s"/${repository.owner}/${repository.name}/wiki")
} else {
flash.update("info", "This patch was not able to be reversed.")
redirect(s"/${repository.owner}/${repository.name}/wiki/_compare/${from}...${to}")
}
} else Unauthorized()
if (revertWikiPage(repository.owner, repository.name, from, to, loginAccount, None)) {
redirect(s"/${repository.owner}/${repository.name}/wiki")
} else {
flash.update("info", "This patch was not able to be reversed.")
redirect(s"/${repository.owner}/${repository.name}/wiki/_compare/${from}...${to}")
}
} else Unauthorized()
}
})
get("/:owner/:repository/wiki/:page/_edit")(readableUsersOnly { repository =>
@@ -169,9 +178,9 @@ trait WikiControllerBase extends ControllerBase {
})
post("/:owner/:repository/wiki/_edit", editForm)(readableUsersOnly { (form, repository) =>
if (isEditable(repository)) {
defining(context.loginAccount.get) {
loginAccount =>
context.withLoginAccount {
loginAccount =>
if (isEditable(repository)) {
saveWikiPage(
repository.owner,
repository.name,
@@ -184,13 +193,9 @@ trait WikiControllerBase extends ControllerBase {
).foreach {
commitId =>
updateLastActivityDate(repository.owner, repository.name)
recordEditWikiPageActivity(
repository.owner,
repository.name,
loginAccount.userName,
form.pageName,
commitId
)
val wikiEditInfo =
EditWikiPageInfo(repository.owner, repository.name, loginAccount.userName, form.pageName, commitId)
recordActivity(wikiEditInfo)
callWebHookOf(repository.owner, repository.name, WebHook.Gollum, context.settings) {
getAccountByUserName(repository.owner).map { repositoryUser =>
WebHookGollumPayload("edited", form.pageName, commitId, repository, repositoryUser, loginAccount)
@@ -202,8 +207,8 @@ trait WikiControllerBase extends ControllerBase {
} else {
redirect(s"/${repository.owner}/${repository.name}/wiki")
}
}
} else Unauthorized()
} else Unauthorized()
}
})
get("/:owner/:repository/wiki/_new")(readableUsersOnly { repository =>
@@ -213,9 +218,9 @@ trait WikiControllerBase extends ControllerBase {
})
post("/:owner/:repository/wiki/_new", newForm)(readableUsersOnly { (form, repository) =>
if (isEditable(repository)) {
defining(context.loginAccount.get) {
loginAccount =>
context.withLoginAccount {
loginAccount =>
if (isEditable(repository)) {
saveWikiPage(
repository.owner,
repository.name,
@@ -228,7 +233,9 @@ trait WikiControllerBase extends ControllerBase {
).foreach {
commitId =>
updateLastActivityDate(repository.owner, repository.name)
recordCreateWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName)
val createWikiPageInfo =
CreateWikiPageInfo(repository.owner, repository.name, loginAccount.userName, form.pageName)
recordActivity(createWikiPageInfo)
callWebHookOf(repository.owner, repository.name, WebHook.Gollum, context.settings) {
getAccountByUserName(repository.owner).map { repositoryUser =>
WebHookGollumPayload("created", form.pageName, commitId, repository, repositoryUser, loginAccount)
@@ -241,28 +248,35 @@ trait WikiControllerBase extends ControllerBase {
} else {
redirect(s"/${repository.owner}/${repository.name}/wiki")
}
}
} else Unauthorized()
} else Unauthorized()
}
})
get("/:owner/:repository/wiki/:page/_delete")(readableUsersOnly { repository =>
if (isEditable(repository)) {
val pageName = StringUtil.urlDecode(params("page"))
context.withLoginAccount {
loginAccount =>
if (isEditable(repository)) {
val pageName = StringUtil.urlDecode(params("page"))
deleteWikiPage(
repository.owner,
repository.name,
pageName,
loginAccount.fullName,
loginAccount.mailAddress,
s"Destroyed ${pageName}"
)
val deleteWikiInfo = DeleteWikiInfo(
repository.owner,
repository.name,
loginAccount.userName,
pageName
)
recordActivity(deleteWikiInfo)
updateLastActivityDate(repository.owner, repository.name)
defining(context.loginAccount.get) { loginAccount =>
deleteWikiPage(
repository.owner,
repository.name,
pageName,
loginAccount.fullName,
loginAccount.mailAddress,
s"Destroyed ${pageName}"
)
updateLastActivityDate(repository.owner, repository.name)
redirect(s"/${repository.owner}/${repository.name}/wiki")
}
} else Unauthorized()
redirect(s"/${repository.owner}/${repository.name}/wiki")
} else Unauthorized()
}
})
get("/:owner/:repository/wiki/_pages")(referrersOnly { repository =>

View File

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

View File

@@ -4,6 +4,7 @@ import gitbucket.core.controller.{Context, ControllerBase}
import gitbucket.core.service._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.{ReadableUsersAuthenticator, ReferrerAuthenticator, RepositoryName}
import org.scalatra.ActionResult
trait ApiIssueCommentControllerBase extends ControllerBase {
self: AccountService
@@ -14,8 +15,8 @@ trait ApiIssueCommentControllerBase extends ControllerBase {
with ReadableUsersAuthenticator
with ReferrerAuthenticator =>
/*
* i. List comments on an issue
* https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue
* i. List issue comments for a repository
* https://docs.github.com/en/rest/reference/issues#list-issue-comments-for-a-repository
*/
get("/api/v3/repos/:owner/:repository/issues/:id/comments")(referrersOnly { repository =>
(for {
@@ -30,18 +31,90 @@ trait ApiIssueCommentControllerBase extends ControllerBase {
})
/*
* ii. List comments in a repository
* https://developer.github.com/v3/issues/comments/#list-comments-in-a-repository
* ii. Get an issue comment
* https://docs.github.com/en/rest/reference/issues#get-an-issue-comment
*/
get("/api/v3/repos/:owner/:repository/issues/comments/:id")(referrersOnly { repository =>
val commentId = params("id").toInt
getCommentForApi(repository.owner, repository.name, commentId) match {
case Some((issueComment, user, issue)) =>
JsonFormat(
ApiComment(issueComment, RepositoryName(repository), issue.issueId, ApiUser(user), issue.isPullRequest)
)
case _ => NotFound()
}
})
/*
* iii. Update an issue comment
* https://docs.github.com/en/rest/reference/issues#update-an-issue-comment
*/
patch("/api/v3/repos/:owner/:repository/issues/comments/:id")(readableUsersOnly { repository =>
val commentId = params("id")
val result = for {
issueComment <- getComment(repository.owner, repository.name, commentId)
issue <- getIssue(repository.owner, repository.name, issueComment.issueId.toString)
} yield {
if (isEditable(repository.owner, repository.name, issueComment.commentedUserName)) {
val body = extractFromJsonBody[CreateAComment].map(_.body)
updateCommentByApi(repository, issue, issueComment.commentId.toString, body)
getComment(repository.owner, repository.name, commentId) match {
case Some(issueComment) =>
JsonFormat(
ApiComment(
issueComment,
RepositoryName(repository),
issue.issueId,
ApiUser(context.loginAccount.get),
issue.isPullRequest
)
)
case _ =>
}
} else {
Unauthorized()
}
}
result match {
case Some(response) => response
case None => NotFound()
}
})
/*
* 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 =>
val maybeDeleteResponse: Option[Either[ActionResult, Option[Int]]] =
for {
commentId <- params("id").toIntOpt
comment <- getComment(repository.owner, repository.name, commentId.toString)
issue <- getIssue(repository.owner, repository.name, comment.issueId.toString)
} yield {
if (isEditable(repository.owner, repository.name, comment.commentedUserName)) {
val maybeDeletedComment = deleteCommentByApi(repository, comment, issue)
Right(maybeDeletedComment.map(_.commentId))
} else {
Left(Unauthorized())
}
}
maybeDeleteResponse
.map {
case Right(maybeDeletedCommentId) => maybeDeletedCommentId.getOrElse(NotFound())
case Left(err) => err
}
.getOrElse(NotFound())
})
/*
* v. List issue comments
* https://docs.github.com/en/rest/reference/issues#list-issue-comments
*/
/*
* iii. Get a single comment
* https://developer.github.com/v3/issues/comments/#get-a-single-comment
*/
/*
* iv. Create a comment
* https://developer.github.com/v3/issues/comments/#create-a-comment
* vi. Create an issue comment
* https://docs.github.com/en/rest/reference/issues#create-an-issue-comment
*/
post("/api/v3/repos/:owner/:repository/issues/:id/comments")(readableUsersOnly { repository =>
(for {
@@ -64,16 +137,6 @@ trait ApiIssueCommentControllerBase extends ControllerBase {
}) getOrElse NotFound()
})
/*
* v. Edit a comment
* https://developer.github.com/v3/issues/comments/#edit-a-comment
*/
/*
* vi. Delete a comment
* https://developer.github.com/v3/issues/comments/#delete-a-comment
*/
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
}

View File

@@ -5,7 +5,7 @@ import gitbucket.core.model.{Account, Issue}
import gitbucket.core.service.{AccountService, IssueCreationService, IssuesService, MilestonesService}
import gitbucket.core.service.IssuesService.IssueSearchCondition
import gitbucket.core.service.PullRequestService.PullRequestLimit
import gitbucket.core.util.{ReadableUsersAuthenticator, ReferrerAuthenticator, RepositoryName, UsersAuthenticator}
import gitbucket.core.util.{ReadableUsersAuthenticator, ReferrerAuthenticator, RepositoryName}
import gitbucket.core.util.Implicits._
trait ApiIssueControllerBase extends ControllerBase {
@@ -29,9 +29,9 @@ trait ApiIssueControllerBase extends ControllerBase {
val page = IssueSearchCondition.page(request)
// TODO: more api spec condition
val condition = IssueSearchCondition(request)
val baseOwner = getAccountByUserName(repository.owner).get
//val baseOwner = getAccountByUserName(repository.owner).get
val issues: List[(Issue, Account, Option[Account])] =
val issues: List[(Issue, Account, List[Account])] =
searchIssueByApi(
condition = condition,
offset = (page - 1) * PullRequestLimit,
@@ -40,14 +40,15 @@ trait ApiIssueControllerBase extends ControllerBase {
)
JsonFormat(issues.map {
case (issue, issueUser, assignedUser) =>
case (issue, issueUser, assigneeUsers) =>
ApiIssue(
issue = issue,
repositoryName = RepositoryName(repository),
user = ApiUser(issueUser),
assignee = assignedUser.map(ApiUser(_)),
assignees = assigneeUsers.map(ApiUser(_)),
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
.map(ApiLabel(_, RepositoryName(repository)))
.map(ApiLabel(_, RepositoryName(repository))),
issue.milestoneId.flatMap { getApiMilestone(repository, _) }
)
})
})
@@ -60,7 +61,8 @@ trait ApiIssueControllerBase extends ControllerBase {
(for {
issueId <- params("id").toIntOpt
issue <- getIssue(repository.owner, repository.name, issueId.toString)
users = getAccountsByUserNames(Set(issue.openedUserName) ++ issue.assignedUserName, Set())
assigneeUsers = getIssueAssignees(repository.owner, repository.name, issueId)
users = getAccountsByUserNames(Set(issue.openedUserName) ++ assigneeUsers.map(_.assigneeUserName), Set())
openedUser <- users.get(issue.openedUserName)
} yield {
JsonFormat(
@@ -68,8 +70,9 @@ trait ApiIssueControllerBase extends ControllerBase {
issue,
RepositoryName(repository),
ApiUser(openedUser),
issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)),
getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository)))
assigneeUsers.flatMap(x => users.get(x.assigneeUserName)).map(ApiUser(_)),
getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository))),
issue.milestoneId.flatMap { getApiMilestone(repository, _) }
)
)
}) getOrElse NotFound()
@@ -90,7 +93,7 @@ trait ApiIssueControllerBase extends ControllerBase {
repository,
data.title,
data.body,
data.assignees.headOption,
data.assignees,
milestone.map(_.milestoneId),
None,
data.labels,
@@ -101,9 +104,12 @@ trait ApiIssueControllerBase extends ControllerBase {
issue,
RepositoryName(repository),
ApiUser(loginAccount),
issue.assignedUserName.flatMap(getAccountByUserName(_)).map(ApiUser(_)),
getIssueAssignees(repository.owner, repository.name, issue.issueId)
.flatMap(x => getAccountByUserName(x.assigneeUserName, false))
.map(ApiUser.apply),
getIssueLabels(repository.owner, repository.name, issue.issueId)
.map(ApiLabel(_, RepositoryName(repository)))
.map(ApiLabel(_, RepositoryName(repository))),
issue.milestoneId.flatMap { getApiMilestone(repository, _) }
)
)
}) getOrElse NotFound()

View File

@@ -121,7 +121,7 @@ trait ApiIssueLabelControllerBase extends ControllerBase {
*/
post("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
JsonFormat(for {
data <- extractFromJsonBody[Seq[String]];
data <- extractFromJsonBody[Seq[String]]
issueId <- params("id").toIntOpt
} yield {
data.map { labelName =>
@@ -160,7 +160,7 @@ trait ApiIssueLabelControllerBase extends ControllerBase {
*/
put("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
JsonFormat(for {
data <- extractFromJsonBody[Seq[String]];
data <- extractFromJsonBody[Seq[String]]
issueId <- params("id").toIntOpt
} yield {
deleteAllIssueLabels(repository.owner, repository.name, issueId, true)

View File

@@ -0,0 +1,104 @@
package gitbucket.core.controller.api
import gitbucket.core.api._
import gitbucket.core.controller.ControllerBase
import gitbucket.core.service.MilestonesService
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
import gitbucket.core.util.Implicits._
import org.scalatra.NoContent
trait ApiIssueMilestoneControllerBase extends ControllerBase {
self: MilestonesService with WritableUsersAuthenticator with ReferrerAuthenticator =>
/*
* i. List milestones
* https://docs.github.com/en/rest/reference/issues#list-milestones
*/
get("/api/v3/repos/:owner/:repository/milestones")(referrersOnly { repository =>
val state = params.getOrElse("state", "all")
// TODO "sort", "direction" params should be implemented.
val apiMilestones = (for (milestoneWithIssue <- getMilestonesWithIssueCount(repository.owner, repository.name)
.sortBy(p => p._1.milestoneId))
yield {
ApiMilestone(
repository.repository,
milestoneWithIssue._1,
milestoneWithIssue._2,
milestoneWithIssue._3
)
}).reverse
state match {
case "all" => JsonFormat(apiMilestones)
case "open" | "closed" =>
JsonFormat(
apiMilestones.filter(p => p.state == state)
)
case _ => NotFound()
}
})
/*
* ii. Create a milestone
* https://docs.github.com/en/rest/reference/issues#create-a-milestone
*/
post("/api/v3/repos/:owner/:repository/milestones")(writableUsersOnly { repository =>
(for {
data <- extractFromJsonBody[CreateAMilestone] if data.isValid
milestoneId = createMilestone(
repository.owner,
repository.name,
data.title,
data.description,
data.due_on
)
apiMilestone <- getApiMilestone(repository, milestoneId)
} yield {
JsonFormat(apiMilestone)
}) getOrElse NotFound()
})
/*
* iii. Get a milestone
* https://docs.github.com/en/rest/reference/issues#get-a-milestone
*/
get("/api/v3/repos/:owner/:repository/milestones/:number")(referrersOnly { repository =>
val milestoneId = params("number").toInt // use milestoneId as number
(for (apiMilestone <- getApiMilestone(repository, milestoneId)) yield {
JsonFormat(apiMilestone)
}) getOrElse NotFound()
})
/*
* iv.Update a milestone
* https://docs.github.com/en/rest/reference/issues#update-a-milestone
*/
patch("/api/v3/repos/:owner/:repository/milestones/:number")(writableUsersOnly { repository =>
val milestoneId = params("number").toInt
(for {
data <- extractFromJsonBody[CreateAMilestone] if data.isValid
milestone <- getMilestone(repository.owner, repository.name, milestoneId)
_ = (data.state, milestone.closedDate) match {
case ("open", Some(_)) =>
openMilestone(milestone)
case ("closed", None) =>
closeMilestone(milestone)
case _ =>
}
milestone <- getMilestone(repository.owner, repository.name, milestoneId)
_ = updateMilestone(milestone.copy(title = data.title, description = data.description, dueDate = data.due_on))
apiMilestone <- getApiMilestone(repository, milestoneId)
} yield {
JsonFormat(apiMilestone)
}) getOrElse NotFound()
})
/*
* v. Delete a milestone
* https://docs.github.com/en/rest/reference/issues#delete-a-milestone
*/
delete("/api/v3/repos/:owner/:repository/milestones/:number")(writableUsersOnly { repository =>
val milestoneId = params("number").toInt // use milestoneId as number
deleteMilestone(repository.owner, repository.name, milestoneId)
NoContent()
})
}

View File

@@ -1,5 +1,5 @@
package gitbucket.core.controller.api
import gitbucket.core.api.{ApiGroup, CreateAGroup, ApiRepository, ApiUser, JsonFormat}
import gitbucket.core.api.{ApiGroup, CreateAGroup, JsonFormat}
import gitbucket.core.controller.ControllerBase
import gitbucket.core.service.{AccountService, RepositoryService}
import gitbucket.core.util.Implicits._

View File

@@ -10,7 +10,7 @@ import gitbucket.core.util.Implicits._
import gitbucket.core.util.JGitUtil.CommitInfo
import gitbucket.core.util._
import org.eclipse.jgit.api.Git
import org.scalatra.NoContent
import org.scalatra.{Conflict, MethodNotAllowed, NoContent, Ok}
import scala.util.Using
import scala.jdk.CollectionConverters._
@@ -40,7 +40,7 @@ trait ApiPullRequestControllerBase extends ControllerBase {
val condition = IssueSearchCondition(request)
val baseOwner = getAccountByUserName(repository.owner).get
val issues: List[(Issue, Account, Int, PullRequest, Repository, Account, Option[Account])] =
val issues: List[(Issue, Account, Int, PullRequest, Repository, Account, List[Account])] =
searchPullRequestByApi(
condition = condition,
offset = (page - 1) * PullRequestLimit,
@@ -49,7 +49,7 @@ trait ApiPullRequestControllerBase extends ControllerBase {
)
JsonFormat(issues.map {
case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner, assignee) =>
case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner, assignees) =>
ApiPullRequest(
issue = issue,
pullRequest = pullRequest,
@@ -58,7 +58,7 @@ trait ApiPullRequestControllerBase extends ControllerBase {
user = ApiUser(issueUser),
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
.map(ApiLabel(_, RepositoryName(repository))),
assignee = assignee.map(ApiUser.apply),
assignees = assignees.map(ApiUser.apply),
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
)
})
@@ -99,7 +99,6 @@ trait ApiPullRequestControllerBase extends ControllerBase {
loginUser = context.loginAccount.get.userName,
title = createPullReq.title,
content = createPullReq.body,
assignedUserName = None,
milestoneId = None,
priorityId = None,
isPullRequest = true
@@ -161,8 +160,28 @@ trait ApiPullRequestControllerBase extends ControllerBase {
/*
* v. Update a pull request
* https://developer.github.com/v3/pulls/#update-a-pull-request
* https://docs.github.com/en/rest/reference/pulls#update-a-pull-request
*/
patch("/api/v3/repos/:owner/:repository/pulls/:id")(referrersOnly { repository =>
(for {
issueId <- params("id").toIntOpt
account <- context.loginAccount
settings = context.settings
data <- extractFromJsonBody[UpdateAPullRequest]
} yield {
updatePullRequestsByApi(
repository,
issueId,
account,
settings,
data.title,
data.body,
data.state,
data.base
)
JsonFormat(getApiPullRequest(repository, issueId))
}) getOrElse NotFound()
})
/*
* vi. List commits on a pull request
@@ -210,15 +229,79 @@ trait ApiPullRequestControllerBase extends ControllerBase {
if (checkConflict(repository.owner, repository.name, pullReq.branch, issueId).isDefined) {
NoContent
} else {
NotFound
NotFound()
}
}).getOrElse(NotFound)
}).getOrElse(NotFound())
})
/*
* ix. Merge a pull request (Merge Button)
* https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-button
* https://docs.github.com/en/rest/reference/pulls#merge-a-pull-request
*/
put("/api/v3/repos/:owner/:repository/pulls/:id/merge")(referrersOnly { repository =>
(for {
//TODO: crash when body is empty
//TODO: Implement sha parameter
data <- extractFromJsonBody[MergeAPullRequest]
issueId <- params("id").toIntOpt
(issue, pullReq) <- getPullRequest(repository.owner, repository.name, issueId)
} yield {
if (checkConflict(repository.owner, repository.name, pullReq.branch, issueId).isDefined) {
Conflict(
JsonFormat(
FailToMergePrResponse(
message = "Head branch was modified. Review and try the merge again.",
documentation_url = "https://docs.github.com/en/rest/reference/pulls#merge-a-pull-request",
)
)
)
} else {
if (issue.closed) {
MethodNotAllowed(
JsonFormat(
FailToMergePrResponse(
message = "Pull Request is not mergeable, Closed",
documentation_url = "https://docs.github.com/en/rest/reference/pulls#merge-a-pull-request",
)
)
)
} else {
val strategy =
if (data.merge_method.getOrElse("merge-commit") == "merge") "merge-commit"
else data.merge_method.getOrElse("merge-commit")
mergePullRequest(
repository,
issueId,
context.loginAccount.get,
data.commit_message.getOrElse(""), //TODO: Implement commit_title
strategy,
pullReq.isDraft,
context.settings
) match {
case Right(objectId) =>
Ok(
JsonFormat(
SuccessToMergePrResponse(
sha = objectId.toString,
merged = true,
message = "Pull Request successfully merged"
)
)
)
case Left(message) =>
MethodNotAllowed(
JsonFormat(
FailToMergePrResponse(
message = "Pull Request is not mergeable",
documentation_url = "https://docs.github.com/en/rest/reference/pulls#merge-a-pull-request",
)
)
)
}
}
}
})
})
/*
* x. Labels, assignees, and milestones
@@ -235,8 +318,8 @@ trait ApiPullRequestControllerBase extends ControllerBase {
baseOwner <- users.get(repository.owner)
headOwner <- users.get(pullRequest.requestUserName)
issueUser <- users.get(issue.openedUserName)
assignee = issue.assignedUserName.flatMap { userName =>
getAccountByUserName(userName, false)
assignees = getIssueAssignees(repository.owner, repository.name, issueId).flatMap { assignedUser =>
getAccountByUserName(assignedUser.assigneeUserName, false)
}
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
} yield {
@@ -248,7 +331,7 @@ trait ApiPullRequestControllerBase extends ControllerBase {
user = ApiUser(issueUser),
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
.map(ApiLabel(_, RepositoryName(repository))),
assignee = assignee.map(ApiUser.apply),
assignees = assignees.map(ApiUser.apply),
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
)
}

View File

@@ -1,5 +1,5 @@
package gitbucket.core.controller.api
import java.io.{ByteArrayInputStream, File}
import java.io.File
import gitbucket.core.api._
import gitbucket.core.controller.ControllerBase
@@ -7,9 +7,8 @@ import gitbucket.core.service.{AccountService, ReleaseService}
import gitbucket.core.util.Directory.getReleaseFilesDir
import gitbucket.core.util.{FileUtil, ReferrerAuthenticator, RepositoryName, WritableUsersAuthenticator}
import gitbucket.core.util.Implicits._
import gitbucket.core.util.SyntaxSugars.defining
import org.apache.commons.io.FileUtils
import org.scalatra.{Created, NoContent}
import org.scalatra.NoContent
trait ApiReleaseControllerBase extends ControllerBase {
self: AccountService with ReleaseService with ReferrerAuthenticator with WritableUsersAuthenticator =>
@@ -87,7 +86,7 @@ trait ApiReleaseControllerBase extends ControllerBase {
/**
* vi. Edit a release
* https://developer.github.com/v3/repos/releases/#edit-a-release
* Incompatiblity info: GitHub API requires :release_id, but GitBucket API requires :tag_name
* Incompatibility info: GitHub API requires :release_id, but GitBucket API requires :tag_name
*/
patch("/api/v3/repos/:owner/:repository/releases/:tag")(writableUsersOnly { repository =>
(for {
@@ -104,7 +103,7 @@ trait ApiReleaseControllerBase extends ControllerBase {
/**
* vii. Delete a release
* https://developer.github.com/v3/repos/releases/#delete-a-release
* Incompatiblity info: GitHub API requires :release_id, but GitBucket API requires :tag_name
* Incompatibility info: GitHub API requires :release_id, but GitBucket API requires :tag_name
*/
delete("/api/v3/repos/:owner/:repository/releases/:tag")(writableUsersOnly { repository =>
val tag = params("tag")
@@ -120,41 +119,40 @@ trait ApiReleaseControllerBase extends ControllerBase {
* ix. Upload a release asset
* https://developer.github.com/v3/repos/releases/#upload-a-release-asset
*/
post("/api/v3/repos/:owner/:repository/releases/:tag/assets")(writableUsersOnly { repository =>
val name = params("name")
val tag = params("tag")
getRelease(repository.owner, repository.name, tag)
.map {
release =>
defining(FileUtil.generateFileId) { fileId =>
val buf = new Array[Byte](request.inputStream.available())
request.inputStream.read(buf)
FileUtils.writeByteArrayToFile(
new File(
getReleaseFilesDir(repository.owner, repository.name),
FileUtil.checkFilename(tag + "/" + fileId)
),
buf
)
createReleaseAsset(
repository.owner,
repository.name,
tag,
fileId,
name,
request.contentLength.getOrElse(0),
context.loginAccount.get
)
getReleaseAsset(repository.owner, repository.name, tag, fileId)
.map { asset =>
JsonFormat(ApiReleaseAsset(asset, RepositoryName(repository)))
}
.getOrElse {
ApiError("Unknown error")
}
}
}
.getOrElse(NotFound())
post("/api/v3/repos/:owner/:repository/releases/:tag/assets")(writableUsersOnly {
repository =>
val name = params("name")
val tag = params("tag")
getRelease(repository.owner, repository.name, tag)
.map { release =>
val fileId = FileUtil.generateFileId
val buf = new Array[Byte](request.inputStream.available())
request.inputStream.read(buf)
FileUtils.writeByteArrayToFile(
new File(
getReleaseFilesDir(repository.owner, repository.name),
FileUtil.checkFilename(tag + "/" + fileId)
),
buf
)
createReleaseAsset(
repository.owner,
repository.name,
tag,
fileId,
name,
request.contentLength.getOrElse(0),
context.loginAccount.get
)
getReleaseAsset(repository.owner, repository.name, tag, fileId)
.map { asset =>
JsonFormat(ApiReleaseAsset(asset, RepositoryName(repository)))
}
.getOrElse {
ApiError("Unknown error")
}
}
.getOrElse(NotFound())
})
/**

View File

@@ -7,6 +7,8 @@ import gitbucket.core.util.Directory._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.JGitUtil.getBranches
import org.eclipse.jgit.api.Git
import org.scalatra.NoContent
import scala.util.Using
trait ApiRepositoryBranchControllerBase extends ControllerBase {
@@ -22,7 +24,7 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
/**
* i. List branches
* https://developer.github.com/v3/repos/branches/#list-branches
* https://docs.github.com/en/rest/reference/repos#list-branches
*/
get("/api/v3/repos/:owner/:repository/branches")(referrersOnly { repository =>
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
@@ -41,8 +43,8 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
})
/**
* ii. Get branch
* https://developer.github.com/v3/repos/branches/#get-branch
* ii. Get a branch
* https://docs.github.com/en/rest/reference/repos#get-a-branch
*/
get("/api/v3/repos/:owner/:repository/branches/*")(referrersOnly { repository =>
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
@@ -65,147 +67,206 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
/*
* iii. Get branch protection
* https://developer.github.com/v3/repos/branches/#get-branch-protection
* https://docs.github.com/en/rest/reference/repos#get-branch-protection
*/
get("/api/v3/repos/:owner/:repository/branches/:branch/protection")(referrersOnly { repository =>
val branch = params("branch")
if (repository.branchList.contains(branch)) {
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
JsonFormat(
ApiBranchProtection(protection)
)
} else { NotFound() }
})
/*
* iv. Update branch protection
* https://developer.github.com/v3/repos/branches/#update-branch-protection
* https://docs.github.com/en/rest/reference/repos#update-branch-protection
*/
/*
* v. Remove branch protection
* https://developer.github.com/v3/repos/branches/#remove-branch-protection
* v. Delete branch protection
* https://docs.github.com/en/rest/reference/repos#delete-branch-protection
*/
delete("/api/v3/repos/:owner/:repository/branches/:branch/protection")(writableUsersOnly { repository =>
val branch = params("branch")
if (repository.branchList.contains(branch)) {
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
if (protection.enabled) {
disableBranchProtection(repository.owner, repository.name, branch)
NoContent()
} else NotFound()
} else NotFound()
})
/*
* vi. Get admin branch protection
* https://docs.github.com/en/rest/reference/repos#get-admin-branch-protection
*/
/*
* vi. Get required status checks of protected branch
* https://developer.github.com/v3/repos/branches/#get-required-status-checks-of-protected-branch
* vii. Set admin branch protection
* https://docs.github.com/en/rest/reference/repos#set-admin-branch-protection
*/
/*
* vii. Update required status checks of protected branch
* https://developer.github.com/v3/repos/branches/#update-required-status-checks-of-protected-branch
* viii. Delete admin branch protection
* https://docs.github.com/en/rest/reference/repos#delete-admin-branch-protection
*/
/*
* viii. Remove required status checks of protected branch
* https://developer.github.com/v3/repos/branches/#remove-required-status-checks-of-protected-branch
* ix. Get pull request review protection
* https://docs.github.com/en/rest/reference/repos#get-pull-request-review-protection
*/
/*
* ix. List required status checks contexts of protected branch
* https://developer.github.com/v3/repos/branches/#list-required-status-checks-contexts-of-protected-branch
* x. Update pull request review protection
* https://docs.github.com/en/rest/reference/repos#update-pull-request-review-protection
*/
/*
* x. Replace required status checks contexts of protected branch
* https://developer.github.com/v3/repos/branches/#replace-required-status-checks-contexts-of-protected-branch
* xi. Delete pull request review protection
* https://docs.github.com/en/rest/reference/repos#delete-pull-request-review-protection
*/
/*
* xi. Add required status checks contexts of protected branch
* https://developer.github.com/v3/repos/branches/#remove-required-status-checks-contexts-of-protected-branch
* xii. Get commit signature protection
* https://docs.github.com/en/rest/reference/repos#get-commit-signature-protection
*/
/*
* xii. Remove required status checks contexts of protected branch
* https://developer.github.com/v3/repos/branches/#remove-required-status-checks-contexts-of-protected-branch
* xiii. Create commit signature protection
* https://docs.github.com/en/rest/reference/repos#create-commit-signature-protection
*/
/*
* xiii. Get pull request review enforcement of protected branch
* https://developer.github.com/v3/repos/branches/#get-pull-request-review-enforcement-of-protected-branch
* xiv. Delete commit signature protection
* https://docs.github.com/en/rest/reference/repos#delete-commit-signature-protection
*/
/*
* xiv. Update pull request review enforcement of protected branch
* https://developer.github.com/v3/repos/branches/#update-pull-request-review-enforcement-of-protected-branch
* xv. Get status checks protection
* https://docs.github.com/en/rest/reference/repos#get-status-checks-protection
*/
get("/api/v3/repos/:owner/:repository/branches/:branch/protection/required_status_checks")(referrersOnly {
repository =>
val branch = params("branch")
if (repository.branchList.contains(branch)) {
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
JsonFormat(
ApiBranchProtection(protection).required_status_checks
)
} else { NotFound() }
})
/*
* xvi. Update status check protection
* https://docs.github.com/en/rest/reference/repos#update-status-check-protection
*/
/*
* xv. Remove pull request review enforcement of protected branch
* https://developer.github.com/v3/repos/branches/#remove-pull-request-review-enforcement-of-protected-branch
* xvii. Remove status check protection
* https://docs.github.com/en/rest/reference/repos#remove-status-check-protection
*/
/*
* xvi. Get required signatures of protected branch
* https://developer.github.com/v3/repos/branches/#get-required-signatures-of-protected-branch
* xviii. Get all status check contexts
* https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#get-all-status-check-contexts
*/
get("/api/v3/repos/:owner/:repository/branches/:branch/protection/required_status_checks/contexts")(referrersOnly {
repository =>
val branch = params("branch")
if (repository.branchList.contains(branch)) {
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
if (protection.enabled) {
protection.contexts.toList
} else NotFound()
} else NotFound()
})
/*
* xix. Add status check contexts
* https://docs.github.com/en/rest/reference/repos#add-status-check-contexts
*/
/*
* xvii. Add required signatures of protected branch
* https://developer.github.com/v3/repos/branches/#add-required-signatures-of-protected-branch
* xx. Set status check contexts
* https://docs.github.com/en/rest/reference/repos#set-status-check-contexts
*/
/*
* xviii. Remove required signatures of protected branch
* https://developer.github.com/v3/repos/branches/#remove-required-signatures-of-protected-branch
* xxi. Remove status check contexts
* https://docs.github.com/en/rest/reference/repos#remove-status-check-contexts
*/
/*
* xix. Get admin enforcement of protected branch
* https://developer.github.com/v3/repos/branches/#get-admin-enforcement-of-protected-branch
* xxii. Get access restrictions
* https://docs.github.com/en/rest/reference/repos#get-access-restrictions
*/
/*
* xx. Add admin enforcement of protected branch
* https://developer.github.com/v3/repos/branches/#add-admin-enforcement-of-protected-branch
* xxiii. Delete access restrictions
* https://docs.github.com/en/rest/reference/repos#delete-access-restrictions
*/
/*
* xxi. Remove admin enforcement of protected branch
* https://developer.github.com/v3/repos/branches/#remove-admin-enforcement-of-protected-branch
* xxiv. Get apps with access to the protected branch
* https://docs.github.com/en/rest/reference/repos#get-apps-with-access-to-the-protected-branch
*/
/*
* xxii. Get restrictions of protected branch
* https://developer.github.com/v3/repos/branches/#get-restrictions-of-protected-branch
* xxv. Add app access restrictions
* https://docs.github.com/en/rest/reference/repos#add-app-access-restrictions
*/
/*
* xxiii. Remove restrictions of protected branch
* https://developer.github.com/v3/repos/branches/#remove-restrictions-of-protected-branch
* xxvi. Set app access restrictions
* https://docs.github.com/en/rest/reference/repos#set-app-access-restrictions
*/
/*
* xxiv. List team restrictions of protected branch
* https://developer.github.com/v3/repos/branches/#list-team-restrictions-of-protected-branch
* xxvii. Remove app access restrictions
* https://docs.github.com/en/rest/reference/repos#remove-app-access-restrictions
*/
/*
* xxv. Replace team restrictions of protected branch
* https://developer.github.com/v3/repos/branches/#replace-team-restrictions-of-protected-branch
* xxviii. Get teams with access to the protected branch
* https://docs.github.com/en/rest/reference/repos#get-teams-with-access-to-the-protected-branch
*/
/*
* xxvi. Add team restrictions of protected branch
* https://developer.github.com/v3/repos/branches/#add-team-restrictions-of-protected-branch
* xxix. Add team access restrictions
* https://docs.github.com/en/rest/reference/repos#add-team-access-restrictions
*/
/*
* xxvii. Remove team restrictions of protected branch
* https://developer.github.com/v3/repos/branches/#remove-team-restrictions-of-protected-branch
* xxx. Set team access restrictions
* https://docs.github.com/en/rest/reference/repos#set-team-access-restrictions
*/
/*
* xxviii. List user restrictions of protected branch
* https://developer.github.com/v3/repos/branches/#list-user-restrictions-of-protected-branch
* xxxi. Remove team access restrictions
* https://docs.github.com/en/rest/reference/repos#remove-team-access-restrictions
*/
/*
* xxix. Replace user restrictions of protected branch
* https://developer.github.com/v3/repos/branches/#replace-user-restrictions-of-protected-branch
* xxxii. Get users with access to the protected branch
* https://docs.github.com/en/rest/reference/repos#get-users-with-access-to-the-protected-branch
*/
/*
* xxx. Add user restrictions of protected branch
* https://developer.github.com/v3/repos/branches/#add-user-restrictions-of-protected-branch
* xxxiii. Add user access restrictions
* https://docs.github.com/en/rest/reference/repos#add-user-access-restrictions
*/
/*
* xxxi. Remove user restrictions of protected branch
* https://developer.github.com/v3/repos/branches/#remove-user-restrictions-of-protected-branch
* xxxiv. Set user access restrictions
* https://docs.github.com/en/rest/reference/repos#set-user-access-restrictions
*/
/*
* xxxv. Remove user access restrictions
* https://docs.github.com/en/rest/reference/repos#remove-user-access-restrictions
*/
/**

View File

@@ -1,5 +1,5 @@
package gitbucket.core.controller.api
import gitbucket.core.api.{AddACollaborator, ApiUser, JsonFormat}
import gitbucket.core.api.{AddACollaborator, ApiRepositoryCollaborator, ApiUser, JsonFormat}
import gitbucket.core.controller.ControllerBase
import gitbucket.core.service.{AccountService, RepositoryService}
import gitbucket.core.util.Implicits._
@@ -10,8 +10,8 @@ trait ApiRepositoryCollaboratorControllerBase extends ControllerBase {
self: RepositoryService with AccountService with ReferrerAuthenticator with OwnerAuthenticator =>
/*
* i. List collaborators
* https://developer.github.com/v3/repos/collaborators/#list-collaborators
* i. List repository collaborators
* https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#list-repository-collaborators
*/
get("/api/v3/repos/:owner/:repository/collaborators")(referrersOnly { repository =>
// TODO Should ApiUser take permission? getCollaboratorUserNames does not return owner group members.
@@ -19,19 +19,40 @@ trait ApiRepositoryCollaboratorControllerBase extends ControllerBase {
getCollaboratorUserNames(params("owner"), params("repository")).map(u => ApiUser(getAccountByUserName(u).get))
)
})
/*
* ii. Check if a user is a collaborator
* https://developer.github.com/v3/repos/collaborators/#check-if-a-user-is-a-collaborator
* https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#check-if-a-user-is-a-repository-collaborator
*/
get("/api/v3/repos/:owner/:repository/collaborators/:userName")(referrersOnly { repository =>
(for (account <- getAccountByUserName(params("userName"))) yield {
if (getCollaboratorUserNames(repository.owner, repository.name).contains(account.userName)) {
NoContent()
} else {
NotFound()
}
}) getOrElse NotFound()
})
/*
* iii. Review a user's permission level
* https://developer.github.com/v3/repos/collaborators/#review-a-users-permission-level
* iii. Get repository permissions for a user
* https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#get-repository-permissions-for-a-user
*/
get("/api/v3/repos/:owner/:repository/collaborators/:userName/permission")(referrersOnly { repository =>
(for {
account <- getAccountByUserName(params("userName"))
collaborator <- getCollaborators(repository.owner, repository.name)
.find(p => p._1.collaboratorName == account.userName)
} yield {
JsonFormat(
ApiRepositoryCollaborator(collaborator._1.role, ApiUser(account))
)
}) getOrElse NotFound()
})
/*
* iv. Add user as a collaborator
* https://developer.github.com/v3/repos/collaborators/#add-user-as-a-collaborator
* iv. Add a repository collaborator
* https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#add-a-repository-collaborator
* requested #1586
*/
put("/api/v3/repos/:owner/:repository/collaborators/:userName")(ownerOnly { repository =>
@@ -44,8 +65,8 @@ trait ApiRepositoryCollaboratorControllerBase extends ControllerBase {
})
/*
* v. Remove user as a collaborator
* https://developer.github.com/v3/repos/collaborators/#remove-user-as-a-collaborator
* v. Remove a repository collaborator
* https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#remove-a-repository-collaborator
* requested #1586
*/
delete("/api/v3/repos/:owner/:repository/collaborators/:userName")(ownerOnly { repository =>

View File

@@ -1,19 +1,34 @@
package gitbucket.core.controller.api
import gitbucket.core.api.{ApiCommits, JsonFormat}
import gitbucket.core.api.{ApiBranchCommit, ApiBranchForHeadCommit, ApiCommits, JsonFormat}
import gitbucket.core.controller.ControllerBase
import gitbucket.core.model.Account
import gitbucket.core.service.{AccountService, CommitsService}
import gitbucket.core.service.{AccountService, CommitsService, ProtectedBranchService}
import gitbucket.core.util.Directory.getRepositoryDir
import gitbucket.core.util.Implicits._
import gitbucket.core.util.JGitUtil.CommitInfo
import gitbucket.core.util.JGitUtil.{CommitInfo, getBranches, getBranchesOfCommit}
import gitbucket.core.util.{JGitUtil, ReferrerAuthenticator, RepositoryName}
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.revwalk.RevWalk
import org.eclipse.jgit.revwalk.filter.{
AndRevFilter,
AuthorRevFilter,
CommitTimeRevFilter,
MaxCountRevFilter,
RevFilter,
SkipRevFilter
}
import org.eclipse.jgit.treewalk.filter.{AndTreeFilter, PathFilterGroup, TreeFilter}
import scala.collection.mutable.ListBuffer
import scala.jdk.CollectionConverters._
import scala.util.Using
import math.min
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter._
import java.util.Date
import java.time.ZoneOffset
trait ApiRepositoryCommitControllerBase extends ControllerBase {
self: AccountService with CommitsService with ReferrerAuthenticator =>
self: AccountService with CommitsService with ProtectedBranchService with ReferrerAuthenticator =>
/*
* i. List commits on a repository
* https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository
@@ -21,8 +36,13 @@ trait ApiRepositoryCommitControllerBase extends ControllerBase {
get("/api/v3/repos/:owner/:repository/commits")(referrersOnly { repository =>
val owner = repository.owner
val name = repository.name
// TODO: The following parameters need to be implemented. [:path, :author, :since, :until]
val sha = if (request.body.nonEmpty) (parse(request.body) \ "sha").extract[String] else "refs/heads/master";
val sha = params.get("sha").filter(_.nonEmpty).getOrElse("HEAD")
val page = params.get("page").filter(_.nonEmpty).getOrElse("1").toInt
val per_page = min(params.get("per_page").filter(_.nonEmpty).getOrElse("30").toInt, 100)
val author = params.get("author").filter(_.nonEmpty)
val path = params.get("path").filter(_.nonEmpty)
val since = params.get("since").filter(_.nonEmpty)
val until = params.get("until").filter(_.nonEmpty)
Using.resource(Git.open(getRepositoryDir(owner, name))) {
git =>
val repo = git.getRepository
@@ -30,7 +50,37 @@ trait ApiRepositoryCommitControllerBase extends ControllerBase {
revWalk =>
val objectId = repo.resolve(sha)
revWalk.markStart(revWalk.parseCommit(objectId))
JsonFormat(revWalk.asScala.take(30).map {
if (path.nonEmpty) {
revWalk.setTreeFilter(
AndTreeFilter.create(PathFilterGroup.createFromStrings(path.get), TreeFilter.ANY_DIFF)
)
}
val revfilters = new ListBuffer[(RevFilter)]()
if (author.nonEmpty) {
revfilters += AuthorRevFilter.create(author.get)
}
if (since.nonEmpty) {
revfilters += CommitTimeRevFilter.after(
Date.from(LocalDateTime.parse(since.get, ISO_DATE_TIME).toInstant(ZoneOffset.UTC))
)
}
if (until.nonEmpty) {
revfilters += CommitTimeRevFilter.before(
Date.from(LocalDateTime.parse(until.get, ISO_DATE_TIME).toInstant(ZoneOffset.UTC))
)
}
if (page > 1) {
revfilters += SkipRevFilter.create(page * per_page - 2)
}
revfilters += MaxCountRevFilter.create(per_page);
revWalk.setRevFilter(
if (revfilters.size > 1) {
AndRevFilter.create(revfilters.toArray)
} else {
revfilters(0)
}
)
JsonFormat(revWalk.asScala.map {
commit =>
val commitInfo = new CommitInfo(commit)
ApiCommits(
@@ -39,7 +89,7 @@ trait ApiRepositoryCommitControllerBase extends ControllerBase {
diffs = JGitUtil.getDiffs(git, commitInfo.parents.headOption, commitInfo.id, false, true),
author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress),
committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress),
commentCount = getCommitComment(repository.owner, repository.name, commitInfo.id.toString).size
commentCount = getCommitComment(repository.owner, repository.name, commitInfo.id).size
)
})
}
@@ -107,7 +157,25 @@ trait ApiRepositoryCommitControllerBase extends ControllerBase {
*/
/*
* v. Commit signature verification
* https://developer.github.com/v3/repos/commits/#commit-signature-verification
*/
* v. Commit signature verification
* https://developer.github.com/v3/repos/commits/#commit-signature-verification
*/
/*
* vi. List branches for HEAD commit
* https://docs.github.com/en/rest/reference/repos#list-branches-for-head-commit
*/
get("/api/v3/repos/:owner/:repository/commits/:sha/branches-where-head")(referrersOnly { repository =>
val sha = params("sha")
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
val apiBranchForCommits = for {
branch <- getBranchesOfCommit(git, sha)
br <- getBranches(git, branch, repository.repository.originUserName.isEmpty).find(_.name == branch)
} yield {
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
ApiBranchForHeadCommit(branch, ApiBranchCommit(br.commitId), protection.enabled)
}
JsonFormat(apiBranchForCommits)
}
})
}

View File

@@ -1,26 +1,41 @@
package gitbucket.core.controller.api
import gitbucket.core.api.{ApiContents, ApiError, CreateAFile, JsonFormat}
import gitbucket.core.api.{ApiCommit, ApiContents, ApiError, CreateAFile, JsonFormat}
import gitbucket.core.controller.ControllerBase
import gitbucket.core.service.{RepositoryCommitFileService, RepositoryService}
import gitbucket.core.util.Directory.getRepositoryDir
import gitbucket.core.util.JGitUtil.{FileInfo, getContentFromId, getFileList}
import gitbucket.core.util.JGitUtil.{CommitInfo, FileInfo, getContentFromId, getFileList}
import gitbucket.core.util._
import gitbucket.core.view.helpers.{isRenderable, renderMarkup}
import gitbucket.core.util.Implicits._
import org.eclipse.jgit.api.Git
import scala.util.Using
trait ApiRepositoryContentsControllerBase extends ControllerBase {
self: ReferrerAuthenticator with WritableUsersAuthenticator with RepositoryCommitFileService =>
/*
* i. Get the README
* https://developer.github.com/v3/repos/contents/#get-the-readme
/**
* i. Get a repository README
* https://docs.github.com/en/rest/reference/repos#get-a-repository-readme
*/
get("/api/v3/repos/:owner/:repository/readme")(referrersOnly { repository =>
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) {
git =>
val refStr = params.getOrElse("ref", repository.repository.defaultBranch)
val files = getFileList(git, refStr, ".", maxFiles = context.settings.repositoryViewer.maxFiles)
files // files should be sorted alphabetically.
.find { file =>
!file.isDirectory && RepositoryService.readmeFiles.contains(file.name.toLowerCase)
} match {
case Some(x) => getContents(repository = repository, path = x.name, refStr = refStr, ignoreCase = true)
case _ => NotFound()
}
}
})
/**
* ii. Get contents
* https://developer.github.com/v3/repos/contents/#get-contents
* https://docs.github.com/en/rest/reference/repos#get-repository-content
*/
get("/api/v3/repos/:owner/:repository/contents")(referrersOnly { repository =>
getContents(repository, ".", params.getOrElse("ref", repository.repository.defaultBranch))
@@ -28,27 +43,38 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
/**
* ii. Get contents
* https://developer.github.com/v3/repos/contents/#get-contents
* https://docs.github.com/en/rest/reference/repos#get-repository-content
*/
get("/api/v3/repos/:owner/:repository/contents/*")(referrersOnly { repository =>
getContents(repository, multiParams("splat").head, params.getOrElse("ref", repository.repository.defaultBranch))
})
private def getContents(repository: RepositoryService.RepositoryInfo, path: String, refStr: String) = {
def getFileInfo(git: Git, revision: String, pathStr: String): Option[FileInfo] = {
val (dirName, fileName) = pathStr.lastIndexOf('/') match {
case -1 =>
(".", pathStr)
case n =>
(pathStr.take(n), pathStr.drop(n + 1))
}
getFileList(git, revision, dirName).find(f => f.name.equals(fileName))
private def getFileInfo(git: Git, revision: String, pathStr: String, ignoreCase: Boolean): Option[FileInfo] = {
val (dirName, fileName) = pathStr.lastIndexOf('/') match {
case -1 =>
(".", pathStr)
case n =>
(pathStr.take(n), pathStr.drop(n + 1))
}
if (ignoreCase) {
getFileList(git, revision, dirName, maxFiles = context.settings.repositoryViewer.maxFiles)
.find(_.name.toLowerCase.equals(fileName.toLowerCase))
} else {
getFileList(git, revision, dirName, maxFiles = context.settings.repositoryViewer.maxFiles)
.find(_.name.equals(fileName))
}
}
private def getContents(
repository: RepositoryService.RepositoryInfo,
path: String,
refStr: String,
ignoreCase: Boolean = false
) = {
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
val fileList = getFileList(git, refStr, path)
val fileList = getFileList(git, refStr, path, maxFiles = context.settings.repositoryViewer.maxFiles)
if (fileList.isEmpty) { // file or NotFound
getFileInfo(git, refStr, path)
getFileInfo(git, refStr, path, ignoreCase)
.flatMap { f =>
val largeFile = params.get("large_file").exists(s => s.equals("true"))
val content = getContentFromId(git, f.id, largeFile)
@@ -100,56 +126,88 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
}
}
}
/*
/**
* iii. Create a file or iv. Update a file
* https://developer.github.com/v3/repos/contents/#create-a-file
* https://developer.github.com/v3/repos/contents/#update-a-file
* https://docs.github.com/en/rest/reference/repos#create-or-update-file-contents
* if sha is presented, update a file else create a file.
* requested #2112
*/
put("/api/v3/repos/:owner/:repository/contents/*")(writableUsersOnly { repository =>
JsonFormat(for {
data <- extractFromJsonBody[CreateAFile]
} yield {
val branch = data.branch.getOrElse(repository.repository.defaultBranch)
val commit = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
revCommit.name
}
val paths = multiParams("splat").head.split("/")
val path = paths.take(paths.size - 1).toList.mkString("/")
if (data.sha.isDefined && data.sha.get != commit) {
ApiError("The blob SHA is not matched.", Some("https://developer.github.com/v3/repos/contents/#update-a-file"))
} else {
val objectId = commitFile(
repository,
branch,
path,
Some(paths.last),
data.sha.map(_ => paths.last),
StringUtil.base64Decode(data.content),
data.message,
commit,
context.loginAccount.get,
data.committer.map(_.name).getOrElse(context.loginAccount.get.fullName),
data.committer.map(_.email).getOrElse(context.loginAccount.get.mailAddress),
context.settings
)
ApiContents("file", paths.last, path, objectId.name, None, None)(RepositoryName(repository))
}
})
context.withLoginAccount {
loginAccount =>
JsonFormat(for {
data <- extractFromJsonBody[CreateAFile]
} yield {
val branch = data.branch.getOrElse(repository.repository.defaultBranch)
val commit = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
revCommit.name
}
val paths = multiParams("splat").head.split("/")
val path = paths.take(paths.size - 1).toList.mkString("/")
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) {
git =>
val fileInfo = getFileInfo(git, commit, path, false)
fileInfo match {
case Some(f) if !data.sha.contains(f.id.getName) =>
ApiError(
"The blob SHA is not matched.",
Some("https://docs.github.com/en/rest/reference/repos#create-or-update-file-contents")
)
case _ =>
commitFile(
repository,
branch,
path,
Some(paths.last),
data.sha.map(_ => paths.last),
StringUtil.base64Decode(data.content),
data.message,
commit,
loginAccount,
data.committer.map(_.name).getOrElse(loginAccount.fullName),
data.committer.map(_.email).getOrElse(loginAccount.mailAddress),
context.settings
) match {
case Left(error) =>
ApiError(s"Failed to commit a file: ${error}", None)
case Right((_, None)) =>
ApiError("Failed to commit a file.", None)
case Right((commitId, Some(blobId))) =>
Map(
"content" -> ApiContents(
"file",
paths.last,
path,
blobId.name,
Some(data.content),
Some("base64")
)(RepositoryName(repository)),
"commit" -> ApiCommit(
git,
RepositoryName(repository),
new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
)
)
}
}
}
})
}
})
/*
* v. Delete a file
* https://developer.github.com/v3/repos/contents/#delete-a-file
* https://docs.github.com/en/rest/reference/repos#delete-a-file
* should be implemented
*/
/*
* vi. Get archive link
* https://developer.github.com/v3/repos/contents/#get-archive-link
* vi. Download a repository archive (tar/zip)
* https://docs.github.com/en/rest/reference/repos#download-a-repository-archive-tar
* https://docs.github.com/en/rest/reference/repos#download-a-repository-archive-zip
*/
}

View File

@@ -8,6 +8,7 @@ import gitbucket.core.util._
import gitbucket.core.util.Implicits._
import gitbucket.core.model.Profile.profile.blockingApi._
import org.eclipse.jgit.api.Git
import org.scalatra.Forbidden
import scala.concurrent.Await
import scala.concurrent.duration.Duration
@@ -15,6 +16,7 @@ import scala.util.Using
trait ApiRepositoryControllerBase extends ControllerBase {
self: RepositoryService
with ApiGitReferenceControllerBase
with RepositoryCreationService
with AccountService
with OwnerAuthenticator
@@ -26,7 +28,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
/**
* i. List your repositories
* https://developer.github.com/v3/repos/#list-your-repositories
* https://docs.github.com/en/rest/reference/repos#list-repositories-for-the-authenticated-user
*/
get("/api/v3/user/repos")(usersOnly {
JsonFormat(getVisibleRepositories(context.loginAccount, Option(context.loginAccount.get.userName)).map { r =>
@@ -36,7 +38,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
/**
* ii. List user repositories
* https://developer.github.com/v3/repos/#list-user-repositories
* https://docs.github.com/en/rest/reference/repos#list-repositories-for-a-user
*/
get("/api/v3/users/:userName/repos") {
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("userName"))).map { r =>
@@ -46,7 +48,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
/**
* iii. List organization repositories
* https://developer.github.com/v3/repos/#list-organization-repositories
* https://docs.github.com/en/rest/reference/repos#list-organization-repositories
*/
get("/api/v3/orgs/:orgName/repos") {
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("orgName"))).map { r =>
@@ -54,21 +56,24 @@ trait ApiRepositoryControllerBase extends ControllerBase {
})
}
/*
/**
* iv. List all public repositories
* https://developer.github.com/v3/repos/#list-all-public-repositories
* Not implemented
* https://docs.github.com/en/rest/reference/repos#list-public-repositories
*/
get("/api/v3/repositories") {
JsonFormat(getPublicRepositories().map { r =>
ApiRepository(r, getAccountByUserName(r.owner).get)
})
}
/*
* v. Create
* https://developer.github.com/v3/repos/#create
* Implemented with two methods (user/orgs)
*/
/**
* Create user repository
* https://developer.github.com/v3/repos/#create
* https://docs.github.com/en/rest/reference/repos#create-a-repository-for-the-authenticated-user
*/
post("/api/v3/user/repos")(usersOnly {
val owner = context.loginAccount.get.userName
@@ -76,7 +81,12 @@ trait ApiRepositoryControllerBase extends ControllerBase {
data <- extractFromJsonBody[CreateARepository] if data.isValid
} yield {
LockUtil.lock(s"${owner}/${data.name}") {
if (getRepository(owner, data.name).isEmpty) {
if (getRepository(owner, data.name).isDefined) {
ApiError(
"A repository with this name already exists on this account",
Some("https://developer.github.com/v3/repos/#create")
)
} else {
val f = createRepository(
context.loginAccount.get,
owner,
@@ -91,11 +101,6 @@ trait ApiRepositoryControllerBase extends ControllerBase {
getRepository(owner, data.name)(session).get
}
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(owner).get)))
} else {
ApiError(
"A repository with this name already exists on this account",
Some("https://developer.github.com/v3/repos/#create")
)
}
}
}) getOrElse NotFound()
@@ -103,15 +108,22 @@ trait ApiRepositoryControllerBase extends ControllerBase {
/**
* Create group repository
* https://developer.github.com/v3/repos/#create
* https://docs.github.com/en/rest/reference/repos#create-an-organization-repository
*/
post("/api/v3/orgs/:org/repos")(managersOnly {
post("/api/v3/orgs/:org/repos")(usersOnly {
val groupName = params("org")
(for {
data <- extractFromJsonBody[CreateARepository] if data.isValid
} yield {
LockUtil.lock(s"${groupName}/${data.name}") {
if (getRepository(groupName, data.name).isEmpty) {
if (getRepository(groupName, data.name).isDefined) {
ApiError(
"A repository with this name already exists for this group",
Some("https://developer.github.com/v3/repos/#create")
)
} else if (!canCreateRepository(groupName, context.loginAccount.get)) {
Forbidden()
} else {
val f = createRepository(
context.loginAccount.get,
groupName,
@@ -125,11 +137,6 @@ trait ApiRepositoryControllerBase extends ControllerBase {
getRepository(groupName, data.name).get
}
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(groupName).get)))
} else {
ApiError(
"A repository with this name already exists for this group",
Some("https://developer.github.com/v3/repos/#create")
)
}
}
}) getOrElse NotFound()
@@ -137,7 +144,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
/*
* vi. Get
* https://developer.github.com/v3/repos/#get
* https://docs.github.com/en/rest/reference/repos#get-a-repository
*/
get("/api/v3/repos/:owner/:repository")(referrersOnly { repository =>
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(repository.owner).get)))
@@ -145,47 +152,54 @@ trait ApiRepositoryControllerBase extends ControllerBase {
/*
* vii. Edit
* https://developer.github.com/v3/repos/#edit
* https://docs.github.com/en/rest/reference/repos#update-a-repository
*/
/*
* viii. List all topics for a repository
* https://developer.github.com/v3/repos/#list-all-topics-for-a-repository
* https://docs.github.com/en/rest/reference/repos#get-all-repository-topics
*/
/*
* ix. Replace all topics for a repository
* https://developer.github.com/v3/repos/#replace-all-topics-for-a-repository
* https://docs.github.com/en/rest/reference/repos#replace-all-repository-topics
*/
/*
* x. List contributors
* https://developer.github.com/v3/repos/#list-contributors
* https://docs.github.com/en/rest/reference/repos#list-repository-contributors
*/
/*
* xi. List languages
* https://developer.github.com/v3/repos/#list-languages
* https://docs.github.com/en/rest/reference/repos#list-repository-languages
*/
/*
* xii. List teams
* https://developer.github.com/v3/repos/#list-teams
* https://docs.github.com/en/rest/reference/repos#list-repository-teams
*/
/*
* xiii. List tags
* https://developer.github.com/v3/repos/#list-tags
* xiii. List repository tags
* https://docs.github.com/en/rest/reference/repos#list-repository-tags
*/
get("/api/v3/repos/:owner/:repository/tags")(referrersOnly { repository =>
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
JsonFormat(
self.getRef("tags", repository)
)
}
})
/*
* xiv. Delete a repository
* https://developer.github.com/v3/repos/#delete-a-repository
* https://docs.github.com/en/rest/reference/repos#delete-a-repository
*/
/*
* xv. Transfer a repository
* https://developer.github.com/v3/repos/#transfer-a-repository
* https://docs.github.com/en/rest/reference/repos#transfer-a-repository
*/
/**

View File

@@ -47,7 +47,7 @@ trait ApiRepositoryStatusControllerBase extends ControllerBase {
ref <- params.get("ref")
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
} yield {
JsonFormat(getCommitStatuesWithCreator(repository.owner, repository.name, sha).map {
JsonFormat(getCommitStatusesWithCreator(repository.owner, repository.name, sha).map {
case (status, creator) =>
ApiCommitStatus(status, ApiUser(creator))
})
@@ -73,7 +73,7 @@ trait ApiRepositoryStatusControllerBase extends ControllerBase {
owner <- getAccountByUserName(repository.owner)
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
} yield {
val statuses = getCommitStatuesWithCreator(repository.owner, repository.name, sha)
val statuses = getCommitStatusesWithCreator(repository.owner, repository.name, sha)
JsonFormat(ApiCombinedCommitStatus(sha, statuses, ApiRepository(repository, owner)))
}) getOrElse NotFound()
})

View File

@@ -0,0 +1,120 @@
package gitbucket.core.controller.api
import gitbucket.core.api._
import gitbucket.core.controller.ControllerBase
import gitbucket.core.model.{WebHook, WebHookContentType}
import gitbucket.core.service.{RepositoryService, WebHookService}
import gitbucket.core.util._
import gitbucket.core.util.Implicits._
import org.scalatra.NoContent
trait ApiRepositoryWebhookControllerBase extends ControllerBase {
self: RepositoryService with WebHookService with ReferrerAuthenticator with WritableUsersAuthenticator =>
/*
* i. List repository webhooks
* https://docs.github.com/en/rest/reference/repos#list-repository-webhooks
*/
get("/api/v3/repos/:owner/:repository/hooks")(referrersOnly { repository =>
val apiWebhooks = for {
(hook, events) <- getWebHooks(repository.owner, repository.name)
} yield {
ApiWebhook("Repository", hook, events)
}
JsonFormat(apiWebhooks)
})
/*
* ii. Create a repository webhook
* https://docs.github.com/en/rest/reference/repos#create-a-repository-webhook
*/
post("/api/v3/repos/:owner/:repository/hooks")(writableUsersOnly { repository =>
(for {
data <- extractFromJsonBody[CreateARepositoryWebhook] if data.isValid
ctype = if (data.config.content_type == "form") WebHookContentType.FORM else WebHookContentType.JSON
events = data.events.map(p => WebHook.Event.valueOf(p)).toSet
} yield {
addWebHook(
repository.owner,
repository.name,
data.config.url,
events,
ctype,
data.config.secret
)
getWebHook(repository.owner, repository.name, data.config.url) match {
case Some(createdHook) => JsonFormat(ApiWebhook("Repository", createdHook._1, createdHook._2))
case _ =>
}
}) getOrElse NotFound()
})
/*
* iii. Get a repository webhook
* https://docs.github.com/en/rest/reference/repos#get-a-repository-webhook
*/
get("/api/v3/repos/:owner/:repository/hooks/:id")(referrersOnly { repository =>
val hookId = params("id").toInt
getWebHookById(hookId) match {
case Some(hook) => JsonFormat(ApiWebhook("Repository", hook._1, hook._2))
case _ => NotFound()
}
})
/*
* iv. Update a repository webhook
* https://docs.github.com/en/rest/reference/repos#update-a-repository-webhook
*/
patch("/api/v3/repos/:owner/:repository/hooks/:id")(writableUsersOnly { repository =>
val hookId = params("id").toInt
(for {
data <- extractFromJsonBody[UpdateARepositoryWebhook] if data.isValid
ctype = data.config.content_type match {
case "json" => WebHookContentType.JSON
case _ => WebHookContentType.FORM
}
} yield {
val events = (data.events ++ data.add_events)
.filterNot(p => data.remove_events.contains(p))
.map(p => WebHook.Event.valueOf(p))
.toSet
updateWebHookByApi(
hookId,
repository.owner,
repository.name,
data.config.url,
events,
ctype,
data.config.secret
)
getWebHookById(hookId) match {
case Some(updatedHook) => JsonFormat(ApiWebhook("Repository", updatedHook._1, updatedHook._2))
case _ =>
}
}) getOrElse NotFound()
})
/*
* v. Delete a repository webhook
* https://docs.github.com/en/rest/reference/repos#delete-a-repository-webhook
*/
delete("/api/v3/repos/:owner/:repository/hooks/:id")(writableUsersOnly { repository =>
val hookId = params("id").toInt
getWebHookById(hookId) match {
case Some(_) =>
deleteWebHookById(params("id").toInt)
NoContent()
case _ => NotFound()
}
})
/*
* vi. Ping a repository webhook
* https://docs.github.com/en/rest/reference/repos#ping-a-repository-webhook
*/
/*
* vi. Test the push repository webhook
* https://docs.github.com/en/rest/reference/repos#test-the-push-repository-webhook
*/
}

View File

@@ -104,7 +104,7 @@ trait ApiUserControllerBase extends ControllerBase {
*/
delete("/api/v3/users/:userName/suspended")(adminOnly {
val userName = params("userName")
getAccountByUserName(userName) match {
getAccountByUserName(userName, true) match {
case Some(targetAccount) =>
updateAccount(targetAccount.copy(isRemoved = false))
NoContent()

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).<>(AccessToken.tupled, AccessToken.unapply)
}
}
case class AccessToken(

View File

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

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).<>(AccountExtraMailAddress.tupled, AccountExtraMailAddress.unapply)
}
}

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).<>(AccountFederation.tupled, AccountFederation.unapply)
def byPrimaryKey(issuer: String, subject: String): Rep[Boolean] =
(this.issuer === issuer.bind) && (this.subject === subject.bind)

View File

@@ -0,0 +1,21 @@
package gitbucket.core.model
trait AccountPreferenceComponent { self: Profile =>
import profile.api._
lazy val AccountPreferences = TableQuery[AccountPreferences]
class AccountPreferences(tag: Tag) extends Table[AccountPreference](tag, "ACCOUNT_PREFERENCE") {
val userName = column[String]("USER_NAME", O PrimaryKey)
val highlighterTheme = column[String]("HIGHLIGHTER_THEME")
def * =
(userName, highlighterTheme).<>(AccountPreference.tupled, AccountPreference.unapply)
def byPrimaryKey(userName: String): Rep[Boolean] = this.userName === userName.bind
}
}
case class AccountPreference(
userName: String,
highlighterTheme: String = "prettify"
)

View File

@@ -3,7 +3,7 @@ package gitbucket.core.model
trait AccountWebHookComponent extends TemplateComponent { self: Profile =>
import profile.api._
private implicit val whContentTypeColumnType =
private implicit val whContentTypeColumnType: BaseColumnType[WebHookContentType] =
MappedColumnType.base[WebHookContentType, String](whct => whct.code, code => WebHookContentType.valueOf(code))
lazy val AccountWebHooks = TableQuery[AccountWebHooks]
@@ -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).<>((AccountWebHook.apply _).tupled, AccountWebHook.unapply)
def byPrimaryKey(userName: String, url: String) = (this.userName === userName.bind) && (this.url === url.bind)
}

View File

@@ -1,8 +1,6 @@
package gitbucket.core.model
trait AccountWebHookEventComponent extends TemplateComponent {
self: Profile =>
trait AccountWebHookEventComponent extends TemplateComponent { self: Profile =>
import profile.api._
import gitbucket.core.model.Profile.AccountWebHooks
@@ -14,7 +12,7 @@ trait AccountWebHookEventComponent extends TemplateComponent {
val url = column[String]("URL")
val event = column[WebHook.Event]("EVENT")
def * = (userName, url, event) <> ((AccountWebHookEvent.apply _).tupled, AccountWebHookEvent.unapply)
def * = (userName, url, event).<>((AccountWebHookEvent.apply _).tupled, AccountWebHookEvent.unapply)
def byAccountWebHook(userName: String, url: String) = (this.userName === userName.bind) && (this.url === url.bind)

View File

@@ -1,5 +1,9 @@
package gitbucket.core.model
/**
* ActivityComponent has been deprecated, but keep it for binary compatibility.
*/
@deprecated("ActivityComponent has been deprecated, but keep it for binary compatibility.", "4.34.0")
trait ActivityComponent extends TemplateComponent { self: Profile =>
import profile.api._
import self._
@@ -7,14 +11,7 @@ trait ActivityComponent extends TemplateComponent { self: Profile =>
lazy val Activities = TableQuery[Activities]
class Activities(tag: Tag) extends Table[Activity](tag, "ACTIVITY") with BasicTemplate {
val activityId = column[Int]("ACTIVITY_ID", O AutoInc)
val activityUserName = column[String]("ACTIVITY_USER_NAME")
val activityType = column[String]("ACTIVITY_TYPE")
val message = column[String]("MESSAGE")
val additionalInfo = column[String]("ADDITIONAL_INFO")
val activityDate = column[java.util.Date]("ACTIVITY_DATE")
def * =
(userName, repositoryName, activityUserName, activityType, message, additionalInfo.?, activityDate, activityId) <> (Activity.tupled, Activity.unapply)
def * = ???
}
}
@@ -26,5 +23,5 @@ case class Activity(
message: String,
additionalInfo: Option[String],
activityDate: java.util.Date,
activityId: Int = 0
activityId: String
)

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).<>(Collaborator.tupled, Collaborator.unapply)
def byPrimaryKey(owner: String, repository: String, collaborator: String) =
byRepository(owner, repository) && (collaboratorName === collaborator.bind)

View File

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

View File

@@ -4,7 +4,8 @@ trait CommitStatusComponent extends TemplateComponent { self: Profile =>
import profile.api._
import self._
implicit val commitStateColumnType = MappedColumnType.base[CommitState, String](b => b.name, i => CommitState(i))
implicit val commitStateColumnType: BaseColumnType[CommitState] =
MappedColumnType.base[CommitState, String](b => b.name, i => CommitState(i))
lazy val CommitStatuses = TableQuery[CommitStatuses]
class CommitStatuses(tag: Tag) extends Table[CommitStatus](tag, "COMMIT_STATUS") with CommitTemplate {
@@ -29,7 +30,7 @@ trait CommitStatusComponent extends TemplateComponent { self: Profile =>
creator,
registeredDate,
updatedDate
) <> ((CommitStatus.apply _).tupled, CommitStatus.unapply)
).<>((CommitStatus.apply _).tupled, CommitStatus.unapply)
def byPrimaryKey(id: Int) = commitStatusId === id.bind
}
}
@@ -77,7 +78,7 @@ object CommitState {
val values: Vector[CommitState] = Vector(PENDING, SUCCESS, ERROR, FAILURE)
private val map: Map[String, CommitState] = values.map(enum => enum.name -> enum).toMap
private val map: Map[String, CommitState] = values.map(e => e.name -> e).toMap
def apply(name: String): CommitState = map(name)

View File

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

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