Compare commits

...

274 Commits

Author SHA1 Message Date
Naoki Takezoe
9cc3a0d36e Optimize Get-Contents API 2021-11-05 20:21:39 +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
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
154 changed files with 3715 additions and 2719 deletions

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

View File

@@ -5,13 +5,14 @@ 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
- name: Cache
uses: actions/cache@v2
uses: actions/cache@v2.1.6
env:
cache-name: cache-sbt-libs
with:
@@ -21,11 +22,14 @@ jobs:
~/.cache/coursier/v1
key: build-${{ env.cache-name }}-${{ hashFiles('build.sbt') }}
- name: Set up JDK
uses: actions/setup-java@v1
uses: actions/setup-java@v2
with:
java-version: ${{ matrix.java }}
distribution: adopt
- name: Run tests
run: sbt scalafmtSbtCheck scalafmtCheck test:scalafmtCheck test
- name: Scala 3
run: sbt '++ 3.0.1!' update # TODO
- name: Build executable
run: sbt executable
- name: Upload artifacts

7
.scala-steward.conf Normal file
View File

@@ -0,0 +1,7 @@
updates.limit = 3
updates.includeScala = true
updates.pin = [
{ groupId = "org.eclipse.jetty", version = "9." }
]

View File

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

View File

@@ -8,6 +8,8 @@ GitBucket is a Git web platform powered by Scala offering:
- High extensibility by plugins
- API compatibility with GitHub
![GitBucket](https://gitbucket.github.io/img/screenshots/screenshot-repository_viewer.png)
You can try an [online demo](https://gitbucket.herokuapp.com/) *(ID: root / Pass: root)* of GitBucket, and also get the latest information at [GitBucket News](https://gitbucket.github.io/gitbucket-news/).
Features
@@ -22,8 +24,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.
@@ -48,24 +48,31 @@ 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.34.x
What's New in 4.36.x
-------------
### 4.35.0 - 25 Dec 2020
### 4.36.2 - 16 Aug 2021
- Escape user name in avatar image tag
- 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.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
See the [change log](CHANGELOG.md) for all of the updates.

120
build.sbt
View File

@@ -1,23 +1,21 @@
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.35.0"
val ScalatraVersion = "2.7.0"
val JettyVersion = "9.4.32.v20200930"
val JgitVersion = "5.9.0.202009080501-r"
val GitBucketVersion = "4.36.2"
val ScalatraVersion = "2.8.2"
val JettyVersion = "9.4.44.v20210927"
val JgitVersion = "5.13.0.202109080827-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.7"
scalafmtOnCompile := true
@@ -33,64 +31,80 @@ resolvers ++= Seq(
libraryDependencies ++= Seq(
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % JgitVersion,
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % JgitVersion,
"org.scalatra" %% "scalatra" % ScalatraVersion,
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
"org.scalatra" %% "scalatra-forms" % ScalatraVersion,
"org.json4s" %% "json4s-jackson" % "3.6.10",
"commons-io" % "commons-io" % "2.8.0",
"org.scalatra" %% "scalatra" % ScalatraVersion cross CrossVersion.for3Use2_13,
"org.scalatra" %% "scalatra-json" % ScalatraVersion cross CrossVersion.for3Use2_13,
"org.scalatra" %% "scalatra-forms" % ScalatraVersion cross CrossVersion.for3Use2_13,
"org.json4s" %% "json4s-jackson" % "4.0.3" cross CrossVersion.for3Use2_13,
"commons-io" % "commons-io" % "2.11.0",
"io.github.gitbucket" % "solidbase" % "1.0.3",
"io.github.gitbucket" % "markedj" % "1.0.16",
"org.apache.commons" % "commons-compress" % "1.20",
"org.apache.commons" % "commons-compress" % "1.21",
"org.apache.commons" % "commons-email" % "1.5",
"commons-net" % "commons-net" % "3.7",
"org.apache.httpcomponents" % "httpclient" % "4.5.12",
"commons-net" % "commons-net" % "3.8.0",
"org.apache.httpcomponents" % "httpclient" % "4.5.13",
"org.apache.sshd" % "apache-sshd" % "2.1.0" exclude ("org.slf4j", "slf4j-jdk14") exclude ("org.apache.sshd", "sshd-mina") exclude ("org.apache.sshd", "sshd-netty"),
"org.apache.tika" % "tika-core" % "1.24.1",
"com.github.takezoe" %% "blocking-slick-32" % "0.0.12",
"org.apache.tika" % "tika-core" % "2.1.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.7.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",
"org.mariadb.jdbc" % "mariadb-java-client" % "2.7.4",
"org.postgresql" % "postgresql" % "42.3.1",
"ch.qos.logback" % "logback-classic" % "1.2.6",
"com.zaxxer" % "HikariCP" % "4.0.3" exclude ("org.slf4j", "slf4j-api"),
"com.typesafe" % "config" % "1.4.1",
"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",
"net.coobird" % "thumbnailator" % "0.4.12",
"io.github.java-diff-utils" % "java-diff-utils" % "4.11",
"org.cache2k" % "cache2k-all" % "1.6.0.Final",
"net.coobird" % "thumbnailator" % "0.4.14",
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
"com.nimbusds" % "oauth2-oidc-sdk" % "5.64.4",
"com.nimbusds" % "oauth2-oidc-sdk" % "9.19",
"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.0.0" % "test",
"com.dimafeng" %% "testcontainers-scala" % "0.39.11" % "test",
"org.testcontainers" % "mysql" % "1.16.2" % "test",
"org.testcontainers" % "postgresql" % "1.16.2" % "test",
"net.i2p.crypto" % "eddsa" % "0.3.0",
"is.tagomor.woothee" % "woothee-java" % "1.11.0",
"org.ec4j.core" % "ec4j-core" % "0.0.3",
"org.kohsuke" % "github-api" % "1.116" % "test"
"org.ec4j.core" % "ec4j-core" % "0.3.0",
"org.kohsuke" % "github-api" % "1.135" % "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,9 +136,9 @@ libraryDependencies ++= Seq(
)
// Run package task before test to generate target/webapp for integration test
test in Test := {
Test / test := {
_root_.sbt.Keys.`package`.value
(test in Test).value
(Test / test).value
}
val executableKey = TaskKey[File]("executable")
@@ -155,7 +169,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)
@@ -186,7 +200,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(
@@ -258,12 +272,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(
@@ -276,3 +285,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

View File

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

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.3")
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.1")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "1.1.0")
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.4")
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2")
addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.2")
addDependencyTreePlugin

View File

@@ -1,51 +1,100 @@
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");
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;
@@ -67,38 +116,69 @@ public class JettyLauncher {
contextPath = "/" + contextPath;
}
if(host != null) {
address = new InetSocketAddress(host, getPort(port));
} else {
address = new InetSocketAddress(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));
}
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 +192,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 +216,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 +253,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.9.0
gist:4.20.0
notifications:1.10.0
gist:4.21.0
emoji:4.6.0
pages:1.9.0
pages:1.10.0

View File

@@ -1,17 +1,35 @@
<?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 tableName="WEB_HOOK" constraintName="IDX_WEB_HOOK_PK"/>
<addColumn tableName="WEB_HOOK">
<column name="HOOK_ID" type="int" nullable="false" unique="true"/>
</addColumn>
<addPrimaryKey constraintName="IDX_WEB_HOOK_PK" tableName="WEB_HOOK" columnNames="USER_NAME, REPOSITORY_NAME, URL, HOOK_ID"/>
<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"/>
<addAutoIncrement columnName="HOOK_ID" columnDataType="int" tableName="WEB_HOOK"/>
<!--================================================================================================-->
<!-- 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"/>

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

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

@@ -3,7 +3,6 @@ package gitbucket.core
import java.io.FileOutputStream
import java.nio.charset.StandardCharsets
import java.sql.Connection
import java.util
import java.util.UUID
import gitbucket.core.model.Activity
@@ -12,7 +11,7 @@ import gitbucket.core.util.JDBCUtil
import io.github.gitbucket.solidbase.Solidbase
import io.github.gitbucket.solidbase.migration.{LiquibaseMigration, Migration, SqlMigration}
import io.github.gitbucket.solidbase.model.{Module, Version}
import org.json4s.NoTypeHints
import org.json4s.{Formats, NoTypeHints}
import org.json4s.jackson.Serialization
import org.json4s.jackson.Serialization.write
@@ -84,8 +83,8 @@ object GitBucketCoreModule
new Version(
"4.34.0",
new Migration() {
override def migrate(moduleId: String, version: String, context: util.Map[String, AnyRef]): Unit = {
implicit val formats = Serialization.formats(NoTypeHints)
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]
@@ -115,4 +114,10 @@ object GitBucketCoreModule
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")
)

View File

@@ -66,7 +66,7 @@ object ApiBranchProtection {
}
}
implicit val enforcementLevelSerializer = new CustomSerializer[EnforcementLevel](
implicit val enforcementLevelSerializer: CustomSerializer[EnforcementLevel] = new CustomSerializer[EnforcementLevel](
format =>
(
{

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

@@ -17,7 +17,8 @@ case class ApiIssue(
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
@@ -43,7 +44,8 @@ object ApiIssue {
repositoryName: RepositoryName,
user: ApiUser,
assignee: Option[ApiUser],
labels: List[ApiLabel]
labels: List[ApiLabel],
milestone: Option[ApiMilestone]
): ApiIssue =
ApiIssue(
number = issue.issueId,
@@ -51,6 +53,7 @@ object ApiIssue {
user = user,
assignee = assignee,
labels = labels,
milestone = milestone,
state = if (issue.closed) { "closed" } else { "open" },
body = issue.content.getOrElse(""),
created_at = issue.registeredDate,

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

@@ -1,8 +1,6 @@
package gitbucket.core.api
import gitbucket.core.model.Profile.{RepositoryWebHookEvents, RepositoryWebHooks}
import gitbucket.core.model.{RepositoryWebHook, WebHook}
import gitbucket.core.util.RepositoryName
/**
* https://docs.github.com/en/rest/reference/repos#webhooks

View File

@@ -86,7 +86,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
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(
@@ -98,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(
@@ -434,7 +434,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))
}
@@ -629,7 +629,9 @@ trait AccountControllerBase extends AccountManagementControllerBase {
}
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 =>
@@ -650,22 +652,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)
@@ -673,28 +673,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)
@@ -703,87 +700,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.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.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.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.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

@@ -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._
@@ -34,45 +35,63 @@ trait DashboardControllerBase extends ControllerBase {
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.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) = {
@@ -85,10 +104,10 @@ 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)
@@ -115,11 +134,11 @@ trait DashboardControllerBase extends ControllerBase {
)
}
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)

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

View File

@@ -1,9 +1,9 @@
package gitbucket.core.controller
import gitbucket.core.issues.html
import gitbucket.core.model.Account
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
@@ -95,212 +95,224 @@ 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),
isIssueCommentManageable(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),
getAssignableUserNames(repository.owner, repository.name),
getMilestonesWithIssueCount(repository.owner, repository.name),
getPriorities(repository.owner, repository.name),
getLabels(repository.owner, repository.name),
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),
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
)
redirect(s"/${issue.userName}/${issue.repositoryName}/issues/${issue.issueId}")
} else Unauthorized()
context.withLoginAccount {
loginAccount =>
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(",")),
loginAccount
)
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)) {
context.withLoginAccount {
loginAccount =>
getComment(repository.owner, repository.name, params("id")).map { comment =>
if (isEditableContent(repository.owner, repository.name, comment.commentedUserName, loginAccount)) {
updateComment(comment.issueId, comment.commentId, form.content)
redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}")
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 (isDeletableComment(owner, name, comment.commentedUserName)) {
Ok(deleteComment(repository.owner, repository.name, 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 =>
@@ -310,17 +322,15 @@ 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 =>
@@ -353,26 +363,27 @@ trait IssuesControllerBase extends ControllerBase {
})
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()
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()
}
})
@@ -390,29 +401,26 @@ trait IssuesControllerBase extends ControllerBase {
})
post("/:owner/:repository/issues/batchedit/assign")(writableUsersOnly { repository =>
defining(assignedUserName("value")) { value =>
executeBatch(repository) {
updateAssignedUserName(repository.owner, repository.name, _, value, true)
}
if (params("uri").nonEmpty) {
redirect(params("uri"))
}
val value = assignedUserName("value")
executeBatch(repository) {
updateAssignedUserName(repository.owner, repository.name, _, value, 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)
}
})
@@ -464,48 +472,51 @@ trait IssuesControllerBase extends ControllerBase {
}
private def searchIssues(repository: RepositoryService.RepositoryInfo) = {
defining(repository.owner, repository.name) {
case (owner, repoName) =>
val page = IssueSearchCondition.page(request)
// retrieve search condition
val condition = IssueSearchCondition(request)
// search issues
val issues =
searchIssue(condition, IssueSearchOption.Issues, (page - 1) * IssueLimit, IssueLimit, owner -> repoName)
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
)
html.list(
"issues",
issues.map(issue => (issue, None)),
page,
getAssignableUserNames(owner, repoName),
getMilestones(owner, repoName),
getPriorities(owner, repoName),
getLabels(owner, repoName),
countIssue(condition.copy(state = "open"), IssueSearchOption.Issues, owner -> repoName),
countIssue(condition.copy(state = "closed"), IssueSearchOption.Issues, 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)(
private def isDeletableComment(owner: String, repository: String, author: String, loginAccount: Account)(
implicit context: Context
): Boolean = {
hasOwnerRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
hasOwnerRole(owner, repository, context.loginAccount) || author == loginAccount.userName
}
}

View File

@@ -32,6 +32,7 @@ trait PreProcessControllerBase extends ControllerBase {
get(!context.settings.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

@@ -8,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._
@@ -58,7 +57,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))),
@@ -111,24 +110,29 @@ 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),
getAssignableUserNames(repository.owner, repository.name),
getMilestonesWithIssueCount(repository.owner, repository.name),
getPriorities(repository.owner, repository.name),
getLabels(repository.owner, repository.name),
isEditable(repository),
isManageable(repository),
hasDeveloperRole(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount),
@@ -162,18 +166,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
@@ -185,19 +200,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
)
@@ -208,39 +228,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,
commitStatuses = getCommitStatuses(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
)
@@ -357,23 +373,22 @@ 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 =>
@@ -475,8 +490,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,
@@ -543,15 +557,14 @@ 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,
@@ -570,14 +583,14 @@ 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) {
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)
@@ -586,7 +599,7 @@ trait PullRequestsControllerBase extends ControllerBase {
}
}
redirect(s"/${owner}/${name}/pull/${issueId}")
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
}
})
@@ -633,44 +646,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)
// search issues
val issues = searchIssue(
condition,
IssueSearchOption.PullRequests,
(page - 1) * PullRequestLimit,
PullRequestLimit,
owner -> repoName
)
// commit status
val status = issues.map { issue =>
issue.commitId.flatMap { commitId =>
getCommitStatusWithSummary(owner, repoName, commitId)
}
}
gitbucket.core.issues.html.list(
"pulls",
issues.zip(status),
page,
getAssignableUserNames(owner, repoName),
getMilestones(owner, repoName),
getPriorities(owner, repoName),
getLabels(owner, repoName),
countIssue(condition.copy(state = "open"), IssueSearchOption.PullRequests, owner -> repoName),
countIssue(condition.copy(state = "closed"), IssueSearchOption.PullRequests, 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

@@ -63,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(
@@ -77,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)
@@ -93,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
@@ -105,33 +105,35 @@ 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)
}
val releaseInfo = ReleaseInfo(repository.owner, repository.name, loginAccount.userName, form.name, tagName)
recordActivity(releaseInfo)
redirect(s"/${repository.owner}/${repository.name}/releases/${tagName}")
})
get("/:owner/:repository/changelog/*...*")(writableUsersOnly { repository =>
@@ -150,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 {
@@ -168,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))
@@ -224,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,6 +1,6 @@
package gitbucket.core.controller
import java.time.{LocalDateTime, ZoneId, ZoneOffset}
import java.time.{LocalDateTime, ZoneOffset}
import java.util.Date
import gitbucket.core.settings.html
@@ -57,7 +57,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
externalWikiUrl: Option[String],
allowFork: Boolean,
mergeOptions: Seq[String],
defaultMergeOption: String
defaultMergeOption: String,
safeMode: Boolean
)
val optionsForm = mapping(
@@ -69,7 +70,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.")
@@ -150,7 +152,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")
@@ -342,7 +345,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
.map(
res =>
Map(
"status" -> res.getStatusLine(),
"status" -> res.getStatusLine.getStatusCode,
"body" -> EntityUtils.toString(res.getEntity()),
"headers" -> _headers(res.getAllHeaders())
)
@@ -385,54 +388,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) {
// 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,
context.loginAccount.get.userName,
repository.name
)
recordActivity(renameInfo)
}
redirect(s"/${repository.owner}/${form.repositoryName}")
} else Forbidden()
context.withLoginAccount {
loginAccount =>
if (context.settings.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) {
// 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,
context.loginAccount.get.userName,
repository.owner
)
recordActivity(renameInfo)
}
redirect(s"/${form.newOwner}/${repository.name}")
} else Forbidden()
context.withLoginAccount {
loginAccount =>
if (context.settings.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.repositoryOperation.delete || loginAccount.isAdmin) {
// Delete the repository and related files
deleteRepository(repository.repository)
redirect(s"/${repository.owner}")
} else Forbidden()
}
})
/**

View File

@@ -9,7 +9,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
@@ -187,7 +186,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(
@@ -201,7 +200,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(
@@ -362,8 +361,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) =>
@@ -463,31 +462,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)
@@ -495,9 +491,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)
@@ -505,13 +501,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 {
@@ -559,12 +554,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

@@ -136,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 =>
@@ -172,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,
@@ -201,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 =>
@@ -212,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,
@@ -242,27 +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 =>
val deleteWikiInfo = DeleteWikiInfo(
repository.owner,
repository.name,
loginAccount.userName,
pageName
)
recordActivity(deleteWikiInfo)
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

@@ -4,7 +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, NoContent}
import org.scalatra.ActionResult
trait ApiIssueCommentControllerBase extends ControllerBase {
self: AccountService

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 {
@@ -47,7 +47,8 @@ trait ApiIssueControllerBase extends ControllerBase {
user = ApiUser(issueUser),
assignee = assignedUser.map(ApiUser(_)),
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
.map(ApiLabel(_, RepositoryName(repository)))
.map(ApiLabel(_, RepositoryName(repository))),
issue.milestoneId.flatMap { getApiMilestone(repository, _) }
)
})
})
@@ -69,7 +70,8 @@ trait ApiIssueControllerBase extends ControllerBase {
RepositoryName(repository),
ApiUser(openedUser),
issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)),
getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository)))
getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository))),
issue.milestoneId.flatMap { getApiMilestone(repository, _) }
)
)
}) getOrElse NotFound()
@@ -103,7 +105,8 @@ trait ApiIssueControllerBase extends ControllerBase {
ApiUser(loginAccount),
issue.assignedUserName.flatMap(getAccountByUserName(_)).map(ApiUser(_)),
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

@@ -102,17 +102,4 @@ trait ApiIssueMilestoneControllerBase extends ControllerBase {
NoContent()
})
private def getApiMilestone(repository: RepositoryInfo, milestoneId: Int): Option[ApiMilestone] = {
getMilestonesWithIssueCount(repository.owner, repository.name)
.find(p => p._1.milestoneId == milestoneId)
.map(
milestoneWithIssue =>
ApiMilestone(
repository.repository,
milestoneWithIssue._1,
milestoneWithIssue._2,
milestoneWithIssue._3
)
)
}
}

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

@@ -230,9 +230,9 @@ trait ApiPullRequestControllerBase extends ControllerBase {
if (checkConflict(repository.owner, repository.name, pullReq.branch, issueId).isDefined) {
NoContent
} else {
NotFound
NotFound()
}
}).getOrElse(NotFound)
}).getOrElse(NotFound())
})
/*

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

@@ -40,7 +40,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
)
})
}

View File

@@ -1,10 +1,9 @@
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.plugin.PluginRegistry
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, getSummaryMessage}
import gitbucket.core.util._
import gitbucket.core.view.helpers.{isRenderable, renderMarkup}
import gitbucket.core.util.Implicits._
@@ -36,7 +35,7 @@ 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, ".", params.getOrElse("ref", repository.repository.defaultBranch))
@@ -44,47 +43,50 @@ 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 getFileInfo(git: Git, revision: String, pathStr: String): Option[FileInfo] = {
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(revision))
getPathObjectId(git, pathStr, revCommit).map { objectId =>
FileInfo(
id = objectId,
isDirectory = false,
name = pathStr.split("/").last,
path = pathStr.split("/").dropRight(1).mkString("/"),
message = getSummaryMessage(revCommit.getFullMessage, revCommit.getShortMessage),
commitId = revCommit.getName,
time = revCommit.getAuthorIdent.getWhen,
author = revCommit.getAuthorIdent.getName,
mailAddress = revCommit.getAuthorIdent.getEmailAddress,
linkUrl = None
)
}
}
private def getContents(
repository: RepositoryService.RepositoryInfo,
path: String,
refStr: String,
ignoreCase: Boolean = false
) = {
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))
}
}
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
val fileList = getFileList(git, refStr, path, maxFiles = context.settings.repositoryViewer.maxFiles)
if (fileList.isEmpty) { // file or NotFound
getFileInfo(git, refStr, path, ignoreCase)
.flatMap { f =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(refStr))
getPathObjectId(git, path, revCommit)
.flatMap { objectId =>
val largeFile = params.get("large_file").exists(s => s.equals("true"))
val content = getContentFromId(git, f.id, largeFile)
val content = getContentFromId(git, objectId, largeFile)
request.getHeader("Accept") match {
case "application/vnd.github.v3.raw" => {
contentType = "application/vnd.github.v3.raw"
content
}
case "application/vnd.github.v3.html" if isRenderable(f.name) => {
case "application/vnd.github.v3.html" if isRenderable(path) => {
contentType = "application/vnd.github.v3.html"
content.map { c =>
List(
@@ -115,11 +117,12 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
}
}
case _ =>
Some(JsonFormat(ApiContents(f, RepositoryName(repository), content)))
getFileInfo(git, refStr, path).map { f =>
JsonFormat(ApiContents(f, RepositoryName(repository), content))
}
}
}
.getOrElse(NotFound())
} else { // directory
JsonFormat(fileList.map { f =>
ApiContents(f, RepositoryName(repository), None)
@@ -127,56 +130,86 @@ 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 =>
getFileInfo(git, commit, path) 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 _ =>
val (commitId, blobId) = 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
)
blobId match {
case None =>
ApiError("Failed to commit a file.", None)
case 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
@@ -26,7 +27,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 +37,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 +47,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 =>
@@ -56,7 +57,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
/**
* iv. List all public repositories
* https://developer.github.com/v3/repos/#list-public-repositories
* https://docs.github.com/en/rest/reference/repos#list-public-repositories
*/
get("/api/v3/repositories") {
JsonFormat(getPublicRepositories().map { r =>
@@ -66,13 +67,12 @@ trait ApiRepositoryControllerBase extends ControllerBase {
/*
* 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
@@ -80,7 +80,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,
@@ -95,11 +100,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()
@@ -107,15 +107,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,
@@ -129,11 +136,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()
@@ -141,7 +143,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)))
@@ -149,32 +151,32 @@ 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
*/
/*
@@ -189,12 +191,12 @@ trait ApiRepositoryControllerBase extends ControllerBase {
/*
* 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

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,15 +10,17 @@ trait Profile {
/**
* java.util.Date Mapped Column Types
*/
implicit val dateColumnType = MappedColumnType.base[java.util.Date, java.sql.Timestamp](
d => new java.sql.Timestamp(d.getTime),
t => new java.util.Date(t.getTime)
)
implicit val dateColumnType: BaseColumnType[java.util.Date] =
MappedColumnType.base[java.util.Date, java.sql.Timestamp](
d => new java.sql.Timestamp(d.getTime),
t => new java.util.Date(t.getTime)
)
/**
* WebHookBase.Event Column Types
*/
implicit val eventColumnType = MappedColumnType.base[WebHook.Event, String](_.name, WebHook.Event.valueOf(_))
implicit val eventColumnType: BaseColumnType[WebHook.Event] =
MappedColumnType.base[WebHook.Event, String](_.name, WebHook.Event.valueOf(_))
/**
* Extends Column to add conditional condition
@@ -45,7 +47,7 @@ trait CoreProfile
with Profile
with AccessTokenComponent
with AccountComponent
with ActivityComponent // ActivityComponent has been deprecated, but keep it for binary compatibility
with ActivityComponent
with CollaboratorComponent
with CommitCommentComponent
with CommitStatusComponent

View File

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

View File

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

View File

@@ -2,9 +2,7 @@ package gitbucket.core.model
import java.util.Date
trait ReleaseAssetComponent extends TemplateComponent {
self: Profile =>
trait ReleaseAssetComponent extends TemplateComponent { self: Profile =>
import profile.api._
import self._
@@ -21,7 +19,8 @@ trait ReleaseAssetComponent extends TemplateComponent {
val updatedDate = column[Date]("UPDATED_DATE")
def * =
(userName, repositoryName, tag, releaseAssetId, fileName, label, size, uploader, registeredDate, updatedDate) <> (ReleaseAsset.tupled, ReleaseAsset.unapply)
(userName, repositoryName, tag, releaseAssetId, fileName, label, size, uploader, registeredDate, updatedDate)
.<>(ReleaseAsset.tupled, ReleaseAsset.unapply)
def byPrimaryKey(owner: String, repository: String, tag: String, fileName: String) =
byTag(owner, repository, tag) && (this.fileName === fileName.bind)
def byTag(owner: String, repository: String, tag: String) =

View File

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

View File

@@ -24,6 +24,7 @@ trait RepositoryComponent extends TemplateComponent { self: Profile =>
val allowFork = column[Boolean]("ALLOW_FORK")
val mergeOptions = column[String]("MERGE_OPTIONS")
val defaultMergeOption = column[String]("DEFAULT_MERGE_OPTION")
val safeMode = column[Boolean]("SAFE_MODE")
def * =
(
@@ -41,47 +42,58 @@ trait RepositoryComponent extends TemplateComponent { self: Profile =>
parentUserName.?,
parentRepositoryName.?
),
(issuesOption, externalIssuesUrl.?, wikiOption, externalWikiUrl.?, allowFork, mergeOptions, defaultMergeOption)
).shaped <> ({
case (repository, options) =>
Repository(
repository._1,
repository._2,
repository._3,
repository._4,
repository._5,
repository._6,
repository._7,
repository._8,
repository._9,
repository._10,
repository._11,
repository._12,
RepositoryOptions.tupled.apply(options)
)
}, { (r: Repository) =>
Some(
(
(
issuesOption,
externalIssuesUrl.?,
wikiOption,
externalWikiUrl.?,
allowFork,
mergeOptions,
defaultMergeOption,
safeMode
)
).shaped.<>(
{
case (repository, options) =>
Repository(
repository._1,
repository._2,
repository._3,
repository._4,
repository._5,
repository._6,
repository._7,
repository._8,
repository._9,
repository._10,
repository._11,
repository._12,
RepositoryOptions.tupled.apply(options)
)
}, { (r: Repository) =>
Some(
(
r.userName,
r.repositoryName,
r.isPrivate,
r.description,
r.defaultBranch,
r.registeredDate,
r.updatedDate,
r.lastActivityDate,
r.originUserName,
r.originRepositoryName,
r.parentUserName,
r.parentRepositoryName
),
(
RepositoryOptions.unapply(r.options).get
(
r.userName,
r.repositoryName,
r.isPrivate,
r.description,
r.defaultBranch,
r.registeredDate,
r.updatedDate,
r.lastActivityDate,
r.originUserName,
r.originRepositoryName,
r.parentUserName,
r.parentRepositoryName
),
(
RepositoryOptions.unapply(r.options).get
)
)
)
)
})
}
)
def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository)
}
@@ -110,5 +122,6 @@ case class RepositoryOptions(
externalWikiUrl: Option[String],
allowFork: Boolean,
mergeOptions: String,
defaultMergeOption: String
defaultMergeOption: String,
safeMode: Boolean
)

View File

@@ -3,7 +3,7 @@ package gitbucket.core.model
trait RepositoryWebHookComponent extends TemplateComponent { self: Profile =>
import profile.api._
implicit val whContentTypeColumnType =
implicit val whContentTypeColumnType: BaseColumnType[WebHookContentType] =
MappedColumnType.base[WebHookContentType, String](whct => whct.code, code => WebHookContentType.valueOf(code))
lazy val RepositoryWebHooks = TableQuery[RepositoryWebHooks]
@@ -14,7 +14,8 @@ trait RepositoryWebHookComponent extends TemplateComponent { self: Profile =>
val token = column[Option[String]]("TOKEN")
val ctype = column[WebHookContentType]("CTYPE")
def * =
(userName, repositoryName, hookId, url, ctype, token) <> ((RepositoryWebHook.apply _).tupled, RepositoryWebHook.unapply)
(userName, repositoryName, hookId, url, ctype, token)
.<>((RepositoryWebHook.apply _).tupled, RepositoryWebHook.unapply)
def byRepositoryUrl(owner: String, repository: String, url: String) =
byRepository(owner, repository) && (this.url === url.bind)

View File

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

View File

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

View File

@@ -8,7 +8,7 @@ object WebHookContentType {
val values: Vector[WebHookContentType] = Vector(JSON, FORM)
private val map: Map[String, WebHookContentType] = values.map(enum => enum.code -> enum).toMap
private val map: Map[String, WebHookContentType] = values.map(e => e.code -> e).toMap
def apply(code: String): WebHookContentType = map(code)

View File

@@ -21,13 +21,13 @@ trait AccessTokenService {
* @return (TokenId, Token)
*/
def generateAccessToken(userName: String, note: String)(implicit s: Session): (Int, String) = {
var token: String = null
var hash: String = null
var token: String = makeAccessTokenString
var hash: String = tokenToHash(token)
do {
while (AccessTokens.filter(_.tokenHash === hash.bind).exists.run) {
token = makeAccessTokenString
hash = tokenToHash(token)
} while (AccessTokens.filter(_.tokenHash === hash.bind).exists.run)
}
val newToken = AccessToken(userName = userName, note = note, tokenHash = hash)
val tokenId = (AccessTokens returning AccessTokens.map(_.accessTokenId)) insert newToken

View File

@@ -47,7 +47,7 @@ trait AccountService {
case _ => None
}
case account if (!account.isGroupAccount && account.password == sha1(password)) => Some(account)
} getOrElse None
}.flatten
}
/**
@@ -105,13 +105,13 @@ trait AccountService {
}
def getAccountByUserName(userName: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] =
Accounts filter (t => (t.userName === userName.bind) && (t.removed === false.bind, !includeRemoved)) firstOption
Accounts filter (t => (t.userName === userName.bind).&&(t.removed === false.bind, !includeRemoved)) firstOption
def getAccountByUserNameIgnoreCase(userName: String, includeRemoved: Boolean = false)(
implicit s: Session
): Option[Account] =
Accounts filter (
t => (t.userName.toLowerCase === userName.toLowerCase.bind) && (t.removed === false.bind, !includeRemoved)
t => (t.userName.toLowerCase === userName.toLowerCase.bind).&&(t.removed === false.bind, !includeRemoved)
) firstOption
def getAccountsByUserNames(userNames: Set[String], knowns: Set[Account], includeRemoved: Boolean = false)(
@@ -123,7 +123,7 @@ trait AccountService {
map
} else {
map ++ Accounts
.filter(t => (t.userName inSetBind needs) && (t.removed === false.bind, !includeRemoved))
.filter(t => (t.userName inSetBind needs).&&(t.removed === false.bind, !includeRemoved))
.list
.map(a => a.userName -> a)
.toMap
@@ -140,15 +140,15 @@ trait AccountService {
(x.map { e =>
e.extraMailAddress.toLowerCase === mailAddress.toLowerCase.bind
}
.getOrElse(false.bind))) && (a.removed === false.bind, !includeRemoved)
.getOrElse(false.bind))).&&(a.removed === false.bind, !includeRemoved)
}
.map { case (a, e) => a } firstOption
def getAllUsers(includeRemoved: Boolean = true, includeGroups: Boolean = true)(implicit s: Session): List[Account] = {
Accounts filter { t =>
(1.bind === 1.bind) &&
(t.groupAccount === false.bind, !includeGroups) &&
(t.removed === false.bind, !includeRemoved)
(1.bind === 1.bind)
.&&(t.groupAccount === false.bind, !includeGroups)
.&&(t.removed === false.bind, !includeRemoved)
} sortBy (_.userName) list
}

View File

@@ -1,8 +1,6 @@
package gitbucket.core.service
import gitbucket.core.model.Activity
import gitbucket.core.util.JGitUtil
import gitbucket.core.model.Profile._
import gitbucket.core.util.Directory._
import org.json4s._
import org.json4s.jackson.Serialization
@@ -11,10 +9,8 @@ import org.json4s.jackson.Serialization.{read, write}
import scala.util.Using
import java.io.FileOutputStream
import java.nio.charset.StandardCharsets
import java.util.UUID
import gitbucket.core.controller.Context
import gitbucket.core.model.activity.BaseActivityInfo
import org.apache.commons.io.input.ReversedLinesFileReader
import scala.collection.mutable.ListBuffer
@@ -22,7 +18,7 @@ import scala.collection.mutable.ListBuffer
trait ActivityService {
self: RequestCache =>
private implicit val formats = Serialization.formats(NoTypeHints)
private implicit val formats: Formats = Serialization.formats(NoTypeHints)
private def writeLog(activity: Activity): Unit = {
Using.resource(new FileOutputStream(ActivityLog, true)) { out =>
@@ -43,9 +39,7 @@ trait ActivityService {
if (isPublic == false) {
list += activity
} else {
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName)
.map(_.isPrivate)
.getOrElse(true)) {
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) {
list += activity
}
}
@@ -65,9 +59,7 @@ trait ActivityService {
var json: String = null
while (list.length < 50 && { json = reader.readLine(); json } != null) {
val activity = read[Activity](json)
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName)
.map(_.isPrivate)
.getOrElse(true)) {
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) {
list += activity
}
}
@@ -87,9 +79,7 @@ trait ActivityService {
val activity = read[Activity](json)
if (owners.contains(activity.userName)) {
list += activity
} else if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName)
.map(_.isPrivate)
.getOrElse(true)) {
} else if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) {
list += activity
}
}
@@ -98,6 +88,8 @@ trait ActivityService {
}
}
def recordActivity[T <: { def toActivity: Activity }](info: T): Unit =
def recordActivity[T <: { def toActivity: Activity }](info: T): Unit = {
import scala.language.reflectiveCalls
writeLog(info.toActivity)
}
}

View File

@@ -13,7 +13,6 @@ import gitbucket.core.model.activity.{
}
import gitbucket.core.plugin.{IssueHook, PluginRegistry}
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.Implicits._
trait HandleCommentService {
@@ -34,105 +33,104 @@ trait HandleCommentService {
actionOpt: Option[String]
)(implicit context: Context, s: Session) = {
context.loginAccount.flatMap { loginAccount =>
defining(repository.owner, repository.name) {
case (owner, name) =>
val userName = loginAccount.userName
val owner = repository.owner
val name = repository.name
val userName = loginAccount.userName
actionOpt.collect {
case "close" if !issue.closed =>
updateClosed(owner, name, issue.issueId, true)
case "reopen" if issue.closed =>
updateClosed(owner, name, issue.issueId, false)
}
val (action, _) = actionOpt
.collect {
case "close" if !issue.closed =>
val info = if (issue.isPullRequest) {
ClosePullRequestInfo(owner, name, userName, issue.issueId, issue.title)
} else {
CloseIssueInfo(owner, name, userName, issue.issueId, issue.title)
}
recordActivity(info)
Some("close") -> info
case "reopen" if issue.closed =>
val info = if (issue.isPullRequest) {
ReopenPullRequestInfo(owner, name, userName, issue.issueId, issue.title)
} else {
ReopenIssueInfo(owner, name, userName, issue.issueId, issue.title)
}
recordActivity(info)
Some("reopen") -> info
}
.getOrElse(None -> None)
val commentId = (content, action) match {
case (None, None) => None
case (None, Some(action)) =>
Some(createComment(owner, name, userName, issue.issueId, action.capitalize, action))
case (Some(content), _) =>
val id = Some(
createComment(
owner,
name,
userName,
issue.issueId,
content,
action.map(_ + "_comment").getOrElse("comment")
)
)
// record comment activity
val commentInfo = if (issue.isPullRequest) {
PullRequestCommentInfo(owner, name, userName, content, issue.issueId)
} else {
IssueCommentInfo(owner, name, userName, content, issue.issueId)
}
recordActivity(commentInfo)
// extract references and create refer comment
createReferComment(owner, name, issue, content, loginAccount)
id
}
// call web hooks
action match {
case None =>
commentId foreach (callIssueCommentWebHook(repository, issue, _, loginAccount, context.settings))
case Some(act) =>
val webHookAction = act match {
case "close" => "closed"
case "reopen" => "reopened"
}
if (issue.isPullRequest)
callPullRequestWebHook(webHookAction, repository, issue.issueId, loginAccount, context.settings)
else
callIssuesWebHook(webHookAction, repository, issue, loginAccount, context.settings)
}
// call hooks
content foreach { x =>
if (issue.isPullRequest)
PluginRegistry().getPullRequestHooks.foreach(_.addedComment(commentId.get, x, issue, repository))
else
PluginRegistry().getIssueHooks.foreach(_.addedComment(commentId.get, x, issue, repository))
}
action foreach {
case "close" =>
if (issue.isPullRequest)
PluginRegistry().getPullRequestHooks.foreach(_.closed(issue, repository))
else
PluginRegistry().getIssueHooks.foreach(_.closed(issue, repository))
case "reopen" =>
if (issue.isPullRequest)
PluginRegistry().getPullRequestHooks.foreach(_.reopened(issue, repository))
else
PluginRegistry().getIssueHooks.foreach(_.reopened(issue, repository))
}
commentId.map(issue -> _)
actionOpt.collect {
case "close" if !issue.closed =>
updateClosed(owner, name, issue.issueId, true)
case "reopen" if issue.closed =>
updateClosed(owner, name, issue.issueId, false)
}
val (action, _) = actionOpt
.collect {
case "close" if !issue.closed =>
val info = if (issue.isPullRequest) {
ClosePullRequestInfo(owner, name, userName, issue.issueId, issue.title)
} else {
CloseIssueInfo(owner, name, userName, issue.issueId, issue.title)
}
recordActivity(info)
Some("close") -> info
case "reopen" if issue.closed =>
val info = if (issue.isPullRequest) {
ReopenPullRequestInfo(owner, name, userName, issue.issueId, issue.title)
} else {
ReopenIssueInfo(owner, name, userName, issue.issueId, issue.title)
}
recordActivity(info)
Some("reopen") -> info
}
.getOrElse(None -> None)
val commentId = (content, action) match {
case (None, None) => None
case (None, Some(action)) =>
Some(createComment(owner, name, userName, issue.issueId, action.capitalize, action))
case (Some(content), _) =>
val id = Some(
createComment(
owner,
name,
userName,
issue.issueId,
content,
action.map(_ + "_comment").getOrElse("comment")
)
)
// record comment activity
val commentInfo = if (issue.isPullRequest) {
PullRequestCommentInfo(owner, name, userName, content, issue.issueId)
} else {
IssueCommentInfo(owner, name, userName, content, issue.issueId)
}
recordActivity(commentInfo)
// extract references and create refer comment
createReferComment(owner, name, issue, content, loginAccount)
id
}
// call web hooks
action match {
case None =>
commentId foreach (callIssueCommentWebHook(repository, issue, _, loginAccount, context.settings))
case Some(act) =>
val webHookAction = act match {
case "close" => "closed"
case "reopen" => "reopened"
}
if (issue.isPullRequest)
callPullRequestWebHook(webHookAction, repository, issue.issueId, loginAccount, context.settings)
else
callIssuesWebHook(webHookAction, repository, issue, loginAccount, context.settings)
}
// call hooks
content foreach { x =>
if (issue.isPullRequest)
PluginRegistry().getPullRequestHooks.foreach(_.addedComment(commentId.get, x, issue, repository))
else
PluginRegistry().getIssueHooks.foreach(_.addedComment(commentId.get, x, issue, repository))
}
action foreach {
case "close" =>
if (issue.isPullRequest)
PluginRegistry().getPullRequestHooks.foreach(_.closed(issue, repository))
else
PluginRegistry().getIssueHooks.foreach(_.closed(issue, repository))
case "reopen" =>
if (issue.isPullRequest)
PluginRegistry().getPullRequestHooks.foreach(_.reopened(issue, repository))
else
PluginRegistry().getIssueHooks.foreach(_.reopened(issue, repository))
}
commentId.map(issue -> _)
}
}
@@ -161,33 +159,32 @@ trait HandleCommentService {
content: Option[String]
)(implicit context: Context, s: Session): Option[(Issue, Int)] = {
context.loginAccount.flatMap { loginAccount =>
defining(repository.owner, repository.name) {
case (owner, name) =>
val userName = loginAccount.userName
content match {
case Some(content) =>
// Update comment
val _commentId = Some(updateComment(issue.issueId, commentId.toInt, content))
// Record comment activity
val commentInfo = if (issue.isPullRequest) {
PullRequestCommentInfo(owner, name, userName, content, issue.issueId)
} else {
IssueCommentInfo(owner, name, userName, content, issue.issueId)
}
recordActivity(commentInfo)
// extract references and create refer comment
createReferComment(owner, name, issue, content, loginAccount)
// call web hooks
commentId foreach (callIssueCommentWebHook(repository, issue, _, loginAccount, context.settings))
// call hooks
if (issue.isPullRequest)
PluginRegistry().getPullRequestHooks
.foreach(_.updatedComment(commentId.toInt, content, issue, repository))
else
PluginRegistry().getIssueHooks.foreach(_.updatedComment(commentId.toInt, content, issue, repository))
_commentId.map(issue -> _)
case _ => None
val owner = repository.owner
val name = repository.name
val userName = loginAccount.userName
content match {
case Some(content) =>
// Update comment
val _commentId = Some(updateComment(issue.issueId, commentId.toInt, content))
// Record comment activity
val commentInfo = if (issue.isPullRequest) {
PullRequestCommentInfo(owner, name, userName, content, issue.issueId)
} else {
IssueCommentInfo(owner, name, userName, content, issue.issueId)
}
recordActivity(commentInfo)
// extract references and create refer comment
createReferComment(owner, name, issue, content, loginAccount)
// call web hooks
commentId foreach (callIssueCommentWebHook(repository, issue, _, loginAccount, context.settings))
// call hooks
if (issue.isPullRequest)
PluginRegistry().getPullRequestHooks
.foreach(_.updatedComment(commentId.toInt, content, issue, repository))
else
PluginRegistry().getIssueHooks.foreach(_.updatedComment(commentId.toInt, content, issue, repository))
_commentId.map(issue -> _)
case _ => None
}
}
}

View File

@@ -5,17 +5,7 @@ import gitbucket.core.util.StringUtil._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.controller.Context
import gitbucket.core.model.{
Account,
CommitState,
Issue,
IssueComment,
IssueLabel,
Label,
PullRequest,
Repository,
Role
}
import gitbucket.core.model.{Account, Issue, IssueComment, IssueLabel, Label, PullRequest, Repository, Role}
import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile._
import gitbucket.core.model.Profile.profile.blockingApi._
@@ -346,54 +336,72 @@ trait IssuesService {
implicit s: Session
) =
Issues filter { t1 =>
(if (repos.size == 1) {
(if (repos.sizeIs == 1) {
t1.byRepository(repos.head._1, repos.head._2)
} else {
((t1.userName ++ "/" ++ t1.repositoryName) inSetBind (repos.map { case (owner, repo) => s"$owner/$repo" }))
}) &&
(t1.closed === (condition.state == "closed").bind) &&
(t1.milestoneId.? isEmpty, condition.milestone == Some(None)) &&
(t1.priorityId.? isEmpty, condition.priority == Some(None)) &&
(t1.assignedUserName.? isEmpty, condition.assigned == Some(None)) &&
(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) &&
(condition.state match {
case "open" => t1.closed === false
case "closed" => t1.closed === true
case _ => t1.closed === true || t1.closed === false
}).&&(t1.milestoneId.? isEmpty, condition.milestone == Some(None))
.&&(t1.priorityId.? isEmpty, condition.priority == Some(None))
.&&(t1.assignedUserName.? isEmpty, condition.assigned == Some(None))
.&&(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) &&
(searchOption match {
case IssueSearchOption.Issues => t1.pullRequest === false
case IssueSearchOption.PullRequests => t1.pullRequest === true
case IssueSearchOption.Both => t1.pullRequest === false || t1.pullRequest === true
}) &&
})
// Milestone filter
(Milestones filter { t2 =>
(t2.byPrimaryKey(t1.userName, t1.repositoryName, t1.milestoneId)) &&
(t2.title === condition.milestone.get.get.bind)
} exists, condition.milestone.flatten.isDefined) &&
// Priority filter
(Priorities filter { t2 =>
(t2.byPrimaryKey(t1.userName, t1.repositoryName, t1.priorityId)) &&
(t2.priorityName === condition.priority.get.get.bind)
} exists, condition.priority.flatten.isDefined) &&
// Assignee filter
(t1.assignedUserName === condition.assigned.get.get.bind, condition.assigned.flatten.isDefined) &&
// Label filter
(IssueLabels filter { t2 =>
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) &&
(t2.labelId in
(Labels filter { t3 =>
(t3.byRepository(t1.userName, t1.repositoryName)) &&
(t3.labelName inSetBind condition.labels)
} map (_.labelId)))
} exists, condition.labels.nonEmpty) &&
// Visibility filter
(Repositories filter { t2 =>
(t2.byRepository(t1.userName, t1.repositoryName)) &&
(t2.isPrivate === (condition.visibility == Some("private")).bind)
} exists, condition.visibility.nonEmpty) &&
// Organization (group) filter
(t1.userName inSetBind condition.groups, condition.groups.nonEmpty) &&
// Mentioned filter
((t1.openedUserName === condition.mentioned.get.bind) || t1.assignedUserName === condition.mentioned.get.bind ||
(IssueComments filter { t2 =>
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) && (t2.commentedUserName === condition.mentioned.get.bind)
} exists), condition.mentioned.isDefined)
.&&(
Milestones filter { t2 =>
(t2.byPrimaryKey(t1.userName, t1.repositoryName, t1.milestoneId)) &&
(t2.title === condition.milestone.get.get.bind)
} exists,
condition.milestone.flatten.isDefined
)
// Priority filter
.&&(
Priorities filter { t2 =>
(t2.byPrimaryKey(t1.userName, t1.repositoryName, t1.priorityId)) &&
(t2.priorityName === condition.priority.get.get.bind)
} exists,
condition.priority.flatten.isDefined
)
// Assignee filter
.&&(t1.assignedUserName === condition.assigned.get.get.bind, condition.assigned.flatten.isDefined)
// Label filter
.&&(
IssueLabels filter { t2 =>
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) &&
(t2.labelId in
(Labels filter { t3 =>
(t3.byRepository(t1.userName, t1.repositoryName)) &&
(t3.labelName inSetBind condition.labels)
} map (_.labelId)))
} exists,
condition.labels.nonEmpty
)
// Visibility filter
.&&(
Repositories filter { t2 =>
(t2.byRepository(t1.userName, t1.repositoryName)) &&
(t2.isPrivate === (condition.visibility == Some("private")).bind)
} exists,
condition.visibility.nonEmpty
)
// Organization (group) filter
.&&(t1.userName inSetBind condition.groups, condition.groups.nonEmpty)
// Mentioned filter
.&&(
(t1.openedUserName === condition.mentioned.get.bind) || t1.assignedUserName === condition.mentioned.get.bind ||
(IssueComments filter { t2 =>
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) && (t2.commentedUserName === condition.mentioned.get.bind)
} exists),
condition.mentioned.isDefined
)
}
def insertIssue(
@@ -692,8 +700,8 @@ trait IssuesService {
case (t1, t2) =>
keywords
.map { keyword =>
(t1.title.toLowerCase like (s"%${likeEncode(keyword)}%", '^')) ||
(t1.content.toLowerCase like (s"%${likeEncode(keyword)}%", '^'))
(t1.title.toLowerCase.like(s"%${likeEncode(keyword)}%", '^')) ||
(t1.content.toLowerCase.like(s"%${likeEncode(keyword)}%", '^'))
}
.reduceLeft(_ && _)
}
@@ -720,7 +728,7 @@ trait IssuesService {
t2.pullRequest === pullRequest.bind &&
keywords
.map { query =>
t1.content.toLowerCase like (s"%${likeEncode(query)}%", '^')
t1.content.toLowerCase.like(s"%${likeEncode(query)}%", '^')
}
.reduceLeft(_ && _)
}
@@ -763,16 +771,37 @@ trait IssuesService {
def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String, loginAccount: Account)(
implicit s: Session
): Unit = {
extractIssueId(message).foreach { issueId =>
val content = s"${fromIssue.issueId}:${fromIssue.title}"
if (getIssue(owner, repository, issueId).isDefined) {
// Not add if refer comment already exist.
if (!getComments(owner, repository, issueId.toInt).exists { x =>
x.action == "refer" && x.content == content
}) {
createComment(owner, repository, loginAccount.userName, issueId.toInt, content, "refer")
extractGlobalIssueId(message).foreach {
case (_referredOwner, _referredRepository, referredIssueId) =>
val referredOwner = _referredOwner.getOrElse(owner)
val referredRepository = _referredRepository.getOrElse(repository)
getRepository(referredOwner, referredRepository).foreach { repo =>
if (isReadable(repo.repository, Option(loginAccount))) {
getIssue(referredOwner, referredRepository, referredIssueId.get).foreach { _ =>
val (content, action) = if (owner == referredOwner && repository == referredRepository) {
(s"${fromIssue.issueId}:${fromIssue.title}", "refer")
} else {
(s"${fromIssue.issueId}:${owner}:${repository}:${fromIssue.title}", "refer_global")
}
referredIssueId.foreach(
x =>
// Not add if refer comment already exist.
if (!getComments(referredOwner, referredRepository, x.toInt).exists { x =>
(x.action == "refer" || x.action == "refer_global") && x.content == content
}) {
createComment(
referredOwner,
referredRepository,
loginAccount.userName,
x.toInt,
content,
action
)
}
)
}
}
}
}
}
}
@@ -913,7 +942,7 @@ object IssuesService {
case x => Some(x)
},
param(request, "mentioned"),
param(request, "state", Seq("open", "closed")).getOrElse("open"),
param(request, "state", Seq("open", "closed", "all")).getOrElse("open"),
param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"),
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
param(request, "visibility"),
@@ -934,7 +963,7 @@ object IssuesService {
case x => Some(x)
},
param(request, "mentioned"),
param(request, "state", Seq("open", "closed")).getOrElse("open"),
param(request, "state", Seq("open", "closed", "all")).getOrElse("open"),
param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"),
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
param(request, "visibility"),

View File

@@ -10,6 +10,7 @@ import gitbucket.core.util.{JGitUtil, LockUtil}
import gitbucket.core.model.Profile.profile.blockingApi._
import gitbucket.core.model.activity.{CloseIssueInfo, MergeInfo, PushInfo}
import gitbucket.core.service.SystemSettingsService.SystemSettings
import gitbucket.core.service.WebHookService.WebHookPushPayload
import gitbucket.core.util.JGitUtil.CommitInfo
import org.eclipse.jgit.merge.{MergeStrategy, Merger, RecursiveMerger}
import org.eclipse.jgit.api.Git
@@ -27,7 +28,8 @@ trait MergeService {
with IssuesService
with RepositoryService
with PullRequestService
with WebHookPullRequestService =>
with WebHookPullRequestService
with WebHookService =>
import MergeService._
@@ -61,40 +63,91 @@ trait MergeService {
/** merge the pull request with a merge commit */
def mergeWithMergeCommit(
git: Git,
userName: String,
repositoryName: String,
repository: RepositoryInfo,
branch: String,
issueId: Int,
message: String,
committer: PersonIdent
)(implicit s: Session): ObjectId = {
new MergeCacheInfo(git, userName, repositoryName, branch, issueId, getReceiveHooks()).merge(message, committer)
loginAccount: Account,
settings: SystemSettings
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
val beforeCommitId = git.getRepository.resolve(s"refs/heads/${branch}")
val afterCommitId = new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks())
.merge(message, new PersonIdent(loginAccount.fullName, loginAccount.mailAddress), loginAccount.userName)
callWebHook(git, repository, branch, beforeCommitId, afterCommitId, loginAccount, settings)
afterCommitId
}
/** rebase to the head of the pull request branch */
def mergeWithRebase(
git: Git,
userName: String,
repositoryName: String,
repository: RepositoryInfo,
branch: String,
issueId: Int,
commits: Seq[RevCommit],
committer: PersonIdent
)(implicit s: Session): ObjectId = {
new MergeCacheInfo(git, userName, repositoryName, branch, issueId, getReceiveHooks()).rebase(committer, commits)
loginAccount: Account,
settings: SystemSettings
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
val beforeCommitId = git.getRepository.resolve(s"refs/heads/${branch}")
val afterCommitId =
new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks())
.rebase(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress), loginAccount.userName, commits)
callWebHook(git, repository, branch, beforeCommitId, afterCommitId, loginAccount, settings)
afterCommitId
}
/** squash commits in the pull request and append it */
def mergeWithSquash(
git: Git,
userName: String,
repositoryName: String,
repository: RepositoryInfo,
branch: String,
issueId: Int,
message: String,
committer: PersonIdent
)(implicit s: Session): ObjectId = {
new MergeCacheInfo(git, userName, repositoryName, branch, issueId, getReceiveHooks()).squash(message, committer)
loginAccount: Account,
settings: SystemSettings
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
val beforeCommitId = git.getRepository.resolve(s"refs/heads/${branch}")
val afterCommitId =
new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks())
.squash(message, new PersonIdent(loginAccount.fullName, loginAccount.mailAddress), loginAccount.userName)
callWebHook(git, repository, branch, beforeCommitId, afterCommitId, loginAccount, settings)
afterCommitId
}
private def callWebHook(
git: Git,
repository: RepositoryInfo,
branch: String,
beforeCommitId: ObjectId,
afterCommitId: ObjectId,
loginAccount: Account,
settings: SystemSettings
)(
implicit s: Session,
c: JsonFormat.Context
): Unit = {
callWebHookOf(repository.owner, repository.name, WebHook.Push, settings) {
getAccountByUserName(repository.owner).map { ownerAccount =>
WebHookPushPayload(
git,
loginAccount,
s"refs/heads/${branch}",
repository,
git
.log()
.addRange(beforeCommitId, afterCommitId)
.call()
.asScala
.map { commit =>
new JGitUtil.CommitInfo(commit)
}
.toList
.reverse,
ownerAccount,
oldId = beforeCommitId,
newId = afterCommitId
)
}
}
}
/** fetch remote branch to my repository refs/pull/{issueId}/head */
@@ -303,7 +356,8 @@ trait MergeService {
message,
strategy,
commits,
getReceiveHooks()
getReceiveHooks(),
settings
) match {
case Some(newCommitId) =>
// mark issue as merged and close.
@@ -428,8 +482,9 @@ trait MergeService {
message: String,
strategy: String,
commits: Seq[Seq[CommitInfo]],
receiveHooks: Seq[ReceiveHook]
)(implicit s: Session): Option[ObjectId] = {
receiveHooks: Seq[ReceiveHook],
settings: SystemSettings
)(implicit s: Session, c: JsonFormat.Context): Option[ObjectId] = {
val revCommits = Using
.resource(new RevWalk(git.getRepository)) { revWalk =>
commits.flatten.map { commit =>
@@ -443,36 +498,36 @@ trait MergeService {
Some(
mergeWithMergeCommit(
git,
repository.owner,
repository.name,
repository,
pullRequest.branch,
issue.issueId,
s"Merge pull request #${issue.issueId} from ${pullRequest.requestUserName}/${pullRequest.requestBranch}\n\n" + message,
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
loginAccount,
settings
)
)
case "rebase" =>
Some(
mergeWithRebase(
git,
repository.owner,
repository.name,
repository,
pullRequest.branch,
issue.issueId,
revCommits,
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
loginAccount,
settings
)
)
case "squash" =>
Some(
mergeWithSquash(
git,
repository.owner,
repository.name,
repository,
pullRequest.branch,
issue.issueId,
s"${issue.title} (#${issue.issueId})\n\n" + message,
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
loginAccount,
settings
)
)
case _ =>
@@ -561,7 +616,7 @@ object MergeService {
}
def checkConflict(): Option[String] = {
checkConflictCache.getOrElse(checkConflictForce)
checkConflictCache().getOrElse(checkConflictForce())
}
def checkConflictForce(): Option[String] = {
@@ -593,7 +648,7 @@ object MergeService {
}
// update branch from cache
def merge(message: String, committer: PersonIdent)(implicit s: Session): ObjectId = {
def merge(message: String, committer: PersonIdent, pusher: String)(implicit s: Session): ObjectId = {
if (checkConflict().isDefined) {
throw new RuntimeException("This pull request can't merge automatically.")
}
@@ -610,7 +665,7 @@ object MergeService {
// call pre-commit hooks
val error = receiveHooks.flatMap { hook =>
hook.preReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true)
hook.preReceive(userName, repositoryName, receivePack, receiveCommand, pusher, true)
}.headOption
error.foreach { error =>
@@ -628,7 +683,7 @@ object MergeService {
objectId
}
def rebase(committer: PersonIdent, commits: Seq[RevCommit])(implicit s: Session): ObjectId = {
def rebase(committer: PersonIdent, pusher: String, commits: Seq[RevCommit])(implicit s: Session): ObjectId = {
if (checkConflict().isDefined) {
throw new RuntimeException("This pull request can't merge automatically.")
}
@@ -664,7 +719,7 @@ object MergeService {
// call pre-commit hooks
val error = receiveHooks.flatMap { hook =>
hook.preReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true)
hook.preReceive(userName, repositoryName, receivePack, receiveCommand, pusher, true)
}.headOption
error.foreach { error =>
@@ -683,7 +738,7 @@ object MergeService {
objectId
}
def squash(message: String, committer: PersonIdent)(implicit s: Session): ObjectId = {
def squash(message: String, committer: PersonIdent, pusher: String)(implicit s: Session): ObjectId = {
if (checkConflict().isDefined) {
throw new RuntimeException("This pull request can't merge automatically.")
}
@@ -714,7 +769,7 @@ object MergeService {
// call pre-commit hooks
val error = receiveHooks.flatMap { hook =>
hook.preReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true)
hook.preReceive(userName, repositoryName, receivePack, receiveCommand, pusher, true)
}.headOption
error.foreach { error =>

View File

@@ -1,9 +1,11 @@
package gitbucket.core.service
import gitbucket.core.api.ApiMilestone
import gitbucket.core.model.Milestone
import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile.blockingApi._
import gitbucket.core.model.Profile.dateColumnType
import gitbucket.core.service.RepositoryService.RepositoryInfo
trait MilestonesService {
@@ -73,4 +75,17 @@ trait MilestonesService {
.sortBy(t => (t.dueDate.asc, t.closedDate.desc, t.milestoneId.desc))
.list
def getApiMilestone(repository: RepositoryInfo, milestoneId: Int)(implicit s: Session): Option[ApiMilestone] = {
getMilestonesWithIssueCount(repository.owner, repository.name)
.find(p => p._1.milestoneId == milestoneId)
.map(
milestoneWithIssue =>
ApiMilestone(
repository.repository,
milestoneWithIssue._1,
milestoneWithIssue._2,
milestoneWithIssue._3
)
)
}
}

View File

@@ -101,7 +101,10 @@ trait OpenIDConnectService {
redirectURI: URI
): Option[AuthenticationSuccessResponse] =
try {
AuthenticationResponseParser.parse(redirectURI, params.asJava) match {
AuthenticationResponseParser.parse(
redirectURI,
params.map { case (key, value) => (key, List(value).asJava) }.asJava
) match {
case response: AuthenticationSuccessResponse =>
if (response.getState == state) {
Some(response)

View File

@@ -3,7 +3,6 @@ package gitbucket.core.service
import gitbucket.core.model.Priority
import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile.blockingApi._
import gitbucket.core.util.StringUtil
trait PrioritiesService {

View File

@@ -71,7 +71,7 @@ object ProtectedBranchService {
pusher: String,
mergePullRequest: Boolean
)(implicit session: Session): Option[String] = {
if (mergePullRequest == true) {
if (mergePullRequest) {
None
} else {
checkBranchProtection(owner, repository, receivePack, command, pusher)
@@ -153,9 +153,9 @@ object ProtectedBranchService {
Some("Cannot force-push to a protected branch")
case ReceiveCommand.Type.UPDATE | ReceiveCommand.Type.UPDATE_NONFASTFORWARD if needStatusCheck(pusher) =>
unSuccessedContexts(command.getNewId.name) match {
case s if s.size == 1 => Some(s"""Required status check "${s.toSeq(0)}" is expected""")
case s if s.size >= 1 => Some(s"${s.size} of ${contexts.size} required status checks are expected")
case _ => None
case s if s.sizeIs == 1 => Some(s"""Required status check "${s.toSeq(0)}" is expected""")
case s if s.sizeIs >= 1 => Some(s"${s.size} of ${contexts.size} required status checks are expected")
case _ => None
}
case ReceiveCommand.Type.DELETE =>
Some("Cannot delete a protected branch")

View File

@@ -1,9 +1,10 @@
package gitbucket.core.service
import com.github.difflib.DiffUtils
import com.github.difflib.patch.DeltaType
import gitbucket.core.model.{CommitComments => _, Session => _, _}
import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile.blockingApi._
import difflib.{Delta, DiffUtils}
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.api.JsonFormat
import gitbucket.core.controller.Context
@@ -77,9 +78,9 @@ trait PullRequestService {
}
.filter {
case (t1, t2) =>
(t2.closed === closed.bind) &&
(t1.userName === owner.get.bind, owner.isDefined) &&
(t1.repositoryName === repository.get.bind, repository.isDefined)
(t2.closed === closed.bind)
.&&(t1.userName === owner.get.bind, owner.isDefined)
.&&(t1.repositoryName === repository.get.bind, repository.isDefined)
}
.groupBy { case (t1, t2) => t2.openedUserName }
.map { case (userName, t) => userName -> t.length }
@@ -183,10 +184,10 @@ trait PullRequestService {
}
.filter {
case (t1, t2) =>
(t1.requestUserName === userName.bind) &&
(t1.requestRepositoryName === repositoryName.bind) &&
(t1.requestBranch === branch.bind) &&
(t2.closed === closed.get.bind, closed.isDefined)
(t1.requestUserName === userName.bind)
.&&(t1.requestRepositoryName === repositoryName.bind)
.&&(t1.requestBranch === branch.bind)
.&&(t2.closed === closed.get.bind, closed.isDefined)
}
.map { case (t1, t2) => t1 }
.list
@@ -201,10 +202,10 @@ trait PullRequestService {
}
.filter {
case (t1, t2) =>
(t1.requestUserName === userName.bind) &&
(t1.requestRepositoryName === repositoryName.bind) &&
(t1.branch === branch.bind) &&
(t2.closed === closed.get.bind, closed.isDefined)
(t1.requestUserName === userName.bind)
.&&(t1.requestRepositoryName === repositoryName.bind)
.&&(t1.branch === branch.bind)
.&&(t2.closed === closed.get.bind, closed.isDefined)
}
.map { case (t1, t2) => t1 }
.list
@@ -243,7 +244,7 @@ trait PullRequestService {
owner: String,
repository: String,
branch: String,
loginAccount: Account,
pusherAccount: Account,
action: String,
settings: SystemSettings
)(
@@ -295,7 +296,7 @@ trait PullRequestService {
action,
getRepository(owner, repository).get,
pullreq.requestBranch,
loginAccount,
pusherAccount,
settings
)
}
@@ -441,16 +442,17 @@ trait PullRequestService {
case Left(oldLine) => updateCommitCommentPosition(commentId, newCommitId, Some(oldLine), None)
case Right(newLine) =>
var counter = newLine
patch.getDeltas.asScala.filter(_.getOriginal.getPosition < newLine).foreach { delta =>
patch.getDeltas.asScala.filter(_.getSource.getPosition < newLine).foreach { delta =>
delta.getType match {
case Delta.TYPE.CHANGE =>
if (delta.getOriginal.getPosition <= newLine - 1 && newLine <= delta.getOriginal.getPosition + delta.getRevised.getLines.size) {
case DeltaType.CHANGE =>
if (delta.getSource.getPosition <= newLine - 1 && newLine <= delta.getSource.getPosition + delta.getTarget.getLines.size) {
counter = -1
} else {
counter = counter + (delta.getRevised.getLines.size - delta.getOriginal.getLines.size)
counter = counter + (delta.getTarget.getLines.size - delta.getSource.getLines.size)
}
case Delta.TYPE.INSERT => counter = counter + delta.getRevised.getLines.size
case Delta.TYPE.DELETE => counter = counter - delta.getOriginal.getLines.size
case DeltaType.INSERT => counter = counter + delta.getTarget.getLines.size
case DeltaType.DELETE => counter = counter - delta.getSource.getLines.size
case DeltaType.EQUAL => // Do nothing
}
}
if (counter >= 0) {
@@ -507,10 +509,11 @@ trait PullRequestService {
def getPullRequestComments(userName: String, repositoryName: String, issueId: Int, commits: Seq[CommitInfo])(
implicit s: Session
): Seq[Comment] = {
(commits
.map(commit => getCommitComments(userName, repositoryName, commit.id, true))
.flatten ++ getComments(userName, repositoryName, issueId))
.groupBy {
(commits.flatMap(commit => getCommitComments(userName, repositoryName, commit.id, true)) ++ getComments(
userName,
repositoryName,
issueId
)).groupBy {
case x: IssueComment => (Some(x.commentId), None, None, None)
case x: CommitComment if x.fileName.isEmpty => (Some(x.commentId), None, None, None)
case x: CommitComment => (None, x.fileName, x.originalOldLine, x.originalNewLine)

View File

@@ -23,23 +23,29 @@ trait RepositoryCommitFileService {
with PullRequestService
with WebHookPullRequestService
with RepositoryService =>
import RepositoryCommitFileService._
/**
* Create multiple files by callback function.
* Returns commitId.
*/
def commitFiles(
repository: RepositoryService.RepositoryInfo,
files: Seq[CommitFile],
branch: String,
path: String,
message: String,
loginAccount: Account,
settings: SystemSettings
)(
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit
)(implicit s: Session, c: JsonFormat.Context) = {
// prepend path to the filename
_commitFile(repository, branch, message, loginAccount, loginAccount.fullName, loginAccount.mailAddress, settings)(f)
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
_createFiles(repository, branch, message, loginAccount, loginAccount.fullName, loginAccount.mailAddress, settings)(
f
)._1
}
/**
* Create a file from string content.
* Returns commitId + blobId.
*/
def commitFile(
repository: RepositoryService.RepositoryInfo,
branch: String,
@@ -52,7 +58,7 @@ trait RepositoryCommitFileService {
commit: String,
loginAccount: Account,
settings: SystemSettings
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
)(implicit s: Session, c: JsonFormat.Context): (ObjectId, Option[ObjectId]) = {
commitFile(
repository,
branch,
@@ -69,6 +75,10 @@ trait RepositoryCommitFileService {
)
}
/**
* Create a file from byte array content.
* Returns commitId + blobId.
*/
def commitFile(
repository: RepositoryService.RepositoryInfo,
branch: String,
@@ -78,11 +88,11 @@ trait RepositoryCommitFileService {
content: Array[Byte],
message: String,
commit: String,
loginAccount: Account,
fullName: String,
mailAddress: String,
pusherAccount: Account,
committerName: String,
committerMailAddress: String,
settings: SystemSettings
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
)(implicit s: Session, c: JsonFormat.Context): (ObjectId, Option[ObjectId]) = {
val newPath = newFileName.map { newFileName =>
if (path.length == 0) newFileName else s"${path}/${newFileName}"
@@ -91,7 +101,7 @@ trait RepositoryCommitFileService {
if (path.length == 0) oldFileName else s"${path}/${oldFileName}"
}
_commitFile(repository, branch, message, loginAccount, fullName, mailAddress, settings) {
_createFiles(repository, branch, message, pusherAccount, committerName, committerMailAddress, settings) {
case (git, headTip, builder, inserter) =>
if (headTip.getName == commit) {
val permission = JGitUtil
@@ -105,28 +115,33 @@ trait RepositoryCommitFileService {
}
.flatten
.headOption
newPath.foreach { newPath =>
builder.add(JGitUtil.createDirCacheEntry(newPath, permission.map { bits =>
.map { bits =>
FileMode.fromBits(bits)
} getOrElse FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content)))
}
.getOrElse(FileMode.REGULAR_FILE)
val objectId = newPath.map { newPath =>
val objectId = inserter.insert(Constants.OBJ_BLOB, content)
builder.add(JGitUtil.createDirCacheEntry(newPath, permission, objectId))
objectId
}
builder.finish()
}
objectId
} else None
}
}
private def _commitFile(
private def _createFiles[R](
repository: RepositoryService.RepositoryInfo,
branch: String,
message: String,
loginAccount: Account,
pusherAccount: Account,
committerName: String,
committerMailAddress: String,
settings: SystemSettings
)(
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => R
)(implicit s: Session, c: JsonFormat.Context): (ObjectId, R) = {
LockUtil.lock(s"${repository.owner}/${repository.name}") {
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
@@ -135,7 +150,7 @@ trait RepositoryCommitFileService {
val headName = s"refs/heads/${branch}"
val headTip = git.getRepository.resolve(headName)
f(git, headTip, builder, inserter)
val result = f(git, headTip, builder, inserter)
val commitId = JGitUtil.createNewCommit(
git,
@@ -156,7 +171,7 @@ trait RepositoryCommitFileService {
// call pre-commit hook
val error = PluginRegistry().getReceiveHooks.flatMap { hook =>
hook.preReceive(repository.owner, repository.name, receivePack, receiveCommand, committerName, false)
hook.preReceive(repository.owner, repository.name, receivePack, receiveCommand, pusherAccount.userName, false)
}.headOption
error match {
@@ -177,12 +192,12 @@ trait RepositoryCommitFileService {
refUpdate.update()
// update pull request
updatePullRequests(repository.owner, repository.name, branch, loginAccount, "synchronize", settings)
updatePullRequests(repository.owner, repository.name, branch, pusherAccount, "synchronize", settings)
// record activity
updateLastActivityDate(repository.owner, repository.name)
val commitInfo = new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
val pushInfo = PushInfo(repository.owner, repository.name, loginAccount.userName, branch, List(commitInfo))
val pushInfo = PushInfo(repository.owner, repository.name, pusherAccount.userName, branch, List(commitInfo))
recordActivity(pushInfo)
// create issue comment by commit message
@@ -192,17 +207,17 @@ trait RepositoryCommitFileService {
if (branch == repository.repository.defaultBranch) {
closeIssuesFromMessage(message, committerName, repository.owner, repository.name).foreach { issueId =>
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
callIssuesWebHook("closed", repository, issue, loginAccount, settings)
callIssuesWebHook("closed", repository, issue, pusherAccount, settings)
val closeIssueInfo = CloseIssueInfo(
repository.owner,
repository.name,
loginAccount.userName,
pusherAccount.userName,
issue.issueId,
issue.title
)
recordActivity(closeIssueInfo)
PluginRegistry().getIssueHooks
.foreach(_.closedByCommitComment(issue, repository, message, loginAccount))
.foreach(_.closedByCommitComment(issue, repository, message, pusherAccount))
}
}
}
@@ -217,7 +232,7 @@ trait RepositoryCommitFileService {
getAccountByUserName(repository.owner).map { ownerAccount =>
WebHookPushPayload(
git,
loginAccount,
pusherAccount,
headName,
repository,
List(commit),
@@ -228,7 +243,7 @@ trait RepositoryCommitFileService {
}
}
}
commitId
(commitId, result)
}
}
}

View File

@@ -25,7 +25,7 @@ object RepositoryCreationService {
private val Creating = new ConcurrentHashMap[String, Option[String]]()
def isCreating(owner: String, repository: String): Boolean = {
Option(Creating.get(s"${owner}/${repository}")).map(_.isEmpty).getOrElse(false)
Option(Creating.get(s"${owner}/${repository}")).exists(_.isEmpty)
}
def startCreation(owner: String, repository: String): Unit = {
@@ -40,7 +40,7 @@ object RepositoryCreationService {
}
def getCreationError(owner: String, repository: String): Option[String] = {
Option(Creating.remove(s"${owner}/${repository}")).getOrElse(None)
Option(Creating.remove(s"${owner}/${repository}")).flatten
}
}
@@ -53,6 +53,11 @@ trait RepositoryCreationService {
with ActivityService
with PrioritiesService =>
def canCreateRepository(repositoryOwner: String, loginAccount: Account)(implicit session: Session): Boolean = {
repositoryOwner == loginAccount.userName || getGroupsByUserName(loginAccount.userName)
.contains(repositoryOwner) || loginAccount.isAdmin
}
def createRepository(
loginAccount: Account,
owner: String,
@@ -76,7 +81,7 @@ trait RepositoryCreationService {
RepositoryCreationService.startCreation(owner, name)
try {
Database() withTransaction { implicit session =>
val ownerAccount = getAccountByUserName(owner).get
//val ownerAccount = getAccountByUserName(owner).get
val loginUserName = loginAccount.userName
val copyRepositoryDir = if (initOption == "COPY") {

View File

@@ -2,7 +2,6 @@ package gitbucket.core.service
import gitbucket.core.controller.Context
import gitbucket.core.util._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.model.{CommitComments => _, Session => _, _}
import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile.blockingApi._
@@ -12,7 +11,7 @@ import gitbucket.core.util.Directory.{getRepositoryDir, getRepositoryFilesDir, g
import gitbucket.core.util.JGitUtil.FileInfo
import org.apache.commons.io.FileUtils
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.{Repository => _, _}
import org.eclipse.jgit.lib.{Repository => _}
import scala.util.Using
trait RepositoryService {
@@ -61,7 +60,8 @@ trait RepositoryService {
externalWikiUrl = None,
allowFork = true,
mergeOptions = "merge-commit,squash,rebase",
defaultMergeOption = "merge-commit"
defaultMergeOption = "merge-commit",
safeMode = true
)
)
@@ -202,22 +202,19 @@ trait RepositoryService {
)
// Move git repository
defining(getRepositoryDir(oldUserName, oldRepositoryName)) { dir =>
if (dir.isDirectory) {
FileUtils.moveDirectory(dir, getRepositoryDir(newUserName, newRepositoryName))
}
val repoDir = getRepositoryDir(oldUserName, oldRepositoryName)
if (repoDir.isDirectory) {
FileUtils.moveDirectory(repoDir, getRepositoryDir(newUserName, newRepositoryName))
}
// Move wiki repository
defining(getWikiRepositoryDir(oldUserName, oldRepositoryName)) { dir =>
if (dir.isDirectory) {
FileUtils.moveDirectory(dir, getWikiRepositoryDir(newUserName, newRepositoryName))
}
val wikiDir = getWikiRepositoryDir(oldUserName, oldRepositoryName)
if (wikiDir.isDirectory) {
FileUtils.moveDirectory(wikiDir, getWikiRepositoryDir(newUserName, newRepositoryName))
}
// Move files directory
defining(getRepositoryFilesDir(oldUserName, oldRepositoryName)) { dir =>
if (dir.isDirectory) {
FileUtils.moveDirectory(dir, getRepositoryFilesDir(newUserName, newRepositoryName))
}
val filesDir = getRepositoryFilesDir(oldUserName, oldRepositoryName)
if (filesDir.isDirectory) {
FileUtils.moveDirectory(filesDir, getRepositoryFilesDir(newUserName, newRepositoryName))
}
// Delete parent directory
FileUtil.deleteDirectoryIfEmpty(getRepositoryFilesDir(oldUserName, oldRepositoryName))
@@ -237,10 +234,11 @@ trait RepositoryService {
LockUtil.lock(s"${repository.userName}/${repository.repositoryName}") {
deleteRepositoryOnModel(repository.userName, repository.repositoryName)
FileUtils.deleteDirectory(getRepositoryDir(repository.userName, repository.repositoryName))
FileUtils.deleteDirectory(getWikiRepositoryDir(repository.userName, repository.repositoryName))
FileUtils.deleteDirectory(getTemporaryDir(repository.userName, repository.repositoryName))
FileUtils.deleteDirectory(getRepositoryFilesDir(repository.userName, repository.repositoryName))
FileUtil.deleteRecursively(getRepositoryDir(repository.userName, repository.repositoryName))
FileUtil.deleteRecursively(getWikiRepositoryDir(repository.userName, repository.repositoryName))
FileUtil.deleteRecursively(getTemporaryDir(repository.userName, repository.repositoryName))
FileUtil.deleteRecursively(getRepositoryFilesDir(repository.userName, repository.repositoryName))
// Call hooks
PluginRegistry().getRepositoryHooks.foreach(_.deleted(repository.userName, repository.repositoryName))
@@ -463,7 +461,7 @@ trait RepositoryService {
.filter { case (t1, t2) => t2.removed === false.bind }
.map { case (t1, t2) => t1 }
// for Normal Users
case Some(x) if (!x.isAdmin || limit) =>
case Some(x) =>
Repositories
.join(Accounts)
.on(_.userName === _.userName)
@@ -553,7 +551,8 @@ trait RepositoryService {
externalWikiUrl: Option[String],
allowFork: Boolean,
mergeOptions: Seq[String],
defaultMergeOption: String
defaultMergeOption: String,
safeMode: Boolean
)(implicit s: Session): Unit = {
Repositories
@@ -569,6 +568,7 @@ trait RepositoryService {
r.allowFork,
r.mergeOptions,
r.defaultMergeOption,
r.safeMode,
r.updatedDate
)
}
@@ -582,6 +582,7 @@ trait RepositoryService {
allowFork,
mergeOptions.mkString(","),
defaultMergeOption,
safeMode,
currentDate
)
}
@@ -765,7 +766,8 @@ trait RepositoryService {
JGitUtil.getContentFromId(git, file.id, true).collect {
case bytes if FileUtil.isText(bytes) => StringUtil.convertFromByteArray(bytes)
}
} getOrElse None
}
.flatten
} getOrElse ""
}
}
@@ -819,11 +821,13 @@ object RepositoryService {
def sshUrl(implicit context: Context): Option[String] = RepositoryService.sshUrl(owner, name)
def splitPath(path: String): (String, String) = {
val id = branchList.collectFirst {
case branch if (path == branch || path.startsWith(branch + "/")) => branch
} orElse tags.collectFirst {
case tag if (path == tag.name || path.startsWith(tag.name + "/")) => tag.name
} getOrElse path.split("/")(0)
val names = (branchList ++ tags.map(_.name)).sortBy(_.length).reverse
val id = names.collectFirst {
case name if (path == name || path.startsWith(name + "/")) => name
} getOrElse {
path.split("/")(0)
}
(id, path.substring(id.length).stripPrefix("/"))
}

View File

@@ -7,7 +7,6 @@ import com.nimbusds.oauth2.sdk.id.{ClientID, Issuer}
import gitbucket.core.service.SystemSettingsService._
import gitbucket.core.util.ConfigUtil._
import gitbucket.core.util.Directory._
import gitbucket.core.util.SyntaxSugars._
import scala.util.Using
trait SystemSettingsService {
@@ -15,175 +14,173 @@ trait SystemSettingsService {
def baseUrl(implicit request: HttpServletRequest): String = loadSystemSettings().baseUrl(request)
def saveSystemSettings(settings: SystemSettings): Unit = {
defining(new java.util.Properties()) { props =>
settings.baseUrl.foreach(x => props.setProperty(BaseURL, x.replaceFirst("/\\Z", "")))
settings.information.foreach(x => props.setProperty(Information, x))
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
props.setProperty(AllowAnonymousAccess, settings.allowAnonymousAccess.toString)
props.setProperty(IsCreateRepoOptionPublic, settings.isCreateRepoOptionPublic.toString)
props.setProperty(RepositoryOperationCreate, settings.repositoryOperation.create.toString)
props.setProperty(RepositoryOperationDelete, settings.repositoryOperation.delete.toString)
props.setProperty(RepositoryOperationRename, settings.repositoryOperation.rename.toString)
props.setProperty(RepositoryOperationTransfer, settings.repositoryOperation.transfer.toString)
props.setProperty(RepositoryOperationFork, settings.repositoryOperation.fork.toString)
props.setProperty(Gravatar, settings.gravatar.toString)
props.setProperty(Notification, settings.notification.toString)
props.setProperty(LimitVisibleRepositories, settings.limitVisibleRepositories.toString)
props.setProperty(SshEnabled, settings.ssh.enabled.toString)
settings.ssh.sshHost.foreach(x => props.setProperty(SshHost, x.trim))
settings.ssh.sshPort.foreach(x => props.setProperty(SshPort, x.toString))
props.setProperty(UseSMTP, settings.useSMTP.toString)
if (settings.useSMTP) {
settings.smtp.foreach { smtp =>
props.setProperty(SmtpHost, smtp.host)
smtp.port.foreach(x => props.setProperty(SmtpPort, x.toString))
smtp.user.foreach(props.setProperty(SmtpUser, _))
smtp.password.foreach(props.setProperty(SmtpPassword, _))
smtp.ssl.foreach(x => props.setProperty(SmtpSsl, x.toString))
smtp.starttls.foreach(x => props.setProperty(SmtpStarttls, x.toString))
smtp.fromAddress.foreach(props.setProperty(SmtpFromAddress, _))
smtp.fromName.foreach(props.setProperty(SmtpFromName, _))
val props = new java.util.Properties()
settings.baseUrl.foreach(x => props.setProperty(BaseURL, x.replaceFirst("/\\Z", "")))
settings.information.foreach(x => props.setProperty(Information, x))
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
props.setProperty(AllowAnonymousAccess, settings.allowAnonymousAccess.toString)
props.setProperty(IsCreateRepoOptionPublic, settings.isCreateRepoOptionPublic.toString)
props.setProperty(RepositoryOperationCreate, settings.repositoryOperation.create.toString)
props.setProperty(RepositoryOperationDelete, settings.repositoryOperation.delete.toString)
props.setProperty(RepositoryOperationRename, settings.repositoryOperation.rename.toString)
props.setProperty(RepositoryOperationTransfer, settings.repositoryOperation.transfer.toString)
props.setProperty(RepositoryOperationFork, settings.repositoryOperation.fork.toString)
props.setProperty(Gravatar, settings.gravatar.toString)
props.setProperty(Notification, settings.notification.toString)
props.setProperty(LimitVisibleRepositories, settings.limitVisibleRepositories.toString)
props.setProperty(SshEnabled, settings.ssh.enabled.toString)
settings.ssh.sshHost.foreach(x => props.setProperty(SshHost, x.trim))
settings.ssh.sshPort.foreach(x => props.setProperty(SshPort, x.toString))
props.setProperty(UseSMTP, settings.useSMTP.toString)
if (settings.useSMTP) {
settings.smtp.foreach { smtp =>
props.setProperty(SmtpHost, smtp.host)
smtp.port.foreach(x => props.setProperty(SmtpPort, x.toString))
smtp.user.foreach(props.setProperty(SmtpUser, _))
smtp.password.foreach(props.setProperty(SmtpPassword, _))
smtp.ssl.foreach(x => props.setProperty(SmtpSsl, x.toString))
smtp.starttls.foreach(x => props.setProperty(SmtpStarttls, x.toString))
smtp.fromAddress.foreach(props.setProperty(SmtpFromAddress, _))
smtp.fromName.foreach(props.setProperty(SmtpFromName, _))
}
}
props.setProperty(LdapAuthentication, settings.ldapAuthentication.toString)
if (settings.ldapAuthentication) {
settings.ldap.foreach { ldap =>
props.setProperty(LdapHost, ldap.host)
ldap.port.foreach(x => props.setProperty(LdapPort, x.toString))
ldap.bindDN.foreach(x => props.setProperty(LdapBindDN, x))
ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x))
props.setProperty(LdapBaseDN, ldap.baseDN)
props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute)
ldap.additionalFilterCondition.foreach(x => props.setProperty(LdapAdditionalFilterCondition, x))
ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x))
ldap.mailAttribute.foreach(x => props.setProperty(LdapMailAddressAttribute, x))
ldap.tls.foreach(x => props.setProperty(LdapTls, x.toString))
ldap.ssl.foreach(x => props.setProperty(LdapSsl, x.toString))
ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x))
}
}
props.setProperty(OidcAuthentication, settings.oidcAuthentication.toString)
if (settings.oidcAuthentication) {
settings.oidc.foreach { oidc =>
props.setProperty(OidcIssuer, oidc.issuer.getValue)
props.setProperty(OidcClientId, oidc.clientID.getValue)
props.setProperty(OidcClientSecret, oidc.clientSecret.getValue)
oidc.jwsAlgorithm.foreach { x =>
props.setProperty(OidcJwsAlgorithm, x.getName)
}
}
props.setProperty(LdapAuthentication, settings.ldapAuthentication.toString)
if (settings.ldapAuthentication) {
settings.ldap.foreach { ldap =>
props.setProperty(LdapHost, ldap.host)
ldap.port.foreach(x => props.setProperty(LdapPort, x.toString))
ldap.bindDN.foreach(x => props.setProperty(LdapBindDN, x))
ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x))
props.setProperty(LdapBaseDN, ldap.baseDN)
props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute)
ldap.additionalFilterCondition.foreach(x => props.setProperty(LdapAdditionalFilterCondition, x))
ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x))
ldap.mailAttribute.foreach(x => props.setProperty(LdapMailAddressAttribute, x))
ldap.tls.foreach(x => props.setProperty(LdapTls, x.toString))
ldap.ssl.foreach(x => props.setProperty(LdapSsl, x.toString))
ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x))
}
}
props.setProperty(OidcAuthentication, settings.oidcAuthentication.toString)
if (settings.oidcAuthentication) {
settings.oidc.foreach { oidc =>
props.setProperty(OidcIssuer, oidc.issuer.getValue)
props.setProperty(OidcClientId, oidc.clientID.getValue)
props.setProperty(OidcClientSecret, oidc.clientSecret.getValue)
oidc.jwsAlgorithm.foreach { x =>
props.setProperty(OidcJwsAlgorithm, x.getName)
}
}
}
props.setProperty(SkinName, settings.skinName.toString)
settings.userDefinedCss.foreach(x => props.setProperty(UserDefinedCss, x))
props.setProperty(ShowMailAddress, settings.showMailAddress.toString)
props.setProperty(WebHookBlockPrivateAddress, settings.webHook.blockPrivateAddress.toString)
props.setProperty(WebHookWhitelist, settings.webHook.whitelist.mkString("\n"))
props.setProperty(UploadMaxFileSize, settings.upload.maxFileSize.toString)
props.setProperty(UploadTimeout, settings.upload.timeout.toString)
props.setProperty(UploadLargeMaxFileSize, settings.upload.largeMaxFileSize.toString)
props.setProperty(UploadLargeTimeout, settings.upload.largeTimeout.toString)
props.setProperty(RepositoryViewerMaxFiles, settings.repositoryViewer.maxFiles.toString)
}
props.setProperty(SkinName, settings.skinName)
settings.userDefinedCss.foreach(x => props.setProperty(UserDefinedCss, x))
props.setProperty(ShowMailAddress, settings.showMailAddress.toString)
props.setProperty(WebHookBlockPrivateAddress, settings.webHook.blockPrivateAddress.toString)
props.setProperty(WebHookWhitelist, settings.webHook.whitelist.mkString("\n"))
props.setProperty(UploadMaxFileSize, settings.upload.maxFileSize.toString)
props.setProperty(UploadTimeout, settings.upload.timeout.toString)
props.setProperty(UploadLargeMaxFileSize, settings.upload.largeMaxFileSize.toString)
props.setProperty(UploadLargeTimeout, settings.upload.largeTimeout.toString)
props.setProperty(RepositoryViewerMaxFiles, settings.repositoryViewer.maxFiles.toString)
Using.resource(new java.io.FileOutputStream(GitBucketConf)) { out =>
props.store(out, null)
}
Using.resource(new java.io.FileOutputStream(GitBucketConf)) { out =>
props.store(out, null)
}
}
def loadSystemSettings(): SystemSettings = {
defining(new java.util.Properties()) { props =>
if (GitBucketConf.exists) {
Using.resource(new java.io.FileInputStream(GitBucketConf)) { in =>
props.load(in)
}
val props = new java.util.Properties()
if (GitBucketConf.exists) {
Using.resource(new java.io.FileInputStream(GitBucketConf)) { in =>
props.load(in)
}
SystemSettings(
getOptionValue[String](props, BaseURL, None).map(x => x.replaceFirst("/\\Z", "")),
getOptionValue(props, Information, None),
getValue(props, AllowAccountRegistration, false),
getValue(props, AllowAnonymousAccess, true),
getValue(props, IsCreateRepoOptionPublic, true),
RepositoryOperation(
create = getValue(props, RepositoryOperationCreate, true),
delete = getValue(props, RepositoryOperationDelete, true),
rename = getValue(props, RepositoryOperationRename, true),
transfer = getValue(props, RepositoryOperationTransfer, true),
fork = getValue(props, RepositoryOperationFork, true)
),
getValue(props, Gravatar, false),
getValue(props, Notification, false),
getValue(props, LimitVisibleRepositories, false),
Ssh(
getValue(props, SshEnabled, false),
getOptionValue[String](props, SshHost, None).map(_.trim),
getOptionValue(props, SshPort, Some(DefaultSshPort))
),
getValue(
props,
UseSMTP,
getValue(props, Notification, false)
), // handle migration scenario from only notification to useSMTP
if (getValue(props, UseSMTP, getValue(props, Notification, false))) {
Some(
Smtp(
getValue(props, SmtpHost, ""),
getOptionValue(props, SmtpPort, Some(DefaultSmtpPort)),
getOptionValue(props, SmtpUser, None),
getOptionValue(props, SmtpPassword, None),
getOptionValue[Boolean](props, SmtpSsl, None),
getOptionValue[Boolean](props, SmtpStarttls, None),
getOptionValue(props, SmtpFromAddress, None),
getOptionValue(props, SmtpFromName, None)
)
)
} else None,
getValue(props, LdapAuthentication, false),
if (getValue(props, LdapAuthentication, false)) {
Some(
Ldap(
getValue(props, LdapHost, ""),
getOptionValue(props, LdapPort, Some(DefaultLdapPort)),
getOptionValue(props, LdapBindDN, None),
getOptionValue(props, LdapBindPassword, None),
getValue(props, LdapBaseDN, ""),
getValue(props, LdapUserNameAttribute, ""),
getOptionValue(props, LdapAdditionalFilterCondition, None),
getOptionValue(props, LdapFullNameAttribute, None),
getOptionValue(props, LdapMailAddressAttribute, None),
getOptionValue[Boolean](props, LdapTls, None),
getOptionValue[Boolean](props, LdapSsl, None),
getOptionValue(props, LdapKeystore, None)
)
)
} else None,
getValue(props, OidcAuthentication, false),
if (getValue(props, OidcAuthentication, false)) {
Some(
OIDC(
getValue(props, OidcIssuer, ""),
getValue(props, OidcClientId, ""),
getValue(props, OidcClientSecret, ""),
getOptionValue(props, OidcJwsAlgorithm, None)
)
)
} else {
None
},
getValue(props, SkinName, "skin-blue"),
getOptionValue(props, UserDefinedCss, None),
getValue(props, ShowMailAddress, false),
WebHook(getValue(props, WebHookBlockPrivateAddress, false), getSeqValue(props, WebHookWhitelist, "")),
Upload(
getValue(props, UploadMaxFileSize, 3 * 1024 * 1024),
getValue(props, UploadTimeout, 3 * 10000),
getValue(props, UploadLargeMaxFileSize, 3 * 1024 * 1024),
getValue(props, UploadLargeTimeout, 3 * 10000)
),
RepositoryViewerSettings(
getValue(props, RepositoryViewerMaxFiles, 0)
)
)
}
SystemSettings(
getOptionValue[String](props, BaseURL, None).map(x => x.replaceFirst("/\\Z", "")),
getOptionValue(props, Information, None),
getValue(props, AllowAccountRegistration, false),
getValue(props, AllowAnonymousAccess, true),
getValue(props, IsCreateRepoOptionPublic, true),
RepositoryOperation(
create = getValue(props, RepositoryOperationCreate, true),
delete = getValue(props, RepositoryOperationDelete, true),
rename = getValue(props, RepositoryOperationRename, true),
transfer = getValue(props, RepositoryOperationTransfer, true),
fork = getValue(props, RepositoryOperationFork, true)
),
getValue(props, Gravatar, false),
getValue(props, Notification, false),
getValue(props, LimitVisibleRepositories, false),
Ssh(
getValue(props, SshEnabled, false),
getOptionValue[String](props, SshHost, None).map(_.trim),
getOptionValue(props, SshPort, Some(DefaultSshPort))
),
getValue(
props,
UseSMTP,
getValue(props, Notification, false)
), // handle migration scenario from only notification to useSMTP
if (getValue(props, UseSMTP, getValue(props, Notification, false))) {
Some(
Smtp(
getValue(props, SmtpHost, ""),
getOptionValue(props, SmtpPort, Some(DefaultSmtpPort)),
getOptionValue(props, SmtpUser, None),
getOptionValue(props, SmtpPassword, None),
getOptionValue[Boolean](props, SmtpSsl, None),
getOptionValue[Boolean](props, SmtpStarttls, None),
getOptionValue(props, SmtpFromAddress, None),
getOptionValue(props, SmtpFromName, None)
)
)
} else None,
getValue(props, LdapAuthentication, false),
if (getValue(props, LdapAuthentication, false)) {
Some(
Ldap(
getValue(props, LdapHost, ""),
getOptionValue(props, LdapPort, Some(DefaultLdapPort)),
getOptionValue(props, LdapBindDN, None),
getOptionValue(props, LdapBindPassword, None),
getValue(props, LdapBaseDN, ""),
getValue(props, LdapUserNameAttribute, ""),
getOptionValue(props, LdapAdditionalFilterCondition, None),
getOptionValue(props, LdapFullNameAttribute, None),
getOptionValue(props, LdapMailAddressAttribute, None),
getOptionValue[Boolean](props, LdapTls, None),
getOptionValue[Boolean](props, LdapSsl, None),
getOptionValue(props, LdapKeystore, None)
)
)
} else None,
getValue(props, OidcAuthentication, false),
if (getValue(props, OidcAuthentication, false)) {
Some(
OIDC(
getValue(props, OidcIssuer, ""),
getValue(props, OidcClientId, ""),
getValue(props, OidcClientSecret, ""),
getOptionValue(props, OidcJwsAlgorithm, None)
)
)
} else {
None
},
getValue(props, SkinName, "skin-blue"),
getOptionValue(props, UserDefinedCss, None),
getValue(props, ShowMailAddress, false),
WebHook(getValue(props, WebHookBlockPrivateAddress, false), getSeqValue(props, WebHookWhitelist, "")),
Upload(
getValue(props, UploadMaxFileSize, 3 * 1024 * 1024),
getValue(props, UploadTimeout, 3 * 10000),
getValue(props, UploadLargeMaxFileSize, 3 * 1024 * 1024),
getValue(props, UploadLargeTimeout, 3 * 10000)
),
RepositoryViewerSettings(
getValue(props, RepositoryViewerMaxFiles, 0)
)
)
}
}
@@ -368,12 +365,11 @@ object SystemSettingsService {
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = {
getConfigValue(key).getOrElse {
defining(props.getProperty(key)) { value =>
if (value == null || value.isEmpty) {
default
} else {
convertType(value).asInstanceOf[A]
}
val value = props.getProperty(key)
if (value == null || value.isEmpty) {
default
} else {
convertType(value).asInstanceOf[A]
}
}
}
@@ -390,12 +386,11 @@ object SystemSettingsService {
private def getOptionValue[A: ClassTag](props: java.util.Properties, key: String, default: Option[A]): Option[A] = {
getConfigValue(key).orElse {
defining(props.getProperty(key)) { value =>
if (value == null || value.isEmpty) {
default
} else {
Some(convertType(value)).asInstanceOf[Option[A]]
}
val value = props.getProperty(key)
if (value == null || value.isEmpty) {
default
} else {
Some(convertType(value)).asInstanceOf[Option[A]]
}
}
}

View File

@@ -3,7 +3,6 @@ package gitbucket.core.service
import fr.brouillard.oss.security.xhub.XHub
import fr.brouillard.oss.security.xhub.XHub.{XHubConverter, XHubDigest}
import gitbucket.core.api._
import gitbucket.core.controller.Context
import gitbucket.core.model.{
Account,
AccountWebHook,
@@ -21,7 +20,6 @@ import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile.blockingApi._
import org.apache.http.client.utils.URLEncodedUtils
import gitbucket.core.util.JGitUtil.CommitInfo
import gitbucket.core.util.Implicits._
import gitbucket.core.util.{HttpClientUtil, RepositoryName, StringUtil}
import gitbucket.core.service.RepositoryService.RepositoryInfo
import org.apache.http.NameValuePair
@@ -37,6 +35,7 @@ import org.apache.http.HttpRequest
import org.apache.http.HttpResponse
import gitbucket.core.model.WebHookContentType
import gitbucket.core.service.SystemSettingsService.SystemSettings
import gitbucket.core.view.helpers.getApiMilestone
import org.apache.http.client.entity.EntityBuilder
import org.apache.http.entity.ContentType
@@ -128,7 +127,7 @@ trait WebHookService {
ctype = ctype,
token = token
)
events.map { event: WebHook.Event =>
events.map { (event: WebHook.Event) =>
RepositoryWebHookEvents insert RepositoryWebHookEvent(owner, repository, url, event)
}
}
@@ -146,7 +145,7 @@ trait WebHookService {
.map(w => (w.ctype, w.token))
.update((ctype, token))
RepositoryWebHookEvents.filter(_.byRepositoryWebHook(owner, repository, url)).delete
events.map { event: WebHook.Event =>
events.map { (event: WebHook.Event) =>
RepositoryWebHookEvents insert RepositoryWebHookEvent(owner, repository, url, event)
}
}
@@ -165,14 +164,16 @@ trait WebHookService {
.map(w => (w.url, w.ctype, w.token))
.update((url, ctype, token))
RepositoryWebHookEvents.filter(_.byRepositoryWebHook(owner, repository, url)).delete
events.map { event: WebHook.Event =>
events.map { (event: WebHook.Event) =>
RepositoryWebHookEvents insert RepositoryWebHookEvent(owner, repository, url, event)
}
}
// Records in WEB_HOOK_EVENT will be deleted automatically by cascaded constraint
def deleteWebHook(owner: String, repository: String, url: String)(implicit s: Session): Unit =
RepositoryWebHooks.filter(_.byRepositoryUrl(owner, repository, url)).delete
// Records in WEB_HOOK_EVENT will be deleted automatically by cascaded constraint
def deleteWebHookById(id: Int)(implicit s: Session): Unit =
RepositoryWebHooks.filter(_.byId(id)).delete
@@ -228,7 +229,7 @@ trait WebHookService {
token: Option[String]
)(implicit s: Session): Unit = {
AccountWebHooks insert AccountWebHook(owner, url, ctype, token)
events.map { event: WebHook.Event =>
events.map { (event: WebHook.Event) =>
AccountWebHookEvents insert AccountWebHookEvent(owner, url, event)
}
}
@@ -242,7 +243,7 @@ trait WebHookService {
)(implicit s: Session): Unit = {
AccountWebHooks.filter(_.byPrimaryKey(owner, url)).map(w => (w.ctype, w.token)).update((ctype, token))
AccountWebHookEvents.filter(_.byAccountWebHook(owner, url)).delete
events.map { event: WebHook.Event =>
events.map { (event: WebHook.Event) =>
AccountWebHookEvents insert AccountWebHookEvent(owner, url, event)
}
}
@@ -255,11 +256,11 @@ trait WebHookService {
)(implicit s: Session, c: JsonFormat.Context): Unit = {
val webHooks = getWebHooksByEvent(owner, repository, event)
if (webHooks.nonEmpty) {
makePayload.map(callWebHook(event, webHooks, _, settings))
makePayload.foreach(callWebHook(event, webHooks, _, settings))
}
val accountWebHooks = getAccountWebHooksByEvent(owner, event)
if (accountWebHooks.nonEmpty) {
makePayload.map(callWebHook(event, accountWebHooks, _, settings))
makePayload.foreach(callWebHook(event, accountWebHooks, _, settings))
}
}
@@ -283,7 +284,7 @@ trait WebHookService {
val json = JsonFormat(payload)
webHooks.map { webHook =>
val reqPromise = Promise[HttpRequest]
val reqPromise = Promise[HttpRequest]()
val f = Future {
val itcp = new org.apache.http.HttpRequestInterceptor {
def process(res: HttpRequest, ctx: HttpContext): Unit = {
@@ -394,7 +395,8 @@ trait WebHookPullRequestService extends WebHookService {
ApiUser(issueUser),
issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)),
getIssueLabels(repository.owner, repository.name, issue.issueId)
.map(ApiLabel(_, RepositoryName(repository)))
.map(ApiLabel(_, RepositoryName(repository))),
getApiMilestone(repository, issue.milestoneId getOrElse (0))
),
sender = ApiUser(sender)
)
@@ -576,6 +578,7 @@ trait WebHookIssueCommentService extends WebHookPullRequestService {
commenter <- users.get(issueComment.commentedUserName)
assignedUser = issue.assignedUserName.flatMap(users.get(_))
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
milestone = getApiMilestone(repository, issue.milestoneId getOrElse (0))
} yield {
WebHookIssueCommentPayload(
issue = issue,
@@ -586,7 +589,8 @@ trait WebHookIssueCommentService extends WebHookPullRequestService {
repositoryUser = repoOwner,
assignedUser = assignedUser,
sender = sender,
labels = labels
labels = labels,
milestone = milestone
)
}
}
@@ -760,7 +764,8 @@ object WebHookService {
repositoryUser: Account,
assignedUser: Option[Account],
sender: Account,
labels: List[Label]
labels: List[Label],
milestone: Option[ApiMilestone]
): WebHookIssueCommentPayload =
WebHookIssueCommentPayload(
action = "created",
@@ -770,7 +775,8 @@ object WebHookService {
RepositoryName(repository),
ApiUser(issueUser),
assignedUser.map(ApiUser(_)),
labels.map(ApiLabel(_, RepositoryName(repository)))
labels.map(ApiLabel(_, RepositoryName(repository))),
milestone
),
comment =
ApiComment(comment, RepositoryName(repository), issue.issueId, ApiUser(commentUser), issue.isPullRequest),

View File

@@ -5,7 +5,6 @@ import gitbucket.core.controller.Context
import gitbucket.core.model.Account
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.util._
import gitbucket.core.util.SyntaxSugars._
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.treewalk.CanonicalTreeParser
import org.eclipse.jgit.lib._
@@ -54,20 +53,19 @@ trait WikiService {
def createWikiRepository(loginAccount: Account, owner: String, repository: String): Unit =
LockUtil.lock(s"${owner}/${repository}/wiki") {
defining(Directory.getWikiRepositoryDir(owner, repository)) { dir =>
if (!dir.exists) {
JGitUtil.initRepository(dir)
saveWikiPage(
owner,
repository,
"Home",
"Home",
s"Welcome to the ${repository} wiki!!",
loginAccount,
"Initial Commit",
None
)
}
val dir = Directory.getWikiRepositoryDir(owner, repository)
if (!dir.exists) {
JGitUtil.initRepository(dir)
saveWikiPage(
owner,
repository,
"Home",
"Home",
s"Welcome to the ${repository} wiki!!",
loginAccount,
"Initial Commit",
None
)
}
}
@@ -77,13 +75,15 @@ trait WikiService {
def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = {
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
if (!JGitUtil.isEmpty(git)) {
JGitUtil.getFileList(git, "master", ".").find(_.name == pageName + ".md").map { file =>
val fileName = pageName + ".md"
JGitUtil.getLatestCommitFromPath(git, fileName, "master").map { latestCommit =>
val content = JGitUtil.getContentFromPath(git, latestCommit.getTree, fileName, true)
WikiPageInfo(
file.name,
StringUtil.convertFromByteArray(git.getRepository.open(file.id).getBytes),
file.author,
file.time,
file.commitId
fileName,
StringUtil.convertFromByteArray(content.getOrElse(Array.empty)),
latestCommit.getAuthorIdent.getName,
latestCommit.getAuthorIdent.getWhen,
latestCommit.getName
)
}
} else None
@@ -147,7 +147,7 @@ trait WikiService {
if (!p.getErrors.isEmpty) {
throw new PatchFormatException(p.getErrors())
}
val revertInfo = (p.getFiles.asScala.map { fh =>
val revertInfo = p.getFiles.asScala.flatMap { fh =>
fh.getChangeType match {
case DiffEntry.ChangeType.MODIFY => {
val source =
@@ -176,7 +176,7 @@ trait WikiService {
}
case _ => Nil
}
}).flatten
}
if (revertInfo.nonEmpty) {
val builder = DirCache.newInCore.builder()
@@ -257,8 +257,7 @@ trait WikiService {
created = false
updated = JGitUtil
.getContentFromId(git, tree.getEntryObjectId, true)
.map(new String(_, "UTF-8") != content)
.getOrElse(false)
.exists(new String(_, "UTF-8") != content)
}
}
}

View File

@@ -19,8 +19,9 @@ class ApiAuthenticationFilter extends Filter with AccessTokenService with Accoun
override def destroy(): Unit = {}
override def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = {
implicit val request = req.asInstanceOf[HttpServletRequest]
implicit val session = req.getAttribute(Keys.Request.DBSession).asInstanceOf[slick.jdbc.JdbcBackend#Session]
implicit val request: HttpServletRequest = req.asInstanceOf[HttpServletRequest]
implicit val session: slick.jdbc.JdbcBackend#Session =
req.getAttribute(Keys.Request.DBSession).asInstanceOf[slick.jdbc.JdbcBackend#Session]
val response = res.asInstanceOf[HttpServletResponse]
Option(request.getHeader("Authorization"))
.map {

View File

@@ -139,7 +139,7 @@ class GitAuthenticationFilter extends Filter with RepositoryService with Account
case _ =>
() =>
{
logger.debug(s"Not enough path arguments: ${request.paths}")
logger.debug(s"Not enough path arguments: ${request.paths.mkString(", ")}")
response.sendError(HttpServletResponse.SC_NOT_FOUND)
}
}

View File

@@ -6,6 +6,7 @@ import javax.servlet.http.{HttpServlet, HttpServletRequest, HttpServletResponse}
import gitbucket.core.util.{FileUtil, StringUtil}
import org.apache.commons.io.{FileUtils, IOUtils}
import org.json4s.Formats
import org.json4s.jackson.Serialization._
import org.apache.http.HttpStatus
@@ -17,7 +18,7 @@ import scala.util.Using
*/
class GitLfsTransferServlet extends HttpServlet {
private implicit val jsonFormats = gitbucket.core.api.JsonFormat.jsonFormats
private implicit val jsonFormats: Formats = gitbucket.core.api.JsonFormat.jsonFormats
private val LongObjectIdLength = 32
private val LongObjectIdStringLength = LongObjectIdLength * 2

View File

@@ -6,12 +6,12 @@ import java.util.Date
import scala.util.Using
import gitbucket.core.api
import gitbucket.core.api.JsonFormat.Context
import gitbucket.core.model.WebHook
import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
import gitbucket.core.service.IssuesService.IssueSearchCondition
import gitbucket.core.service.WebHookService._
import gitbucket.core.service._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.Implicits._
import gitbucket.core.util._
import gitbucket.core.model.Profile.profile.blockingApi._
@@ -42,6 +42,7 @@ import javax.servlet.ServletConfig
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
import org.eclipse.jgit.diff.DiffEntry.ChangeType
import org.eclipse.jgit.internal.storage.file.FileRepository
import org.json4s.Formats
import org.json4s.jackson.Serialization._
/**
@@ -53,7 +54,7 @@ import org.json4s.jackson.Serialization._
class GitRepositoryServlet extends GitServlet with SystemSettingsService {
private val logger = LoggerFactory.getLogger(classOf[GitRepositoryServlet])
private implicit val jsonFormats = gitbucket.core.api.JsonFormat.jsonFormats
private implicit val jsonFormats: Formats = gitbucket.core.api.JsonFormat.jsonFormats
override def init(config: ServletConfig): Unit = {
setReceivePackFactory(new GitBucketReceivePackFactory())
@@ -200,33 +201,28 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
logger.debug("requestURI: " + request.getRequestURI)
logger.debug("pusher:" + pusher)
defining(request.paths) { paths =>
val owner = paths(1)
val repository = paths(2).stripSuffix(".git")
val paths = request.paths
val owner = paths(1)
val repository = paths(2).stripSuffix(".git")
logger.debug("repository:" + owner + "/" + repository)
logger.debug("repository:" + owner + "/" + repository)
val settings = loadSystemSettings()
val baseUrl = settings.baseUrl(request)
val sshUrl = settings.sshAddress.map { x =>
s"${x.genericUser}@${x.host}:${x.port}"
}
val settings = loadSystemSettings()
val baseUrl = settings.baseUrl(request)
val sshUrl = settings.sshAddress.map { x =>
s"${x.genericUser}@${x.host}:${x.port}"
}
if (!repository.endsWith(".wiki")) {
defining(request) { implicit r =>
val hook = new CommitLogHook(owner, repository, pusher, baseUrl, sshUrl)
receivePack.setPreReceiveHook(hook)
receivePack.setPostReceiveHook(hook)
}
}
if (!repository.endsWith(".wiki")) {
val hook = new CommitLogHook(owner, repository, pusher, baseUrl, sshUrl)
receivePack.setPreReceiveHook(hook)
receivePack.setPostReceiveHook(hook)
}
if (repository.endsWith(".wiki")) {
defining(request) { implicit r =>
receivePack.setPostReceiveHook(
new WikiCommitHook(owner, repository.stripSuffix(".wiki"), pusher, baseUrl, sshUrl)
)
}
}
if (repository.endsWith(".wiki")) {
receivePack.setPostReceiveHook(
new WikiCommitHook(owner, repository.stripSuffix(".wiki"), pusher, baseUrl, sshUrl)
)
}
}
@@ -293,7 +289,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
val pushedIds = scala.collection.mutable.Set[String]()
commands.asScala.foreach { command =>
logger.debug(s"commandType: ${command.getType}, refName: ${command.getRefName}")
implicit val apiContext = api.JsonFormat.Context(baseUrl, sshUrl)
implicit val apiContext: Context = api.JsonFormat.Context(baseUrl, sshUrl)
val refName = command.getRefName.split("/")
val branchName = refName.drop(2).mkString("/")
val commits = if (refName(1) == "tags") {
@@ -351,9 +347,9 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
// set PR as merged
val pulls = getPullRequestsByBranch(owner, repository, branchName, Some(false))
pulls.foreach { pull =>
if (commits.find { c =>
if (commits.exists { c =>
c.id == pull.commitIdTo
}.isDefined) {
}) {
markMergeAndClosePullRequest(pusher, owner, repository, pull)
getAccountByUserName(pusher).foreach { pusherAccount =>
callPullRequestWebHook("closed", repositoryInfo, pull.issueId, pusherAccount, settings)
@@ -469,7 +465,7 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl:
Database() withTransaction { implicit session =>
try {
commands.asScala.headOption.foreach { command =>
implicit val apiContext = api.JsonFormat.Context(baseUrl, sshUrl)
implicit val apiContext: Context = api.JsonFormat.Context(baseUrl, sshUrl)
val refName = command.getRefName.split("/")
val commitIds = if (refName(1) == "tags") {
None

View File

@@ -2,7 +2,6 @@ package gitbucket.core.servlet
import java.io.{File, FileOutputStream}
import com.typesafe.config.ConfigFactory
import gitbucket.core.GitBucketCoreModule
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service.SystemSettingsService

View File

@@ -1,7 +1,6 @@
package gitbucket.core.servlet
import javax.servlet._
import javax.servlet.http.HttpServletRequest
import gitbucket.core.controller.ControllerBase
import gitbucket.core.plugin.PluginRegistry

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