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

View File

@@ -5,13 +5,14 @@ on: [push, pull_request]
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 30
strategy: strategy:
matrix: matrix:
java: [8, 11] java: [8, 11, 17]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Cache - name: Cache
uses: actions/cache@v2 uses: actions/cache@v2.1.6
env: env:
cache-name: cache-sbt-libs cache-name: cache-sbt-libs
with: with:
@@ -21,11 +22,14 @@ jobs:
~/.cache/coursier/v1 ~/.cache/coursier/v1
key: build-${{ env.cache-name }}-${{ hashFiles('build.sbt') }} key: build-${{ env.cache-name }}-${{ hashFiles('build.sbt') }}
- name: Set up JDK - name: Set up JDK
uses: actions/setup-java@v1 uses: actions/setup-java@v2
with: with:
java-version: ${{ matrix.java }} java-version: ${{ matrix.java }}
distribution: adopt
- name: Run tests - name: Run tests
run: sbt scalafmtSbtCheck scalafmtCheck test:scalafmtCheck test run: sbt scalafmtSbtCheck scalafmtCheck test:scalafmtCheck test
- name: Scala 3
run: sbt '++ 3.0.1!' update # TODO
- name: Build executable - name: Build executable
run: sbt executable run: sbt executable
- name: Upload artifacts - 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 # Changelog
All changes to the project will be documented in this file. 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 ### 4.35.0 - 25 Dec 2020
- Editor and source viewer color theme - Editor and source viewer color theme
- Auto completion for issues and pull requests - 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 - High extensibility by plugins
- API compatibility with GitHub - 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/). 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 Features
@@ -22,8 +24,6 @@ The current version of GitBucket provides many features such as:
- Account and group management with LDAP integration - Account and group management with LDAP integration
- a Plug-in system - 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 Installation
-------- --------
GitBucket requires **Java8**. You have to install it, if it is not already installed. 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/). 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 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 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. - 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 ### 4.36.1 - 22 Jul 2021
- Auto completion for issues and pull requests - Bump gitbucket-gist-plugin to 4.21.0
- Upload image from clipboard
- Close multiple issues by commit comment ### 4.36.0 - 17 Jul 2021
- Create pull request from online editor - Tag selector in the repository viewer
- Milestone overview - Link issues/pull requests of other repositories
- Commit status at various places - Files and lines can be linked in the diff view
- WebAPI coverage improvements - Option to disable XSS protection
See the [change log](CHANGELOG.md) for all of the updates. 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.license.{DepModuleInfo, LicenseInfo}
import com.typesafe.sbt.pgp.PgpKeys._ import com.jsuereth.sbtpgp.PgpKeys._
val Organization = "io.github.gitbucket" val Organization = "io.github.gitbucket"
val Name = "gitbucket" val Name = "gitbucket"
val GitBucketVersion = "4.35.0" val GitBucketVersion = "4.36.2"
val ScalatraVersion = "2.7.0" val ScalatraVersion = "2.8.2"
val JettyVersion = "9.4.32.v20200930" val JettyVersion = "9.4.44.v20210927"
val JgitVersion = "5.9.0.202009080501-r" val JgitVersion = "5.13.0.202109080827-r"
lazy val root = (project in file(".")) lazy val root = (project in file("."))
.enablePlugins(SbtTwirl, ScalatraPlugin) .enablePlugins(SbtTwirl, ScalatraPlugin)
.settings(
)
sourcesInBase := false sourcesInBase := false
organization := Organization organization := Organization
name := Name name := Name
version := GitBucketVersion version := GitBucketVersion
scalaVersion := "2.13.1" scalaVersion := "2.13.7"
scalafmtOnCompile := true scalafmtOnCompile := true
@@ -33,64 +31,80 @@ resolvers ++= Seq(
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % JgitVersion, "org.eclipse.jgit" % "org.eclipse.jgit.http.server" % JgitVersion,
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % JgitVersion, "org.eclipse.jgit" % "org.eclipse.jgit.archive" % JgitVersion,
"org.scalatra" %% "scalatra" % ScalatraVersion, "org.scalatra" %% "scalatra" % ScalatraVersion cross CrossVersion.for3Use2_13,
"org.scalatra" %% "scalatra-json" % ScalatraVersion, "org.scalatra" %% "scalatra-json" % ScalatraVersion cross CrossVersion.for3Use2_13,
"org.scalatra" %% "scalatra-forms" % ScalatraVersion, "org.scalatra" %% "scalatra-forms" % ScalatraVersion cross CrossVersion.for3Use2_13,
"org.json4s" %% "json4s-jackson" % "3.6.10", "org.json4s" %% "json4s-jackson" % "4.0.3" cross CrossVersion.for3Use2_13,
"commons-io" % "commons-io" % "2.8.0", "commons-io" % "commons-io" % "2.11.0",
"io.github.gitbucket" % "solidbase" % "1.0.3", "io.github.gitbucket" % "solidbase" % "1.0.3",
"io.github.gitbucket" % "markedj" % "1.0.16", "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", "org.apache.commons" % "commons-email" % "1.5",
"commons-net" % "commons-net" % "3.7", "commons-net" % "commons-net" % "3.8.0",
"org.apache.httpcomponents" % "httpclient" % "4.5.12", "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.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", "org.apache.tika" % "tika-core" % "2.1.0",
"com.github.takezoe" %% "blocking-slick-32" % "0.0.12", "com.github.takezoe" %% "blocking-slick-32" % "0.0.12" cross CrossVersion.for3Use2_13,
"com.novell.ldap" % "jldap" % "2009-10-07", "com.novell.ldap" % "jldap" % "2009-10-07",
"com.h2database" % "h2" % "1.4.199", "com.h2database" % "h2" % "1.4.199",
"org.mariadb.jdbc" % "mariadb-java-client" % "2.7.0", "org.mariadb.jdbc" % "mariadb-java-client" % "2.7.4",
"org.postgresql" % "postgresql" % "42.2.6", "org.postgresql" % "postgresql" % "42.3.1",
"ch.qos.logback" % "logback-classic" % "1.2.3", "ch.qos.logback" % "logback-classic" % "1.2.6",
"com.zaxxer" % "HikariCP" % "3.4.5", "com.zaxxer" % "HikariCP" % "4.0.3" exclude ("org.slf4j", "slf4j-api"),
"com.typesafe" % "config" % "1.4.0", "com.typesafe" % "config" % "1.4.1",
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.1.0", "fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.1.0",
"com.github.bkromhout" % "java-diff-utils" % "2.1.1", "io.github.java-diff-utils" % "java-diff-utils" % "4.11",
"org.cache2k" % "cache2k-all" % "1.2.4.Final", "org.cache2k" % "cache2k-all" % "1.6.0.Final",
"net.coobird" % "thumbnailator" % "0.4.12", "net.coobird" % "thumbnailator" % "0.4.14",
"com.github.zafarkhaja" % "java-semver" % "0.9.0", "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", "org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided", "javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
"junit" % "junit" % "4.13" % "test", "junit" % "junit" % "4.13.2" % "test",
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test", "org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test" cross CrossVersion.for3Use2_13,
"org.mockito" % "mockito-core" % "3.3.3" % "test", "org.mockito" % "mockito-core" % "4.0.0" % "test",
"com.dimafeng" %% "testcontainers-scala" % "0.37.0" % "test", "com.dimafeng" %% "testcontainers-scala" % "0.39.11" % "test",
"org.testcontainers" % "mysql" % "1.14.3" % "test", "org.testcontainers" % "mysql" % "1.16.2" % "test",
"org.testcontainers" % "postgresql" % "1.14.3" % "test", "org.testcontainers" % "postgresql" % "1.16.2" % "test",
"net.i2p.crypto" % "eddsa" % "0.3.0", "net.i2p.crypto" % "eddsa" % "0.3.0",
"is.tagomor.woothee" % "woothee-java" % "1.11.0", "is.tagomor.woothee" % "woothee-java" % "1.11.0",
"org.ec4j.core" % "ec4j-core" % "0.0.3", "org.ec4j.core" % "ec4j-core" % "0.3.0",
"org.kohsuke" % "github-api" % "1.116" % "test" "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 // Compiler settings
scalacOptions := Seq("-deprecation", "-language:postfixOps", "-opt:l:method") scalacOptions := Seq(
javacOptions in compile ++= Seq("-target", "8", "-source", "8") "-deprecation",
javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml" "-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 // Test settings
//testOptions in Test += Tests.Argument("-l", "ExternalDBTest") //testOptions in Test += Tests.Argument("-l", "ExternalDBTest")
javaOptions in Test += "-Dgitbucket.home=target/gitbucket_home_for_test" Test / javaOptions += "-Dgitbucket.home=target/gitbucket_home_for_test"
testOptions in Test += Tests.Setup(() => new java.io.File("target/gitbucket_home_for_test").mkdir()) Test / testOptions += Tests.Setup(() => new java.io.File("target/gitbucket_home_for_test").mkdir())
fork in Test := true Test / fork := true
// Packaging options // Packaging options
packageOptions += Package.MainClass("JettyLauncher") packageOptions += Package.MainClass("JettyLauncher")
// Assembly settings // Assembly settings
test in assembly := {} assembly / test := {}
assemblyMergeStrategy in assembly := { assembly / assemblyMergeStrategy := {
case PathList("META-INF", xs @ _*) => case PathList("META-INF", xs @ _*) =>
(xs map { _.toLowerCase }) match { (xs map { _.toLowerCase }) match {
case ("manifest.mf" :: Nil) => MergeStrategy.discard case ("manifest.mf" :: Nil) => MergeStrategy.discard
@@ -122,9 +136,9 @@ libraryDependencies ++= Seq(
) )
// Run package task before test to generate target/webapp for integration test // Run package task before test to generate target/webapp for integration test
test in Test := { Test / test := {
_root_.sbt.Keys.`package`.value _root_.sbt.Keys.`package`.value
(test in Test).value (Test / test).value
} }
val executableKey = TaskKey[File]("executable") val executableKey = TaskKey[File]("executable")
@@ -155,7 +169,7 @@ executableKey := {
IO unzip (warFile, temp) IO unzip (warFile, temp)
// include launcher classes // include launcher classes
val classDir = (Keys.classDirectory in Compile).value val classDir = (Compile / Keys.classDirectory).value
val launchClasses = Seq("JettyLauncher.class" /*, "HttpsSupportConnector.class" */ ) val launchClasses = Seq("JettyLauncher.class" /*, "HttpsSupportConnector.class" */ )
launchClasses foreach { name => launchClasses foreach { name =>
IO copyFile (classDir / name, temp / name) IO copyFile (classDir / name, temp / name)
@@ -186,7 +200,7 @@ executableKey := {
manifest.getMainAttributes put (AttrName.MANIFEST_VERSION, "1.0") manifest.getMainAttributes put (AttrName.MANIFEST_VERSION, "1.0")
manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher") manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher")
val outputFile = workDir / warName 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 // generate checksums
Seq( Seq(
@@ -258,12 +272,7 @@ pomExtra := (
</developers> </developers>
) )
licenseOverrides := { Test / testOptions ++= {
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 ++= {
if (scala.util.Properties.isWin) { if (scala.util.Properties.isWin) {
Seq( Seq(
Tests.Exclude( Tests.Exclude(
@@ -276,3 +285,8 @@ testOptions in Test ++= {
Nil 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. 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`. it references three authenticators: `ReadableUsersAuthenticator`, `ReferrerAuthenticator` and `CollaboratorsAuthenticator`.
```scala ```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 - `ReadableUsersAuthenticator` provides `readableUsersOnly` method
- `ReferrerAuthenticator` provides `referrersOnly` method - `ReferrerAuthenticator` provides `referrersOnly` method
- `CollaboratorsAuthenticator` provides `collaboratorsOnly` method - `CollaboratorsAuthenticator` provides `collaboratorsOnly` method
These methods are available in each actions as below: These methods are available in each action as below:
```scala ```scala
// Allows only the repository owner (or manager for group repository) and administrators. // 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) => 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. | |OneselfAuthenticator |oneselfOnly |Allows only oneself and administrators. |
|OwnerAuthenticator |ownerOnly |Allows only the repository owner 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. | |AdminAuthenticator |adminOnly |Allows only administrators. |
|CollaboratorsAuthenticator|collaboratorsOnly|Allows only collaborators and administrators. | |CollaboratorsAuthenticator|collaboratorsOnly|Allows only collaborators and administrators. |
|ReferrerAuthenticator |referrersOnly |Allows only the repository owner (or manager for group repository) 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. | |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. 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 ```scala
object GitBucketCoreModule extends Module("gitbucket-core", 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
<?xml version="1.0" encoding="UTF-8"?> <?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> </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) 1. Specified path (if specified)
2. `${moduleId}_${version}_${database}.sql` 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`. 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 Build war file
-------- --------
To build war file, run the following command: To build a war file, run the following command:
```shell ```shell
$ sbt package $ 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") scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.1") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3")
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.0") addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.1")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0") addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "1.1.0")
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.4") addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.4")
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2") addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2")
addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0") 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.9.2")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1")
addDependencyTreePlugin

View File

@@ -1,51 +1,100 @@
import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server; 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.handler.StatisticsHandler;
import org.eclipse.jetty.server.session.DefaultSessionCache; import org.eclipse.jetty.server.session.DefaultSessionCache;
import org.eclipse.jetty.server.session.FileSessionDataStore; import org.eclipse.jetty.server.session.FileSessionDataStore;
import org.eclipse.jetty.server.session.SessionCache; import org.eclipse.jetty.server.session.SessionCache;
import org.eclipse.jetty.server.session.SessionHandler; import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.webapp.WebAppContext;
import java.io.File; import java.io.File;
import java.net.InetAddress;
import java.net.URL; import java.net.URL;
import java.net.InetSocketAddress;
import java.security.ProtectionDomain; 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 { 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 { public static void main(String[] args) throws Exception {
System.setProperty("java.awt.headless", "true"); System.setProperty("java.awt.headless", "true");
String host = null; String connectors = getEnvironmentVariable("gitbucket.connectors");
String port = null; String host = getEnvironmentVariable("gitbucket.host");
InetSocketAddress address = null; String port = getEnvironmentVariable("gitbucket.port");
String contextPath = "/"; String securePort = getEnvironmentVariable("gitbucket.securePort");
String tmpDirPath=""; String keyStorePath = getEnvironmentVariable("gitbucket.keyStorePath");
boolean forceHttps = false; 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; boolean saveSessions = false;
host = getEnvironmentVariable("gitbucket.host");
port = getEnvironmentVariable("gitbucket.port");
contextPath = getEnvironmentVariable("gitbucket.prefix");
tmpDirPath = getEnvironmentVariable("gitbucket.tempDir");
for(String arg: args) { for(String arg: args) {
if(arg.equals("--save_sessions")) { if(arg.equals("--save_sessions")) {
saveSessions = true; saveSessions = true;
} }
if(arg.startsWith("--") && arg.contains("=")) { if(arg.startsWith("--") && arg.contains("=")) {
String[] dim = arg.split("="); String[] dim = arg.split("=", 2);
if(dim.length >= 2) { if(dim.length == 2) {
switch (dim[0]) { switch (dim[0]) {
case "--connectors":
connectors = dim[1];
break;
case "--host": case "--host":
host = dim[1]; host = dim[1];
break; break;
case "--port": case "--port":
port = dim[1]; port = dim[1];
break; 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": case "--prefix":
contextPath = dim[1]; contextPath = dim[1];
break; break;
@@ -67,38 +116,69 @@ public class JettyLauncher {
contextPath = "/" + contextPath; contextPath = "/" + contextPath;
} }
if(host != null) { final String hostName = InetAddress.getByName(fallback(host, Defaults.HOST)).getHostName();
address = new InetSocketAddress(host, getPort(port));
} else { final Server server = new Server();
address = new InetSocketAddress(getPort(port));
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(); connectorInstances.add(connector);
// 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);
}
}
} }
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(); WebAppContext context = new WebAppContext();
if(saveSessions) { if(saveSessions) {
File sessDir = new File(getGitBucketHome(), "sessions"); File sessDir = new File(getGitBucketHome(), "sessions");
if(!sessDir.exists()){ if(!sessDir.exists()){
sessDir.mkdirs(); mkdir(sessDir);
} }
SessionHandler sessions = context.getSessionHandler(); SessionHandler sessions = context.getSessionHandler();
SessionCache cache = new DefaultSessionCache(sessions); SessionCache cache = new DefaultSessionCache(sessions);
@@ -112,7 +192,7 @@ public class JettyLauncher {
if(tmpDirPath == null || tmpDirPath.equals("")){ if(tmpDirPath == null || tmpDirPath.equals("")){
tmpDir = new File(getGitBucketHome(), "tmp"); tmpDir = new File(getGitBucketHome(), "tmp");
if(!tmpDir.exists()){ if(!tmpDir.exists()){
tmpDir.mkdirs(); mkdir(tmpDir);
} }
} else { } else {
tmpDir = new File(tmpDirPath); tmpDir = new File(tmpDirPath);
@@ -136,13 +216,16 @@ public class JettyLauncher {
context.setDescriptor(location.toExternalForm() + "/WEB-INF/web.xml"); context.setDescriptor(location.toExternalForm() + "/WEB-INF/web.xml");
context.setServer(server); context.setServer(server);
context.setWar(location.toExternalForm()); 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.setStopAtShutdown(true);
server.setStopTimeout(7_000); server.setStopTimeout(7_000);
server.start(); server.start();
@@ -170,11 +253,28 @@ public class JettyLauncher {
} }
} }
private static int getPort(String port){ private static <T, R> T fallback(R value, T defaultValue, Function<R, T> converter) {
if(port == null) { return value == null ? defaultValue : converter.apply(value);
return 8080; }
} else {
return Integer.parseInt(port); 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) public static String apply(String source, String patch, FileHeader fh)
throws IOException, PatchApplyException { throws IOException, PatchApplyException {
RawText rt = new RawText(source.getBytes("UTF-8")); 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++) for (int i = 0; i < rt.size(); i++)
oldLines.add(rt.getString(i)); oldLines.add(rt.getString(i));
List<String> newLines = new ArrayList<String>(oldLines); List<String> newLines = new ArrayList<>(oldLines);
for (HunkHeader hh : fh.getHunks()) { for (HunkHeader hh : fh.getHunks()) {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(patch.getBytes("UTF-8"), hh.getStartOffset(), hh.getEndOffset() - hh.getStartOffset()); out.write(patch.getBytes("UTF-8"), hh.getStartOffset(), hh.getEndOffset() - hh.getStartOffset());
RawText hrt = new RawText(out.toByteArray()); 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++) for (int i = 0; i < hrt.size(); i++)
hunkLines.add(hrt.getString(i)); hunkLines.add(hrt.getString(i));
int pos = 0; int pos = 0;

View File

@@ -1,4 +1,4 @@
notifications:1.9.0 notifications:1.10.0
gist:4.20.0 gist:4.21.0
emoji:4.6.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"?> <?xml version="1.0" encoding="UTF-8"?>
<changeSet> <changeSet>
<!--================================================================================================-->
<!-- WEB_HOOK -->
<!--================================================================================================-->
<dropForeignKeyConstraint constraintName="IDX_WEB_HOOK_EVENT_FK0" baseTableName="WEB_HOOK_EVENT"/> <dropForeignKeyConstraint constraintName="IDX_WEB_HOOK_EVENT_FK0" baseTableName="WEB_HOOK_EVENT"/>
<dropForeignKeyConstraint constraintName="IDX_WEB_HOOK_FK0" baseTableName="WEB_HOOK"/> <dropForeignKeyConstraint constraintName="IDX_WEB_HOOK_FK0" baseTableName="WEB_HOOK"/>
<dropPrimaryKey tableName="WEB_HOOK" constraintName="IDX_WEB_HOOK_PK"/> <dropPrimaryKey constraintName="IDX_WEB_HOOK_PK" tableName="WEB_HOOK"/>
<addColumn tableName="WEB_HOOK">
<column name="HOOK_ID" type="int" nullable="false" unique="true"/> <createTable tableName="WEB_HOOK_2">
</addColumn> <column name="HOOK_ID" type="int" nullable="true" autoIncrement="true" unique="false" primaryKeyName="IDX_WEB_HOOK_PK" primaryKey="true" />
<addPrimaryKey constraintName="IDX_WEB_HOOK_PK" tableName="WEB_HOOK" columnNames="USER_NAME, REPOSITORY_NAME, URL, HOOK_ID"/> <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"/> <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_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"/> <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"> <createTable tableName="ACCOUNT_PREFERENCE">
<column name="USER_NAME" type="varchar(100)" nullable="false"/> <column name="USER_NAME" type="varchar(100)" nullable="false"/>
<column name="HIGHLIGHTER_THEME" type="varchar(100)" nullable="false" defaultValue="prettify"/> <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 javax.servlet._
import gitbucket.core.controller.{ReleaseController, _} import gitbucket.core.controller.{ReleaseController, _}
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service.SystemSettingsService import gitbucket.core.service.SystemSettingsService
import gitbucket.core.servlet._ import gitbucket.core.servlet._
import gitbucket.core.util.Directory import gitbucket.core.util.Directory

View File

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

View File

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

View File

@@ -17,7 +17,8 @@ case class ApiIssue(
state: String, state: String,
created_at: Date, created_at: Date,
updated_at: Date, updated_at: Date,
body: String body: String,
milestone: Option[ApiMilestone]
)(repositoryName: RepositoryName, isPullRequest: Boolean) { )(repositoryName: RepositoryName, isPullRequest: Boolean) {
val id = 0 // dummy id val id = 0 // dummy id
val assignees = List(assignee).flatten val assignees = List(assignee).flatten
@@ -43,7 +44,8 @@ object ApiIssue {
repositoryName: RepositoryName, repositoryName: RepositoryName,
user: ApiUser, user: ApiUser,
assignee: Option[ApiUser], assignee: Option[ApiUser],
labels: List[ApiLabel] labels: List[ApiLabel],
milestone: Option[ApiMilestone]
): ApiIssue = ): ApiIssue =
ApiIssue( ApiIssue(
number = issue.issueId, number = issue.issueId,
@@ -51,6 +53,7 @@ object ApiIssue {
user = user, user = user,
assignee = assignee, assignee = assignee,
labels = labels, labels = labels,
milestone = milestone,
state = if (issue.closed) { "closed" } else { "open" }, state = if (issue.closed) { "closed" } else { "open" },
body = issue.content.getOrElse(""), body = issue.content.getOrElse(""),
created_at = issue.registeredDate, created_at = issue.registeredDate,

View File

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

View File

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

View File

@@ -86,7 +86,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
val newForm = mapping( val newForm = mapping(
"userName" -> trim(label("User name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))), "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)))), "fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))), "mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))),
"extraMailAddresses" -> list( "extraMailAddresses" -> list(
@@ -98,7 +98,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
)(AccountNewForm.apply) )(AccountNewForm.apply)
val editForm = mapping( 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)))), "fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))), "mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))),
"extraMailAddresses" -> list( "extraMailAddresses" -> list(
@@ -434,7 +434,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
post("/:userName/_personalToken", personalTokenForm)(oneselfOnly { form => post("/:userName/_personalToken", personalTokenForm)(oneselfOnly { form =>
val userName = params("userName") val userName = params("userName")
getAccountByUserName(userName).map { x => getAccountByUserName(userName).foreach { x =>
val (tokenId, token) = generateAccessToken(userName, form.note) val (tokenId, token) = generateAccessToken(userName, form.note)
flash.update("generatedToken", (tokenId, token)) flash.update("generatedToken", (tokenId, token))
} }
@@ -629,7 +629,9 @@ trait AccountControllerBase extends AccountManagementControllerBase {
} }
get("/groups/new")(usersOnly { 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 => post("/groups/new", newGroupForm)(usersOnly { form =>
@@ -650,22 +652,20 @@ trait AccountControllerBase extends AccountManagementControllerBase {
}) })
get("/:groupName/_editgroup")(managersOnly { get("/:groupName/_editgroup")(managersOnly {
defining(params("groupName")) { groupName => val groupName = params("groupName")
getAccountByUserName(groupName, true).map { account => getAccountByUserName(groupName, true).map { account =>
html.editgroup(account, getGroupMembers(groupName), flash.get("info")) html.editgroup(account, getGroupMembers(groupName), flash.get("info"))
} getOrElse NotFound() } getOrElse NotFound()
}
}) })
get("/:groupName/_deletegroup")(managersOnly { get("/:groupName/_deletegroup")(managersOnly {
defining(params("groupName")) { val groupName = params("groupName")
groupName => // Remove from GROUP_MEMBER
// Remove from GROUP_MEMBER updateGroupMembers(groupName, Nil)
updateGroupMembers(groupName, Nil) // Disable group
// Disable group getAccountByUserName(groupName, false).foreach { account =>
getAccountByUserName(groupName, false).foreach { account => updateGroup(groupName, account.description, account.url, true)
updateGroup(groupName, account.description, account.url, true) }
}
// // Remove repositories // // Remove repositories
// getRepositoryNamesOfUser(groupName).foreach { repositoryName => // getRepositoryNamesOfUser(groupName).foreach { repositoryName =>
// deleteRepository(groupName, repositoryName) // deleteRepository(groupName, repositoryName)
@@ -673,28 +673,25 @@ trait AccountControllerBase extends AccountManagementControllerBase {
// FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName)) // FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName))
// FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName)) // FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName))
// } // }
}
redirect("/") redirect("/")
}) })
post("/:groupName/_editgroup", editGroupForm)(managersOnly { form => post("/:groupName/_editgroup", editGroupForm)(managersOnly { form =>
defining( val groupName = params("groupName")
params("groupName"), val members = form.members
form.members .split(",")
.split(",") .map {
.map { _.split(":") match {
_.split(":") match { case Array(userName, isManager) => (userName, isManager.toBoolean)
case Array(userName, isManager) => (userName, isManager.toBoolean)
}
} }
.toList }
) { .toList
case (groupName, members) =>
getAccountByUserName(groupName, true).map { account =>
updateGroup(groupName, form.description, form.url, false)
// Update GROUP_MEMBER getAccountByUserName(groupName, true).map { account =>
updateGroupMembers(form.groupName, members) updateGroup(groupName, form.description, form.url, false)
// Update GROUP_MEMBER
updateGroupMembers(form.groupName, members)
// // Update COLLABORATOR for group repositories // // Update COLLABORATOR for group repositories
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName => // getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
// removeCollaborators(form.groupName, repositoryName) // removeCollaborators(form.groupName, repositoryName)
@@ -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.") flash.update("info", "Account information has been updated.")
redirect(s"/${groupName}/_editgroup") redirect(s"/${groupName}/_editgroup")
} getOrElse NotFound() } getOrElse NotFound()
}
}) })
/** /**
* Show the new repository form. * Show the new repository form.
*/ */
get("/new")(usersOnly { 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. * Create new repository.
*/ */
post("/new", newRepositoryForm)(usersOnly { form => post("/new", newRepositoryForm)(usersOnly { form =>
if (context.settings.repositoryOperation.create || context.loginAccount.get.isAdmin) { context.withLoginAccount {
LockUtil.lock(s"${form.owner}/${form.name}") { loginAccount =>
if (getRepository(form.owner, form.name).isEmpty) { if (context.settings.repositoryOperation.create || loginAccount.isAdmin) {
createRepository( LockUtil.lock(s"${form.owner}/${form.name}") {
context.loginAccount.get, if (getRepository(form.owner, form.name).isDefined) {
form.owner, // redirect to the repository if repository already exists
form.name, redirect(s"/${form.owner}/${form.name}")
form.description, } else if (!canCreateRepository(form.owner, loginAccount)) {
form.isPrivate, // Permission error
form.initOption, Forbidden()
form.sourceUrl } else {
) // create repository asynchronously
} createRepository(
} loginAccount,
// redirect to the repository form.owner,
redirect(s"/${form.owner}/${form.name}") form.name,
} else Forbidden() 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 => get("/:owner/:repository/fork")(readableUsersOnly { repository =>
val loginAccount = context.loginAccount.get context.withLoginAccount {
if (repository.repository.options.allowFork && (context.settings.repositoryOperation.fork || loginAccount.isAdmin)) { loginAccount =>
val loginUserName = loginAccount.userName if (repository.repository.options.allowFork && (context.settings.repositoryOperation.fork || loginAccount.isAdmin)) {
val groups = getGroupsByUserName(loginUserName) val loginUserName = loginAccount.userName
groups match { val groups = getGroupsByUserName(loginUserName)
case _: List[String] => groups match {
val managerPermissions = groups.map { group => case _: List[String] =>
val members = getGroupMembers(group) val managerPermissions = groups.map { group =>
context.loginAccount.exists( val members = getGroupMembers(group)
x => context.loginAccount.exists(
members.exists { member => x =>
member.userName == x.userName && member.isManager 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( } else BadRequest()
repository, }
(groups zip managerPermissions).sortBy(_._1)
)
case _ => redirect(s"/${loginUserName}")
}
} else BadRequest()
}) })
post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) => post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) =>
val loginAccount = context.loginAccount.get context.withLoginAccount {
if (repository.repository.options.allowFork && (context.settings.repositoryOperation.fork || loginAccount.isAdmin)) { loginAccount =>
val loginUserName = loginAccount.userName if (repository.repository.options.allowFork && (context.settings.repositoryOperation.fork || loginAccount.isAdmin)) {
val accountName = form.accountName val loginUserName = loginAccount.userName
val accountName = form.accountName
if (getRepository(accountName, repository.name).isDefined || if (getRepository(accountName, repository.name).isDefined) {
(accountName != loginUserName && !getGroupsByUserName(loginUserName).contains(accountName))) { // redirect to the repository if repository already exists
// redirect to the repository if repository already exists redirect(s"/${accountName}/${repository.name}")
redirect(s"/${accountName}/${repository.name}") } else if (!canCreateRepository(accountName, loginAccount)) {
} else { // Permission error
// fork repository asynchronously Forbidden()
forkRepository(accountName, repository, loginUserName) } else {
// redirect to the repository // fork repository asynchronously
redirect(s"/${accountName}/${repository.name}") forkRepository(accountName, repository, loginUserName)
} // redirect to the repository
} else Forbidden() redirect(s"/${accountName}/${repository.name}")
}
} else Forbidden()
}
}) })
private def existsAccount: Constraint = new Constraint() { 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.eclipse.jgit.treewalk._
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.json4s.Formats
/** /**
* Provides generic features for controller implementations. * Provides generic features for controller implementations.
@@ -43,7 +44,7 @@ abstract class ControllerBase
private val logger = LoggerFactory.getLogger(getClass) 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/*") { before("/api/v3/*") {
contentType = formats("json") contentType = formats("json")
@@ -157,11 +158,8 @@ abstract class ControllerBase
org.scalatra.Unauthorized( org.scalatra.Unauthorized(
redirect( redirect(
"/signin?redirect=" + StringUtil.urlEncode( "/signin?redirect=" + StringUtil.urlEncode(
defining(request.getQueryString) { queryString => request.getRequestURI
request.getRequestURI.substring(request.getContextPath.length) + (if (queryString != null) .substring(request.getContextPath.length) + Option(request.getQueryString).map("?" + _).getOrElse("")
"?" + queryString
else "")
}
) )
) )
) )
@@ -255,7 +253,7 @@ abstract class ControllerBase
repository: RepositoryService.RepositoryInfo repository: RepositoryService.RepositoryInfo
): Unit = { ): Unit = {
JGitUtil.getObjectLoaderFromId(git, objectId) { loader => JGitUtil.getObjectLoaderFromId(git, objectId) { loader =>
contentType = FileUtil.getSafeMimeType(path) contentType = FileUtil.getSafeMimeType(path, repository.repository.options.safeMode)
if (loader.isLarge) { if (loader.isLarge) {
response.setContentLength(loader.getSize.toInt) response.setContentLength(loader.getSize.toInt)
@@ -302,20 +300,27 @@ case class Context(
} }
val sidebarCollapse = request.getSession.getAttribute("sidebar-collapse") != null 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. * Get object from cache.
* *
* If object has not been cached with the specified key then retrieves by given action. * If object has not been cached with the specified key then retrieves by given action.
* Cached object are available during a request. * Cached object are available during a request.
*/ */
def cache[A](key: String)(action: => A): A = def cache[A](key: String)(action: => A): A = {
defining(Keys.Request.Cache(key)) { cacheKey => val cacheKey = Keys.Request.Cache(key)
Option(request.getAttribute(cacheKey).asInstanceOf[A]).getOrElse { Option(request.getAttribute(cacheKey).asInstanceOf[A]).getOrElse {
val newObject = action val newObject = action
request.setAttribute(cacheKey, newObject) request.setAttribute(cacheKey, newObject)
newObject newObject
}
} }
}
} }
@@ -421,7 +426,7 @@ trait AccountManagementControllerBase extends ControllerBase {
"new" "new"
) )
protected def reservedNames(): Constraint = new Constraint() { protected def reservedNames: Constraint = new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] = override def validate(name: String, value: String, messages: Messages): Option[String] =
if (allReservedNames.contains(value.toLowerCase)) { if (allReservedNames.contains(value.toLowerCase)) {
Some(s"${value} is reserved") Some(s"${value} is reserved")

View File

@@ -1,6 +1,7 @@
package gitbucket.core.controller package gitbucket.core.controller
import gitbucket.core.dashboard.html import gitbucket.core.dashboard.html
import gitbucket.core.model.Account
import gitbucket.core.service._ import gitbucket.core.service._
import gitbucket.core.util.{Keys, UsersAuthenticator} import gitbucket.core.util.{Keys, UsersAuthenticator}
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
@@ -34,45 +35,63 @@ trait DashboardControllerBase extends ControllerBase {
with UsersAuthenticator => with UsersAuthenticator =>
get("/dashboard/repos")(usersOnly { get("/dashboard/repos")(usersOnly {
val repos = getVisibleRepositories( context.withLoginAccount { loginAccount =>
context.loginAccount, val repos = getVisibleRepositories(
None, context.loginAccount,
withoutPhysicalInfo = true, None,
limit = context.settings.limitVisibleRepositories withoutPhysicalInfo = true,
) limit = context.settings.limitVisibleRepositories
html.repos(getGroupNames(context.loginAccount.get.userName), repos, repos) )
html.repos(getGroupNames(loginAccount.userName), repos, repos)
}
}) })
get("/dashboard/issues")(usersOnly { get("/dashboard/issues")(usersOnly {
searchIssues("created_by") context.withLoginAccount { loginAccount =>
searchIssues(loginAccount, "created_by")
}
}) })
get("/dashboard/issues/assigned")(usersOnly { get("/dashboard/issues/assigned")(usersOnly {
searchIssues("assigned") context.withLoginAccount { loginAccount =>
searchIssues(loginAccount, "assigned")
}
}) })
get("/dashboard/issues/created_by")(usersOnly { get("/dashboard/issues/created_by")(usersOnly {
searchIssues("created_by") context.withLoginAccount { loginAccount =>
searchIssues(loginAccount, "created_by")
}
}) })
get("/dashboard/issues/mentioned")(usersOnly { get("/dashboard/issues/mentioned")(usersOnly {
searchIssues("mentioned") context.withLoginAccount { loginAccount =>
searchIssues(loginAccount, "mentioned")
}
}) })
get("/dashboard/pulls")(usersOnly { get("/dashboard/pulls")(usersOnly {
searchPullRequests("created_by") context.withLoginAccount { loginAccount =>
searchPullRequests(loginAccount, "created_by")
}
}) })
get("/dashboard/pulls/created_by")(usersOnly { get("/dashboard/pulls/created_by")(usersOnly {
searchPullRequests("created_by") context.withLoginAccount { loginAccount =>
searchPullRequests(loginAccount, "created_by")
}
}) })
get("/dashboard/pulls/assigned")(usersOnly { get("/dashboard/pulls/assigned")(usersOnly {
searchPullRequests("assigned") context.withLoginAccount { loginAccount =>
searchPullRequests(loginAccount, "assigned")
}
}) })
get("/dashboard/pulls/mentioned")(usersOnly { get("/dashboard/pulls/mentioned")(usersOnly {
searchPullRequests("mentioned") context.withLoginAccount { loginAccount =>
searchPullRequests(loginAccount, "mentioned")
}
}) })
private def getOrCreateCondition(key: String, filter: String, userName: String) = { 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._ import IssuesService._
val userName = context.loginAccount.get.userName val userName = loginAccount.userName
val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName) val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName)
val userRepos = getUserRepositories(userName, true).map(repo => repo.owner -> repo.name) val userRepos = getUserRepositories(userName, true).map(repo => repo.owner -> repo.name)
val page = IssueSearchCondition.page(request) val page = IssueSearchCondition.page(request)
@@ -115,11 +134,11 @@ trait DashboardControllerBase extends ControllerBase {
) )
} }
private def searchPullRequests(filter: String) = { private def searchPullRequests(loginAccount: Account, filter: String) = {
import IssuesService._ import IssuesService._
import PullRequestService._ import PullRequestService._
val userName = context.loginAccount.get.userName val userName = loginAccount.userName
val condition = getOrCreateCondition(Keys.Session.DashboardPulls, filter, userName) val condition = getOrCreateCondition(Keys.Session.DashboardPulls, filter, userName)
val allRepos = getAllRepositories(userName) val allRepos = getAllRepositories(userName)
val page = IssueSearchCondition.page(request) 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.service.{AccountService, ReleaseService, RepositoryService}
import gitbucket.core.servlet.Database import gitbucket.core.servlet.Database
import gitbucket.core.util._ import gitbucket.core.util._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
@@ -18,6 +17,7 @@ import org.apache.commons.io.{FileUtils, IOUtils}
import scala.util.Using import scala.util.Using
import gitbucket.core.service.SystemSettingsService import gitbucket.core.service.SystemSettingsService
import slick.jdbc.JdbcBackend.Session
/** /**
* Provides Ajax based file upload functionality. * Provides Ajax based file upload functionality.
@@ -37,10 +37,10 @@ class FileUploadController
execute( execute(
{ (file, fileId) => { (file, fileId) =>
FileUtils 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 session += Keys.Session.Upload(fileId) -> file.name
}, },
FileUtil.isImage FileUtil.isImage(_)
) )
} }
@@ -49,7 +49,7 @@ class FileUploadController
execute( execute(
{ (file, fileId) => { (file, fileId) =>
FileUtils 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 session += Keys.Session.Upload(fileId) -> file.name
}, },
_ => true _ => true
@@ -65,7 +65,7 @@ class FileUploadController
getAttachedDir(params("owner"), params("repository")), getAttachedDir(params("owner"), params("repository")),
FileUtil.checkFilename(fileId + "." + FileUtil.getExtension(file.getName)) FileUtil.checkFilename(fileId + "." + FileUtil.getExtension(file.getName))
), ),
file.get file.get()
) )
}, },
_ => true _ => true
@@ -131,7 +131,7 @@ class FileUploadController
} getOrElse BadRequest() } getOrElse BadRequest()
} }
post("/release/:owner/:repository/:tag") { post("/release/:owner/:repository/*") {
setMultipartConfigForLargeFile() setMultipartConfigForLargeFile()
session session
.get(Keys.Session.LoginAccount) .get(Keys.Session.LoginAccount)
@@ -139,12 +139,12 @@ class FileUploadController
case _: Account => case _: Account =>
val owner = params("owner") val owner = params("owner")
val repository = params("repository") val repository = params("repository")
val tag = params("tag") val tag = multiParams("splat").head
execute( execute(
{ (file, fileId) => { (file, fileId) =>
FileUtils.writeByteArrayToFile( FileUtils.writeByteArrayToFile(
new File(getReleaseFilesDir(owner, repository), FileUtil.checkFilename(tag + "/" + fileId)), new File(getReleaseFilesDir(owner, repository), FileUtil.checkFilename(tag + "/" + fileId)),
file.get file.get()
) )
}, },
_ => true _ => true
@@ -178,7 +178,7 @@ class FileUploadController
} }
private def onlyWikiEditable(owner: String, repository: String, loginAccount: Account)(action: => Any): Any = { 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 { getRepository(owner, repository) match {
case Some(x) => case Some(x) =>
x.repository.options.wikiOption match { 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 { fileParams.get("file") match {
case Some(file) if (mimeTypeChcker(file.name)) => case Some(file) if mimeTypeChecker(file.name) =>
defining(FileUtil.generateFileId) { fileId => val fileId = FileUtil.generateFileId
f(file, fileId) f(file, fileId)
contentType = "text/plain" contentType = "text/plain"
Ok(fileId) Ok(fileId)
}
case _ => BadRequest() case _ => BadRequest()
} }

View File

@@ -8,7 +8,6 @@ import gitbucket.core.helper.xml
import gitbucket.core.model.Account import gitbucket.core.model.Account
import gitbucket.core.service._ import gitbucket.core.service._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util._ import gitbucket.core.util._
import gitbucket.core.view.helpers._ import gitbucket.core.view.helpers._
import org.scalatra.Ok import org.scalatra.Ok
@@ -237,50 +236,49 @@ trait IndexControllerBase extends ControllerBase {
// TODO Move to RepositoryViewrController? // TODO Move to RepositoryViewrController?
get("/:owner/:repository/search")(referrersOnly { repository => get("/:owner/:repository/search")(referrersOnly { repository =>
defining(params.getOrElse("q", "").trim, params.getOrElse("type", "code")) { val query = params.getOrElse("q", "").trim
case (query, target) => val target = params.getOrElse("type", "code")
val page = try { val page = try {
val i = params.getOrElse("page", "1").toInt val i = params.getOrElse("page", "1").toInt
if (i <= 0) 1 else i if (i <= 0) 1 else i
} catch { } catch {
case e: NumberFormatException => 1 case _: NumberFormatException => 1
} }
target.toLowerCase match { target.toLowerCase match {
case "issues" => case "issues" =>
gitbucket.core.search.html.issues( gitbucket.core.search.html.issues(
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, false) else Nil, if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, false) else Nil,
false, false,
query, query,
page, page,
repository repository
) )
case "pulls" => case "pulls" =>
gitbucket.core.search.html.issues( gitbucket.core.search.html.issues(
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, true) else Nil, if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, true) else Nil,
true, true,
query, query,
page, page,
repository repository
) )
case "wiki" => case "wiki" =>
gitbucket.core.search.html.wiki( gitbucket.core.search.html.wiki(
if (query.nonEmpty) searchWikiPages(repository.owner, repository.name, query) else Nil, if (query.nonEmpty) searchWikiPages(repository.owner, repository.name, query) else Nil,
query, query,
page, page,
repository repository
) )
case _ => case _ =>
gitbucket.core.search.html.code( gitbucket.core.search.html.code(
if (query.nonEmpty) searchFiles(repository.owner, repository.name, query) else Nil, if (query.nonEmpty) searchFiles(repository.owner, repository.name, query) else Nil,
query, query,
page, page,
repository repository
) )
}
} }
}) })

View File

@@ -1,9 +1,9 @@
package gitbucket.core.controller package gitbucket.core.controller
import gitbucket.core.issues.html import gitbucket.core.issues.html
import gitbucket.core.model.Account
import gitbucket.core.service.IssuesService._ import gitbucket.core.service.IssuesService._
import gitbucket.core.service._ import gitbucket.core.service._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.util._ import gitbucket.core.util._
import gitbucket.core.view import gitbucket.core.view
@@ -95,212 +95,224 @@ trait IssuesControllerBase extends ControllerBase {
}) })
get("/:owner/:repository/issues/:id")(referrersOnly { repository => get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
defining(repository.owner, repository.name, params("id")) { val issueId = params("id")
case (owner, name, issueId) => getIssue(repository.owner, repository.name, issueId) map {
getIssue(owner, name, issueId) map { issue =>
issue => if (issue.isPullRequest) {
if (issue.isPullRequest) { redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}") } else {
} else { html.issue(
html.issue( issue,
issue, getComments(repository.owner, repository.name, issueId.toInt),
getComments(owner, name, issueId.toInt), getIssueLabels(repository.owner, repository.name, issueId.toInt),
getIssueLabels(owner, name, issueId.toInt), getAssignableUserNames(repository.owner, repository.name),
getAssignableUserNames(owner, name), getMilestonesWithIssueCount(repository.owner, repository.name),
getMilestonesWithIssueCount(owner, name), getPriorities(repository.owner, repository.name),
getPriorities(owner, name), getLabels(repository.owner, repository.name),
getLabels(owner, name), isIssueEditable(repository),
isIssueEditable(repository), isIssueManageable(repository),
isIssueManageable(repository), isIssueCommentManageable(repository),
isIssueCommentManageable(repository), repository
repository )
) }
} } getOrElse NotFound()
} getOrElse NotFound()
}
}) })
get("/:owner/:repository/issues/new")(readableUsersOnly { repository => get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator? if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator?
defining(repository.owner, repository.name) { html.create(
case (owner, name) => getAssignableUserNames(repository.owner, repository.name),
html.create( getMilestones(repository.owner, repository.name),
getAssignableUserNames(owner, name), getPriorities(repository.owner, repository.name),
getMilestones(owner, name), getDefaultPriority(repository.owner, repository.name),
getPriorities(owner, name), getLabels(repository.owner, repository.name),
getDefaultPriority(owner, name), isIssueManageable(repository),
getLabels(owner, name), getContentTemplate(repository, "ISSUE_TEMPLATE"),
isIssueManageable(repository), repository
getContentTemplate(repository, "ISSUE_TEMPLATE"), )
repository
)
}
} else Unauthorized() } else Unauthorized()
}) })
post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) => post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) =>
if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator? context.withLoginAccount {
val issue = createIssue( loginAccount =>
repository, if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator?
form.title, val issue = createIssue(
form.content, repository,
form.assignedUserName, form.title,
form.milestoneId, form.content,
form.priorityId, form.assignedUserName,
form.labelNames.toSeq.flatMap(_.split(",")), form.milestoneId,
context.loginAccount.get form.priorityId,
) form.labelNames.toSeq.flatMap(_.split(",")),
loginAccount
redirect(s"/${issue.userName}/${issue.repositoryName}/issues/${issue.issueId}") )
} else Unauthorized() redirect(s"/${issue.userName}/${issue.repositoryName}/issues/${issue.issueId}")
} else Unauthorized()
}
}) })
ajaxPost("/:owner/:repository/issues/edit_title/:id", issueTitleEditForm)(readableUsersOnly { (title, repository) => ajaxPost("/:owner/:repository/issues/edit_title/:id", issueTitleEditForm)(readableUsersOnly { (title, repository) =>
defining(repository.owner, repository.name) { context.withLoginAccount {
case (owner, name) => loginAccount =>
getIssue(owner, name, params("id")).map { getIssue(repository.owner, repository.name, params("id")).map {
issue => issue =>
if (isEditableContent(owner, name, issue.openedUserName)) { if (isEditableContent(repository.owner, repository.name, issue.openedUserName, loginAccount)) {
if (issue.title != title) { if (issue.title != title) {
// update issue // 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 // 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( createComment(
owner, repository.owner,
name, repository.name,
context.loginAccount.get.userName, loginAccount.userName,
issue.issueId, issue.issueId,
issue.title + "\r\n" + title, issue.title + "\r\n" + title,
"change_title" "change_title"
) )
} }
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}") redirect(s"/${repository.owner}/${repository.name}/issues/_data/${issue.issueId}")
} else Unauthorized() } else Unauthorized()
} getOrElse NotFound() } getOrElse NotFound()
} }
}) })
ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (content, repository) => ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (content, repository) =>
defining(repository.owner, repository.name) { context.withLoginAccount {
case (owner, name) => loginAccount =>
getIssue(owner, name, params("id")).map { issue => getIssue(repository.owner, repository.name, params("id")).map { issue =>
if (isEditableContent(owner, name, issue.openedUserName)) { if (isEditableContent(repository.owner, repository.name, issue.openedUserName, loginAccount)) {
// update issue // 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 // 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() } else Unauthorized()
} getOrElse NotFound() } getOrElse NotFound()
} }
}) })
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) => post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue => context.withLoginAccount {
val actionOpt = loginAccount =>
params.get("action").filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName)) getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
handleComment(issue, Some(form.content), repository, actionOpt) map { val actionOpt =
case (issue, id) => params
redirect( .get("action")
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}" .filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName, loginAccount))
) handleComment(issue, Some(form.content), repository, actionOpt) map {
} case (issue, id) =>
} getOrElse NotFound() 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) => post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue => context.withLoginAccount {
val actionOpt = loginAccount =>
params.get("action").filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName)) getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
handleComment(issue, form.content, repository, actionOpt) map { val actionOpt =
case (issue, id) => params
redirect( .get("action")
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}" .filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName, loginAccount))
) handleComment(issue, form.content, repository, actionOpt) map {
} case (issue, id) =>
} getOrElse NotFound() 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) => ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
defining(repository.owner, repository.name) { context.withLoginAccount {
case (owner, name) => loginAccount =>
getComment(owner, name, params("id")).map { comment => getComment(repository.owner, repository.name, params("id")).map { comment =>
if (isEditableContent(owner, name, comment.commentedUserName)) { if (isEditableContent(repository.owner, repository.name, comment.commentedUserName, loginAccount)) {
updateComment(comment.issueId, comment.commentId, form.content) 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() } else Unauthorized()
} getOrElse NotFound() } getOrElse NotFound()
} }
}) })
ajaxPost("/:owner/:repository/issue_comments/delete/:id")(readableUsersOnly { repository => ajaxPost("/:owner/:repository/issue_comments/delete/:id")(readableUsersOnly { repository =>
defining(repository.owner, repository.name) { context.withLoginAccount { loginAccount =>
case (owner, name) => getComment(repository.owner, repository.name, params("id")).map { comment =>
getComment(owner, name, params("id")).map { comment => if (isDeletableComment(repository.owner, repository.name, comment.commentedUserName, loginAccount)) {
if (isDeletableComment(owner, name, comment.commentedUserName)) { Ok(deleteComment(repository.owner, repository.name, comment.issueId, comment.commentId))
Ok(deleteComment(repository.owner, repository.name, comment.issueId, comment.commentId)) } else Unauthorized()
} else Unauthorized() } getOrElse NotFound()
} getOrElse NotFound()
} }
}) })
ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository => ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository =>
getIssue(repository.owner, repository.name, params("id")) map { context.withLoginAccount {
x => loginAccount =>
if (isEditableContent(x.userName, x.repositoryName, x.openedUserName)) { getIssue(repository.owner, repository.name, params("id")) map {
params.get("dataType") collect { x =>
case t if t == "html" => html.editissue(x.content, x.issueId, repository) if (isEditableContent(x.userName, x.repositoryName, x.openedUserName, loginAccount)) {
} getOrElse { params.get("dataType") collect {
contentType = formats("json") case t if t == "html" => html.editissue(x.content, x.issueId, repository)
org.json4s.jackson.Serialization.write( } getOrElse {
Map( contentType = formats("json")
"title" -> x.title, org.json4s.jackson.Serialization.write(
"content" -> Markdown.toHtml( Map(
markdown = x.content getOrElse "No description given.", "title" -> x.title,
repository = repository, "content" -> Markdown.toHtml(
branch = repository.repository.defaultBranch, markdown = x.content getOrElse "No description given.",
enableWikiLink = false, repository = repository,
enableRefsLink = true, branch = repository.repository.defaultBranch,
enableAnchor = true, enableWikiLink = false,
enableLineBreaks = true, enableRefsLink = true,
enableTaskList = true, enableAnchor = true,
hasWritePermission = true enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = true
)
)
) )
) }
) } else Unauthorized()
} } getOrElse NotFound()
} else Unauthorized() }
} getOrElse NotFound()
}) })
ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository => ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository =>
getComment(repository.owner, repository.name, params("id")) map { context.withLoginAccount {
x => loginAccount =>
if (isEditableContent(x.userName, x.repositoryName, x.commentedUserName)) { getComment(repository.owner, repository.name, params("id")) map {
params.get("dataType") collect { x =>
case t if t == "html" => html.editcomment(x.content, x.commentId, repository) if (isEditableContent(x.userName, x.repositoryName, x.commentedUserName, loginAccount)) {
} getOrElse { params.get("dataType") collect {
contentType = formats("json") case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
org.json4s.jackson.Serialization.write( } getOrElse {
Map( contentType = formats("json")
"content" -> view.Markdown.toHtml( org.json4s.jackson.Serialization.write(
markdown = x.content, Map(
repository = repository, "content" -> view.Markdown.toHtml(
branch = repository.repository.defaultBranch, markdown = x.content,
enableWikiLink = false, repository = repository,
enableRefsLink = true, branch = repository.repository.defaultBranch,
enableAnchor = true, enableWikiLink = false,
enableLineBreaks = true, enableRefsLink = true,
enableTaskList = true, enableAnchor = true,
hasWritePermission = true enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = true
)
)
) )
) }
) } else Unauthorized()
} } getOrElse NotFound()
} else Unauthorized() }
} getOrElse NotFound()
}) })
ajaxPost("/:owner/:repository/issues/new/label")(writableUsersOnly { repository => 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 => ajaxPost("/:owner/:repository/issues/:id/label/new")(writableUsersOnly { repository =>
defining(params("id").toInt) { issueId => val issueId = params("id").toInt
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, true) registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, true)
html.labellist(getIssueLabels(repository.owner, repository.name, issueId)) html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
}
}) })
ajaxPost("/:owner/:repository/issues/:id/label/delete")(writableUsersOnly { repository => ajaxPost("/:owner/:repository/issues/:id/label/delete")(writableUsersOnly { repository =>
defining(params("id").toInt) { issueId => val issueId = params("id").toInt
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, true) deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, true)
html.labellist(getIssueLabels(repository.owner, repository.name, issueId)) html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
}
}) })
ajaxPost("/:owner/:repository/issues/:id/assign")(writableUsersOnly { repository => ajaxPost("/:owner/:repository/issues/:id/assign")(writableUsersOnly { repository =>
@@ -353,26 +363,27 @@ trait IssuesControllerBase extends ControllerBase {
}) })
post("/:owner/:repository/issues/batchedit/state")(writableUsersOnly { repository => post("/:owner/:repository/issues/batchedit/state")(writableUsersOnly { repository =>
defining(params.get("value")) { val action = params.get("value")
action => action match {
action match { case Some("open") =>
case Some("open") => executeBatch(repository) { issueId =>
executeBatch(repository) { issueId => getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue => handleComment(issue, None, repository, Some("reopen"))
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()
} }
if (params("uri").nonEmpty) { if (params("uri").nonEmpty) {
redirect(params("uri")) 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 => post("/:owner/:repository/issues/batchedit/assign")(writableUsersOnly { repository =>
defining(assignedUserName("value")) { value => val value = assignedUserName("value")
executeBatch(repository) { executeBatch(repository) {
updateAssignedUserName(repository.owner, repository.name, _, value, true) updateAssignedUserName(repository.owner, repository.name, _, value, true)
} }
if (params("uri").nonEmpty) { if (params("uri").nonEmpty) {
redirect(params("uri")) redirect(params("uri"))
}
} }
}) })
post("/:owner/:repository/issues/batchedit/milestone")(writableUsersOnly { repository => post("/:owner/:repository/issues/batchedit/milestone")(writableUsersOnly { repository =>
defining(milestoneId("value")) { value => val value = milestoneId("value")
executeBatch(repository) { executeBatch(repository) {
updateMilestoneId(repository.owner, repository.name, _, value, true) updateMilestoneId(repository.owner, repository.name, _, value, true)
}
} }
}) })
post("/:owner/:repository/issues/batchedit/priority")(writableUsersOnly { repository => post("/:owner/:repository/issues/batchedit/priority")(writableUsersOnly { repository =>
defining(priorityId("value")) { value => val value = priorityId("value")
executeBatch(repository) { executeBatch(repository) {
updatePriorityId(repository.owner, repository.name, _, value, true) updatePriorityId(repository.owner, repository.name, _, value, true)
}
} }
}) })
@@ -464,48 +472,51 @@ trait IssuesControllerBase extends ControllerBase {
} }
private def searchIssues(repository: RepositoryService.RepositoryInfo) = { private def searchIssues(repository: RepositoryService.RepositoryInfo) = {
defining(repository.owner, repository.name) { val page = IssueSearchCondition.page(request)
case (owner, repoName) => // retrieve search condition
val page = IssueSearchCondition.page(request) val condition = IssueSearchCondition(request)
// retrieve search condition // search issues
val condition = IssueSearchCondition(request) val issues =
// search issues searchIssue(
val issues = condition,
searchIssue(condition, IssueSearchOption.Issues, (page - 1) * IssueLimit, IssueLimit, owner -> repoName) IssueSearchOption.Issues,
(page - 1) * IssueLimit,
IssueLimit,
repository.owner -> repository.name
)
html.list( html.list(
"issues", "issues",
issues.map(issue => (issue, None)), issues.map(issue => (issue, None)),
page, page,
getAssignableUserNames(owner, repoName), getAssignableUserNames(repository.owner, repository.name),
getMilestones(owner, repoName), getMilestones(repository.owner, repository.name),
getPriorities(owner, repoName), getPriorities(repository.owner, repository.name),
getLabels(owner, repoName), getLabels(repository.owner, repository.name),
countIssue(condition.copy(state = "open"), IssueSearchOption.Issues, owner -> repoName), countIssue(condition.copy(state = "open"), IssueSearchOption.Issues, repository.owner -> repository.name),
countIssue(condition.copy(state = "closed"), IssueSearchOption.Issues, owner -> repoName), countIssue(condition.copy(state = "closed"), IssueSearchOption.Issues, repository.owner -> repository.name),
condition, condition,
repository, repository,
isIssueEditable(repository), isIssueEditable(repository),
isIssueManageable(repository) isIssueManageable(repository)
) )
}
} }
/** /**
* Tests whether an issue or a comment is editable by a logged-in user. * Tests whether an issue or a comment is editable by a logged-in user.
*/ */
private def isEditableContent(owner: String, repository: String, author: String)( private def isEditableContent(owner: String, repository: String, author: String, loginAccount: Account)(
implicit context: Context implicit context: Context
): Boolean = { ): 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. * 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 implicit context: Context
): Boolean = { ): 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) { get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) {
if (!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") && if (!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
!context.currentPath.startsWith("/register") && !context.currentPath.endsWith("/info/refs") && !context.currentPath.startsWith("/register") && !context.currentPath.endsWith("/info/refs") &&
!context.currentPath.startsWith("/plugin-assets") &&
!PluginRegistry().getAnonymousAccessiblePaths().exists { path => !PluginRegistry().getAnonymousAccessiblePaths().exists { path =>
context.currentPath.startsWith(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.PullRequestService._
import gitbucket.core.service.RepositoryService.RepositoryInfo import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.service._ import gitbucket.core.service._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.util._ import gitbucket.core.util._
@@ -58,7 +57,7 @@ trait PullRequestsControllerBase extends ControllerBase {
with PrioritiesService => with PrioritiesService =>
val pullRequestForm = mapping( val pullRequestForm = mapping(
"title" -> trim(label("Title", text(required, maxlength(100)))), "title" -> trim(label("Title", text(required))),
"content" -> trim(label("Content", optional(text()))), "content" -> trim(label("Content", optional(text()))),
"targetUserName" -> trim(text(required, maxlength(100))), "targetUserName" -> trim(text(required, maxlength(100))),
"targetBranch" -> 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 => get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
params("id").toIntOpt.flatMap { params("id").toIntOpt.flatMap {
issueId => issueId =>
val owner = repository.owner getPullRequest(repository.owner, repository.name, issueId) map {
val name = repository.name
getPullRequest(owner, name, issueId) map {
case (issue, pullreq) => case (issue, pullreq) =>
val (commits, diffs) = 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( html.conversation(
issue, issue,
pullreq, pullreq,
commits.flatten, commits.flatten,
getPullRequestComments(owner, name, issue.issueId, commits.flatten), getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
diffs.size, diffs.size,
getIssueLabels(owner, name, issueId), getIssueLabels(repository.owner, repository.name, issueId),
getAssignableUserNames(owner, name), getAssignableUserNames(repository.owner, repository.name),
getMilestonesWithIssueCount(owner, name), getMilestonesWithIssueCount(repository.owner, repository.name),
getPriorities(owner, name), getPriorities(repository.owner, repository.name),
getLabels(owner, name), getLabels(repository.owner, repository.name),
isEditable(repository), isEditable(repository),
isManageable(repository), isManageable(repository),
hasDeveloperRole(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount), hasDeveloperRole(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount),
@@ -162,18 +166,29 @@ trait PullRequestsControllerBase extends ControllerBase {
get("/:owner/:repository/pull/:id/commits")(referrersOnly { repository => get("/:owner/:repository/pull/:id/commits")(referrersOnly { repository =>
params("id").toIntOpt.flatMap { params("id").toIntOpt.flatMap {
issueId => issueId =>
val owner = repository.owner getPullRequest(repository.owner, repository.name, issueId) map {
val name = repository.name
getPullRequest(owner, name, issueId) map {
case (issue, pullreq) => case (issue, pullreq) =>
val (commits, diffs) = 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( html.commits(
issue, issue,
pullreq, pullreq,
commits, commitsWithStatus,
getPullRequestComments(owner, name, issue.issueId, commits.flatten), getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
diffs.size, diffs.size,
isManageable(repository), isManageable(repository),
repository repository
@@ -185,19 +200,24 @@ trait PullRequestsControllerBase extends ControllerBase {
get("/:owner/:repository/pull/:id/files")(referrersOnly { repository => get("/:owner/:repository/pull/:id/files")(referrersOnly { repository =>
params("id").toIntOpt.flatMap { params("id").toIntOpt.flatMap {
issueId => issueId =>
val owner = repository.owner getPullRequest(repository.owner, repository.name, issueId) map {
val name = repository.name
getPullRequest(owner, name, issueId) map {
case (issue, pullreq) => case (issue, pullreq) =>
val (commits, diffs) = 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( html.files(
issue, issue,
pullreq, pullreq,
diffs, diffs,
commits.flatten, commits.flatten,
getPullRequestComments(owner, name, issue.issueId, commits.flatten), getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
isManageable(repository), isManageable(repository),
repository repository
) )
@@ -208,39 +228,35 @@ trait PullRequestsControllerBase extends ControllerBase {
ajaxGet("/:owner/:repository/pull/:id/mergeguide")(referrersOnly { repository => ajaxGet("/:owner/:repository/pull/:id/mergeguide")(referrersOnly { repository =>
params("id").toIntOpt.flatMap { params("id").toIntOpt.flatMap {
issueId => issueId =>
val owner = repository.owner getPullRequest(repository.owner, repository.name, issueId) map {
val name = repository.name
getPullRequest(owner, name, issueId) map {
case (issue, pullreq) => case (issue, pullreq) =>
val conflictMessage = LockUtil.lock(s"${owner}/${name}") { val conflictMessage = LockUtil.lock(s"${repository.owner}/${repository.name}") {
checkConflict(owner, name, pullreq.branch, issueId) checkConflict(repository.owner, repository.name, pullreq.branch, issueId)
} }
val hasMergePermission = hasDeveloperRole(owner, name, context.loginAccount) val hasMergePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.branch) val branchProtection = getProtectedBranchInfo(repository.owner, repository.name, pullreq.branch)
val mergeStatus = PullRequestService.MergeStatus( val mergeStatus = PullRequestService.MergeStatus(
conflictMessage = conflictMessage, conflictMessage = conflictMessage,
commitStatuses = getCommitStatuses(owner, name, pullreq.commitIdTo), commitStatuses = getCommitStatuses(repository.owner, repository.name, pullreq.commitIdTo),
branchProtection = branchProtection, branchProtection = branchProtection,
branchIsOutOfDate = JGitUtil.getShaByRef(owner, name, pullreq.branch) != Some(pullreq.commitIdFrom), branchIsOutOfDate = JGitUtil.getShaByRef(repository.owner, repository.name, pullreq.branch) != Some(
needStatusCheck = context.loginAccount pullreq.commitIdFrom
.map { u => ),
branchProtection.needStatusCheck(u.userName) needStatusCheck = context.loginAccount.forall { u =>
} branchProtection.needStatusCheck(u.userName)
.getOrElse(true), },
hasUpdatePermission = hasDeveloperRole( hasUpdatePermission = hasDeveloperRole(
pullreq.requestUserName, pullreq.requestUserName,
pullreq.requestRepositoryName, pullreq.requestRepositoryName,
context.loginAccount context.loginAccount
) && ) &&
context.loginAccount context.loginAccount.exists { u =>
.map { u => !getProtectedBranchInfo(
!getProtectedBranchInfo( pullreq.requestUserName,
pullreq.requestUserName, pullreq.requestRepositoryName,
pullreq.requestRepositoryName, pullreq.requestBranch
pullreq.requestBranch ).needStatusCheck(u.userName)
).needStatusCheck(u.userName) },
}
.getOrElse(false),
hasMergePermission = hasMergePermission, hasMergePermission = hasMergePermission,
commitIdTo = pullreq.commitIdTo commitIdTo = pullreq.commitIdTo
) )
@@ -357,23 +373,22 @@ trait PullRequestsControllerBase extends ControllerBase {
}) })
post("/:owner/:repository/pull/:id/merge", mergeForm)(writableUsersOnly { (form, repository) => post("/:owner/:repository/pull/:id/merge", mergeForm)(writableUsersOnly { (form, repository) =>
params("id").toIntOpt.flatMap { issueId => context.withLoginAccount { loginAccount =>
val owner = repository.owner params("id").toIntOpt.flatMap { issueId =>
val name = repository.name mergePullRequest(
repository,
mergePullRequest( issueId,
repository, loginAccount,
issueId, form.message,
context.loginAccount.get, form.strategy,
form.message, form.isDraft,
form.strategy, context.settings
form.isDraft, ) match {
context.settings case Right(objectId) => redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
) match { case Left(message) => Some(BadRequest(message))
case Right(objectId) => redirect(s"/${owner}/${name}/pull/${issueId}") }
case Left(message) => Some(BadRequest(message)) } getOrElse NotFound()
} }
} getOrElse NotFound()
}) })
get("/:owner/:repository/compare")(referrersOnly { forkedRepository => get("/:owner/:repository/compare")(referrersOnly { forkedRepository =>
@@ -475,8 +490,7 @@ trait PullRequestsControllerBase extends ControllerBase {
(repository.userName, repository.repositoryName, repository.defaultBranch) (repository.userName, repository.repositoryName, repository.defaultBranch)
}, },
commits.flatten commits.flatten
.map(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false)) .flatMap(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false))
.flatten
.toList, .toList,
originId, originId,
forkedId, forkedId,
@@ -543,15 +557,14 @@ trait PullRequestsControllerBase extends ControllerBase {
}) })
post("/:owner/:repository/pulls/new", pullRequestForm)(readableUsersOnly { (form, repository) => post("/:owner/:repository/pulls/new", pullRequestForm)(readableUsersOnly { (form, repository) =>
defining(repository.owner, repository.name) { context.withLoginAccount {
case (owner, name) => loginAccount =>
val manageable = isManageable(repository) val manageable = isManageable(repository)
val loginUserName = context.loginAccount.get.userName
val issueId = insertIssue( val issueId = insertIssue(
owner = repository.owner, owner = repository.owner,
repository = repository.name, repository = repository.name,
loginUser = loginUserName, loginUser = loginAccount.userName,
title = form.title, title = form.title,
content = form.content, content = form.content,
assignedUserName = if (manageable) form.assignedUserName else None, assignedUserName = if (manageable) form.assignedUserName else None,
@@ -570,14 +583,14 @@ trait PullRequestsControllerBase extends ControllerBase {
commitIdFrom = form.commitIdFrom, commitIdFrom = form.commitIdFrom,
commitIdTo = form.commitIdTo, commitIdTo = form.commitIdTo,
isDraft = form.isDraft, isDraft = form.isDraft,
loginAccount = context.loginAccount.get, loginAccount = loginAccount,
settings = context.settings settings = context.settings
) )
// insert labels // insert labels
if (manageable) { if (manageable) {
form.labelNames.foreach { value => form.labelNames.foreach { value =>
val labels = getLabels(owner, name) val labels = getLabels(repository.owner, repository.name)
value.split(",").foreach { labelName => value.split(",").foreach { labelName =>
labels.find(_.labelName == labelName).map { label => labels.find(_.labelName == labelName).map { label =>
registerIssueLabel(repository.owner, repository.name, issueId, label.labelId) 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) html.proposals(proposedBranches, targetRepository, repository)
}) })
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) = private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) = {
defining(repository.owner, repository.name) { val page = IssueSearchCondition.page(request)
case (owner, repoName) => // retrieve search condition
val page = IssueSearchCondition.page(request) val condition = IssueSearchCondition(request)
// retrieve search condition // search issues
val condition = IssueSearchCondition(request) val issues = searchIssue(
// search issues condition,
val issues = searchIssue( IssueSearchOption.PullRequests,
condition, (page - 1) * PullRequestLimit,
IssueSearchOption.PullRequests, PullRequestLimit,
(page - 1) * PullRequestLimit, repository.owner -> repository.name
PullRequestLimit, )
owner -> repoName // commit status
) val status = issues.map { issue =>
// commit status issue.commitId.flatMap { commitId =>
val status = issues.map { issue => getCommitStatusWithSummary(repository.owner, repository.name, commitId)
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)
)
} }
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. * 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 => get("/:owner/:repository/releases/*")(referrersOnly { repository =>
val tagName = params("tag") val tagName = multiParams("splat").head
getRelease(repository.owner, repository.name, tagName) getRelease(repository.owner, repository.name, tagName)
.map { release => .map { release =>
html.release( html.release(
@@ -77,8 +77,8 @@ trait ReleaseControllerBase extends ControllerBase {
.getOrElse(NotFound()) .getOrElse(NotFound())
}) })
get("/:owner/:repository/releases/:tag/assets/:fileId")(referrersOnly { repository => get("/:owner/:repository/releases/*/assets/:fileId")(referrersOnly { repository =>
val tagName = params("tag") val tagName = multiParams("splat").head
val fileId = params("fileId") val fileId = params("fileId")
(for { (for {
_ <- repository.tags.find(_.name == tagName) _ <- repository.tags.find(_.name == tagName)
@@ -93,8 +93,8 @@ trait ReleaseControllerBase extends ControllerBase {
}).getOrElse(NotFound()) }).getOrElse(NotFound())
}) })
get("/:owner/:repository/releases/:tag/create")(writableUsersOnly { repository => get("/:owner/:repository/releases/*/create")(writableUsersOnly { repository =>
val tagName = params("tag") val tagName = multiParams("splat").head
val previousTags = repository.tags.takeWhile(_.name != tagName).reverse val previousTags = repository.tags.takeWhile(_.name != tagName).reverse
repository.tags repository.tags
@@ -105,33 +105,35 @@ trait ReleaseControllerBase extends ControllerBase {
.getOrElse(NotFound()) .getOrElse(NotFound())
}) })
post("/:owner/:repository/releases/:tag/create", releaseForm)(writableUsersOnly { (form, repository) => post("/:owner/:repository/releases/*/create", releaseForm)(writableUsersOnly { (form, repository) =>
val tagName = params("tag") context.withLoginAccount {
val loginAccount = context.loginAccount.get loginAccount =>
val tagName = multiParams("splat").head
// Insert into RELEASE // Insert into RELEASE
createRelease(repository.owner, repository.name, form.name, form.content, tagName, loginAccount) createRelease(repository.owner, repository.name, form.name, form.content, tagName, loginAccount)
// Insert into RELEASE_ASSET // Insert into RELEASE_ASSET
val files = params.toMap.collect { val files = params.toMap.collect {
case (name, value) if name.startsWith("file:") => case (name, value) if name.startsWith("file:") =>
val Array(_, fileId) = name.split(":") val Array(_, fileId) = name.split(":")
(fileId, value) (fileId, value)
}
files.foreach {
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 => get("/:owner/:repository/changelog/*...*")(writableUsersOnly { repository =>
@@ -150,8 +152,8 @@ trait ReleaseControllerBase extends ControllerBase {
commitLog commitLog
}) })
get("/:owner/:repository/releases/:tag/edit")(writableUsersOnly { repository => get("/:owner/:repository/releases/*/edit")(writableUsersOnly { repository =>
val tagName = params("tag") val tagName = multiParams("splat").head
val previousTags = repository.tags.takeWhile(_.name != tagName).reverse val previousTags = repository.tags.takeWhile(_.name != tagName).reverse
(for { (for {
@@ -168,52 +170,54 @@ trait ReleaseControllerBase extends ControllerBase {
}).getOrElse(NotFound()) }).getOrElse(NotFound())
}) })
post("/:owner/:repository/releases/:tag/edit", releaseForm)(writableUsersOnly { post("/:owner/:repository/releases/*/edit", releaseForm)(writableUsersOnly { (form, repository) =>
(form, repository) => context.withLoginAccount {
val tagName = params("tag") loginAccount =>
val loginAccount = context.loginAccount.get val tagName = multiParams("splat").head
getRelease(repository.owner, repository.name, tagName) getRelease(repository.owner, repository.name, tagName)
.map { release => .map {
// Update RELEASE release =>
updateRelease(repository.owner, repository.name, tagName, form.name, form.content) // Update RELEASE
updateRelease(repository.owner, repository.name, tagName, form.name, form.content)
// Delete and Insert RELEASE_ASSET // Delete and Insert RELEASE_ASSET
val assets = getReleaseAssets(repository.owner, repository.name, tagName) val assets = getReleaseAssets(repository.owner, repository.name, tagName)
deleteReleaseAssets(repository.owner, repository.name, tagName) deleteReleaseAssets(repository.owner, repository.name, tagName)
val files = params.toMap.collect { val files = params.toMap.collect {
case (name, value) if name.startsWith("file:") => case (name, value) if name.startsWith("file:") =>
val Array(_, fileId) = name.split(":") val Array(_, fileId) = name.split(":")
(fileId, value) (fileId, value)
}
files.foreach {
case (fileId, fileName) =>
val size =
new File(
getReleaseFilesDir(repository.owner, repository.name),
FileUtil.checkFilename(tagName + "/" + fileId)
).length
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
}
assets.foreach { asset =>
if (!files.exists { case (fileId, _) => fileId == asset.fileName }) {
val file = new File(
getReleaseFilesDir(repository.owner, repository.name),
FileUtil.checkFilename(release.tag + "/" + asset.fileName)
)
FileUtils.forceDelete(file)
}
}
redirect(s"/${release.userName}/${release.repositoryName}/releases/${tagName}")
} }
files.foreach { .getOrElse(NotFound())
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())
}) })
post("/:owner/:repository/releases/:tag/delete")(writableUsersOnly { repository => post("/:owner/:repository/releases/*/delete")(writableUsersOnly { repository =>
val tagName = params("tag") val tagName = multiParams("splat").head
getRelease(repository.owner, repository.name, tagName).foreach { release => getRelease(repository.owner, repository.name, tagName).foreach { release =>
FileUtils.deleteDirectory( FileUtils.deleteDirectory(
new File(getReleaseFilesDir(repository.owner, repository.name), FileUtil.checkFilename(release.tag)) 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) = { private def fetchReleases(repository: RepositoryService.RepositoryInfo, page: Int) = {
import gitbucket.core.service.ReleaseService._ import gitbucket.core.service.ReleaseService._
val (offset, limit) = ((page - 1) * ReleaseLimit, ReleaseLimit) val (offset, limit) = ((page - 1) * ReleaseLimit, ReleaseLimit)

View File

@@ -1,6 +1,6 @@
package gitbucket.core.controller package gitbucket.core.controller
import java.time.{LocalDateTime, ZoneId, ZoneOffset} import java.time.{LocalDateTime, ZoneOffset}
import java.util.Date import java.util.Date
import gitbucket.core.settings.html import gitbucket.core.settings.html
@@ -57,7 +57,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
externalWikiUrl: Option[String], externalWikiUrl: Option[String],
allowFork: Boolean, allowFork: Boolean,
mergeOptions: Seq[String], mergeOptions: Seq[String],
defaultMergeOption: String defaultMergeOption: String,
safeMode: Boolean
) )
val optionsForm = mapping( val optionsForm = mapping(
@@ -69,7 +70,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
"externalWikiUrl" -> trim(label("External Wiki URL", optional(text(maxlength(200))))), "externalWikiUrl" -> trim(label("External Wiki URL", optional(text(maxlength(200))))),
"allowFork" -> trim(label("Allow Forking", boolean())), "allowFork" -> trim(label("Allow Forking", boolean())),
"mergeOptions" -> mergeOptions, "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 => )(OptionsForm.apply).verifying { form =>
if (!form.mergeOptions.contains(form.defaultMergeOption)) { if (!form.mergeOptions.contains(form.defaultMergeOption)) {
Seq("defaultMergeOption" -> s"This merge strategy isn't enabled.") Seq("defaultMergeOption" -> s"This merge strategy isn't enabled.")
@@ -150,7 +152,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
form.externalWikiUrl, form.externalWikiUrl,
form.allowFork, form.allowFork,
form.mergeOptions, form.mergeOptions,
form.defaultMergeOption form.defaultMergeOption,
form.safeMode
) )
flash.update("info", "Repository settings has been updated.") flash.update("info", "Repository settings has been updated.")
redirect(s"/${repository.owner}/${repository.name}/settings/options") redirect(s"/${repository.owner}/${repository.name}/settings/options")
@@ -342,7 +345,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
.map( .map(
res => res =>
Map( Map(
"status" -> res.getStatusLine(), "status" -> res.getStatusLine.getStatusCode,
"body" -> EntityUtils.toString(res.getEntity()), "body" -> EntityUtils.toString(res.getEntity()),
"headers" -> _headers(res.getAllHeaders()) "headers" -> _headers(res.getAllHeaders())
) )
@@ -385,54 +388,62 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Rename repository. * Rename repository.
*/ */
post("/:owner/:repository/settings/rename", renameForm)(ownerOnly { (form, repository) => post("/:owner/:repository/settings/rename", renameForm)(ownerOnly { (form, repository) =>
if (context.settings.repositoryOperation.rename || context.loginAccount.get.isAdmin) { context.withLoginAccount {
if (repository.name != form.repositoryName) { loginAccount =>
// Update database and move git repository if (context.settings.repositoryOperation.rename || loginAccount.isAdmin) {
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName) if (repository.name != form.repositoryName) {
// Record activity log // Update database and move git repository
val renameInfo = RenameRepositoryInfo( renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
repository.owner, // Record activity log
form.repositoryName, val renameInfo = RenameRepositoryInfo(
context.loginAccount.get.userName, repository.owner,
repository.name form.repositoryName,
) loginAccount.userName,
recordActivity(renameInfo) repository.name
} )
redirect(s"/${repository.owner}/${form.repositoryName}") recordActivity(renameInfo)
} else Forbidden() }
redirect(s"/${repository.owner}/${form.repositoryName}")
} else Forbidden()
}
}) })
/** /**
* Transfer repository ownership. * Transfer repository ownership.
*/ */
post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) => post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) =>
if (context.settings.repositoryOperation.transfer || context.loginAccount.get.isAdmin) { context.withLoginAccount {
// Change repository owner loginAccount =>
if (repository.owner != form.newOwner) { if (context.settings.repositoryOperation.transfer || loginAccount.isAdmin) {
// Update database and move git repository // Change repository owner
renameRepository(repository.owner, repository.name, form.newOwner, repository.name) if (repository.owner != form.newOwner) {
// Record activity log // Update database and move git repository
val renameInfo = RenameRepositoryInfo( renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
form.newOwner, // Record activity log
repository.name, val renameInfo = RenameRepositoryInfo(
context.loginAccount.get.userName, form.newOwner,
repository.owner repository.name,
) loginAccount.userName,
recordActivity(renameInfo) repository.owner
} )
redirect(s"/${form.newOwner}/${repository.name}") recordActivity(renameInfo)
} else Forbidden() }
redirect(s"/${form.newOwner}/${repository.name}")
} else Forbidden()
}
}) })
/** /**
* Delete the repository. * Delete the repository.
*/ */
post("/:owner/:repository/settings/delete")(ownerOnly { repository => post("/:owner/:repository/settings/delete")(ownerOnly { repository =>
if (context.settings.repositoryOperation.delete || context.loginAccount.get.isAdmin) { context.withLoginAccount { loginAccount =>
// Delete the repository and related files if (context.settings.repositoryOperation.delete || loginAccount.isAdmin) {
deleteRepository(repository.repository) // Delete the repository and related files
redirect(s"/${repository.owner}") deleteRepository(repository.repository)
} else Forbidden() 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.ssh.SshServer
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.util.StringUtil._ import gitbucket.core.util.StringUtil._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.{AdminAuthenticator, Mailer} import gitbucket.core.util.{AdminAuthenticator, Mailer}
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
import org.apache.commons.mail.EmailException import org.apache.commons.mail.EmailException
@@ -187,7 +186,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
val newUserForm = mapping( val newUserForm = mapping(
"userName" -> trim(label("Username", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))), "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)))), "fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))), "mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))),
"extraMailAddresses" -> list( "extraMailAddresses" -> list(
@@ -201,7 +200,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
val editUserForm = mapping( val editUserForm = mapping(
"userName" -> trim(label("Username", text(required, maxlength(100), identifier))), "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)))), "fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))), "mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))),
"extraMailAddresses" -> list( "extraMailAddresses" -> list(
@@ -362,8 +361,8 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
}) })
get("/admin/users")(adminOnly { get("/admin/users")(adminOnly {
val includeRemoved = params.get("includeRemoved").map(_.toBoolean).getOrElse(false) val includeRemoved = params.get("includeRemoved").exists(_.toBoolean)
val includeGroups = params.get("includeGroups").map(_.toBoolean).getOrElse(false) val includeGroups = params.get("includeGroups").exists(_.toBoolean)
val users = getAllUsers(includeRemoved, includeGroups) val users = getAllUsers(includeRemoved, includeGroups)
val members = users.collect { val members = users.collect {
case account if (account.isGroupAccount) => case account if (account.isGroupAccount) =>
@@ -463,31 +462,28 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
}) })
get("/admin/users/:groupName/_editgroup")(adminOnly { get("/admin/users/:groupName/_editgroup")(adminOnly {
defining(params("groupName")) { groupName => val groupName = params("groupName")
html.usergroup(getAccountByUserName(groupName, true), getGroupMembers(groupName)) html.usergroup(getAccountByUserName(groupName, true), getGroupMembers(groupName))
}
}) })
post("/admin/users/:groupName/_editgroup", editGroupForm)(adminOnly { form => post("/admin/users/:groupName/_editgroup", editGroupForm)(adminOnly { form =>
defining( val groupName = params("groupName")
params("groupName"), val members = form.members
form.members .split(",")
.split(",") .map {
.map { _.split(":") match {
_.split(":") match { case Array(userName, isManager) => (userName, isManager.toBoolean)
case Array(userName, isManager) => (userName, isManager.toBoolean)
}
} }
.toList }
) { .toList
case (groupName, members) =>
getAccountByUserName(groupName, true).map {
account =>
updateGroup(groupName, form.description, form.url, form.isRemoved)
if (form.isRemoved) { getAccountByUserName(groupName, true).map {
// Remove from GROUP_MEMBER account =>
updateGroupMembers(form.groupName, Nil) updateGroup(groupName, form.description, form.url, form.isRemoved)
if (form.isRemoved) {
// Remove from GROUP_MEMBER
updateGroupMembers(form.groupName, Nil)
// // Remove repositories // // Remove repositories
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName => // getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
// deleteRepository(groupName, repositoryName) // deleteRepository(groupName, repositoryName)
@@ -495,9 +491,9 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
// FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName)) // FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName))
// FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName)) // FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName))
// } // }
} else { } else {
// Update GROUP_MEMBER // Update GROUP_MEMBER
updateGroupMembers(form.groupName, members) updateGroupMembers(form.groupName, members)
// // Update COLLABORATOR for group repositories // // Update COLLABORATOR for group repositories
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName => // getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
// removeCollaborators(form.groupName, repositoryName) // removeCollaborators(form.groupName, repositoryName)
@@ -505,13 +501,12 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
// addCollaborator(form.groupName, repositoryName, userName) // addCollaborator(form.groupName, repositoryName, userName)
// } // }
// } // }
} }
updateImage(form.groupName, form.fileId, form.clearImage) updateImage(form.groupName, form.fileId, form.clearImage)
redirect("/admin/users") redirect("/admin/users")
} getOrElse NotFound() } getOrElse NotFound()
}
}) })
get("/admin/data")(adminOnly { get("/admin/data")(adminOnly {
@@ -559,12 +554,11 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
protected def disableByNotYourself(paramName: String): Constraint = protected def disableByNotYourself(paramName: String): Constraint =
new Constraint() { new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] = { override def validate(name: String, value: String, messages: Messages): Option[String] = {
params.get(paramName).flatMap { userName => for {
if (userName == context.loginAccount.get.userName && params.get("removed") == Some("true")) userName <- params.get(paramName)
Some("You can't disable your account yourself") loginAccount <- context.loginAccount
else if userName == loginAccount.userName && params.get("removed") == Some("true")
None } 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 => get("/:owner/:repository/wiki/:page/_revert/:commitId")(readableUsersOnly { repository =>
if (isEditable(repository)) { context.withLoginAccount {
val pageName = StringUtil.urlDecode(params("page")) loginAccount =>
val Array(from, to) = params("commitId").split("\\.\\.\\.") 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))) { if (revertWikiPage(repository.owner, repository.name, from, to, loginAccount, Some(pageName))) {
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}") redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}")
} else { } else {
flash.update("info", "This patch was not able to be reversed.") flash.update("info", "This patch was not able to be reversed.")
redirect( redirect(
s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}" s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}"
) )
} }
} else Unauthorized() } else Unauthorized()
}
}) })
get("/:owner/:repository/wiki/_revert/:commitId")(readableUsersOnly { repository => get("/:owner/:repository/wiki/_revert/:commitId")(readableUsersOnly { repository =>
if (isEditable(repository)) { context.withLoginAccount {
val Array(from, to) = params("commitId").split("\\.\\.\\.") loginAccount =>
if (isEditable(repository)) {
val Array(from, to) = params("commitId").split("\\.\\.\\.")
if (revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, None)) { if (revertWikiPage(repository.owner, repository.name, from, to, loginAccount, None)) {
redirect(s"/${repository.owner}/${repository.name}/wiki") redirect(s"/${repository.owner}/${repository.name}/wiki")
} else { } else {
flash.update("info", "This patch was not able to be reversed.") flash.update("info", "This patch was not able to be reversed.")
redirect(s"/${repository.owner}/${repository.name}/wiki/_compare/${from}...${to}") redirect(s"/${repository.owner}/${repository.name}/wiki/_compare/${from}...${to}")
} }
} else Unauthorized() } else Unauthorized()
}
}) })
get("/:owner/:repository/wiki/:page/_edit")(readableUsersOnly { repository => get("/:owner/:repository/wiki/:page/_edit")(readableUsersOnly { repository =>
@@ -172,9 +178,9 @@ trait WikiControllerBase extends ControllerBase {
}) })
post("/:owner/:repository/wiki/_edit", editForm)(readableUsersOnly { (form, repository) => post("/:owner/:repository/wiki/_edit", editForm)(readableUsersOnly { (form, repository) =>
if (isEditable(repository)) { context.withLoginAccount {
defining(context.loginAccount.get) { loginAccount =>
loginAccount => if (isEditable(repository)) {
saveWikiPage( saveWikiPage(
repository.owner, repository.owner,
repository.name, repository.name,
@@ -201,8 +207,8 @@ trait WikiControllerBase extends ControllerBase {
} else { } else {
redirect(s"/${repository.owner}/${repository.name}/wiki") redirect(s"/${repository.owner}/${repository.name}/wiki")
} }
} } else Unauthorized()
} else Unauthorized() }
}) })
get("/:owner/:repository/wiki/_new")(readableUsersOnly { repository => get("/:owner/:repository/wiki/_new")(readableUsersOnly { repository =>
@@ -212,9 +218,9 @@ trait WikiControllerBase extends ControllerBase {
}) })
post("/:owner/:repository/wiki/_new", newForm)(readableUsersOnly { (form, repository) => post("/:owner/:repository/wiki/_new", newForm)(readableUsersOnly { (form, repository) =>
if (isEditable(repository)) { context.withLoginAccount {
defining(context.loginAccount.get) { loginAccount =>
loginAccount => if (isEditable(repository)) {
saveWikiPage( saveWikiPage(
repository.owner, repository.owner,
repository.name, repository.name,
@@ -242,27 +248,35 @@ trait WikiControllerBase extends ControllerBase {
} else { } else {
redirect(s"/${repository.owner}/${repository.name}/wiki") redirect(s"/${repository.owner}/${repository.name}/wiki")
} }
} } else Unauthorized()
} else Unauthorized() }
}) })
get("/:owner/:repository/wiki/:page/_delete")(readableUsersOnly { repository => get("/:owner/:repository/wiki/:page/_delete")(readableUsersOnly { repository =>
if (isEditable(repository)) { context.withLoginAccount {
val pageName = StringUtil.urlDecode(params("page")) 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 => redirect(s"/${repository.owner}/${repository.name}/wiki")
val deleteWikiInfo = DeleteWikiInfo( } else Unauthorized()
repository.owner, }
repository.name,
loginAccount.userName,
pageName
)
recordActivity(deleteWikiInfo)
updateLastActivityDate(repository.owner, repository.name)
redirect(s"/${repository.owner}/${repository.name}/wiki")
}
} else Unauthorized()
}) })
get("/:owner/:repository/wiki/_pages")(referrersOnly { repository => 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.service._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.util.{ReadableUsersAuthenticator, ReferrerAuthenticator, RepositoryName} import gitbucket.core.util.{ReadableUsersAuthenticator, ReferrerAuthenticator, RepositoryName}
import org.scalatra.{ActionResult, NoContent} import org.scalatra.ActionResult
trait ApiIssueCommentControllerBase extends ControllerBase { trait ApiIssueCommentControllerBase extends ControllerBase {
self: AccountService 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.{AccountService, IssueCreationService, IssuesService, MilestonesService}
import gitbucket.core.service.IssuesService.IssueSearchCondition import gitbucket.core.service.IssuesService.IssueSearchCondition
import gitbucket.core.service.PullRequestService.PullRequestLimit 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._ import gitbucket.core.util.Implicits._
trait ApiIssueControllerBase extends ControllerBase { trait ApiIssueControllerBase extends ControllerBase {
@@ -47,7 +47,8 @@ trait ApiIssueControllerBase extends ControllerBase {
user = ApiUser(issueUser), user = ApiUser(issueUser),
assignee = assignedUser.map(ApiUser(_)), assignee = assignedUser.map(ApiUser(_)),
labels = getIssueLabels(repository.owner, repository.name, issue.issueId) labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
.map(ApiLabel(_, RepositoryName(repository))) .map(ApiLabel(_, RepositoryName(repository))),
issue.milestoneId.flatMap { getApiMilestone(repository, _) }
) )
}) })
}) })
@@ -69,7 +70,8 @@ trait ApiIssueControllerBase extends ControllerBase {
RepositoryName(repository), RepositoryName(repository),
ApiUser(openedUser), ApiUser(openedUser),
issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)), 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() }) getOrElse NotFound()
@@ -103,7 +105,8 @@ trait ApiIssueControllerBase extends ControllerBase {
ApiUser(loginAccount), ApiUser(loginAccount),
issue.assignedUserName.flatMap(getAccountByUserName(_)).map(ApiUser(_)), issue.assignedUserName.flatMap(getAccountByUserName(_)).map(ApiUser(_)),
getIssueLabels(repository.owner, repository.name, issue.issueId) getIssueLabels(repository.owner, repository.name, issue.issueId)
.map(ApiLabel(_, RepositoryName(repository))) .map(ApiLabel(_, RepositoryName(repository))),
issue.milestoneId.flatMap { getApiMilestone(repository, _) }
) )
) )
}) getOrElse NotFound() }) getOrElse NotFound()

View File

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

View File

@@ -102,17 +102,4 @@ trait ApiIssueMilestoneControllerBase extends ControllerBase {
NoContent() 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 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.controller.ControllerBase
import gitbucket.core.service.{AccountService, RepositoryService} import gitbucket.core.service.{AccountService, RepositoryService}
import gitbucket.core.util.Implicits._ 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) { if (checkConflict(repository.owner, repository.name, pullReq.branch, issueId).isDefined) {
NoContent NoContent
} else { } else {
NotFound NotFound()
} }
}).getOrElse(NotFound) }).getOrElse(NotFound())
}) })
/* /*

View File

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

View File

@@ -40,7 +40,7 @@ trait ApiRepositoryCommitControllerBase extends ControllerBase {
diffs = JGitUtil.getDiffs(git, commitInfo.parents.headOption, commitInfo.id, false, true), diffs = JGitUtil.getDiffs(git, commitInfo.parents.headOption, commitInfo.id, false, true),
author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress), author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress),
committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress), committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress),
commentCount = getCommitComment(repository.owner, repository.name, commitInfo.id.toString).size commentCount = getCommitComment(repository.owner, repository.name, commitInfo.id).size
) )
}) })
} }

View File

@@ -1,10 +1,9 @@
package gitbucket.core.controller.api 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.controller.ControllerBase
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service.{RepositoryCommitFileService, RepositoryService} import gitbucket.core.service.{RepositoryCommitFileService, RepositoryService}
import gitbucket.core.util.Directory.getRepositoryDir 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.util._
import gitbucket.core.view.helpers.{isRenderable, renderMarkup} import gitbucket.core.view.helpers.{isRenderable, renderMarkup}
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
@@ -36,7 +35,7 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
/** /**
* ii. Get contents * 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 => get("/api/v3/repos/:owner/:repository/contents")(referrersOnly { repository =>
getContents(repository, ".", params.getOrElse("ref", repository.repository.defaultBranch)) getContents(repository, ".", params.getOrElse("ref", repository.repository.defaultBranch))
@@ -44,47 +43,50 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
/** /**
* ii. Get contents * 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 => get("/api/v3/repos/:owner/:repository/contents/*")(referrersOnly { repository =>
getContents(repository, multiParams("splat").head, params.getOrElse("ref", repository.repository.defaultBranch)) 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( private def getContents(
repository: RepositoryService.RepositoryInfo, repository: RepositoryService.RepositoryInfo,
path: String, path: String,
refStr: String, refStr: String,
ignoreCase: Boolean = false 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 => Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
val fileList = getFileList(git, refStr, path, maxFiles = context.settings.repositoryViewer.maxFiles) val fileList = getFileList(git, refStr, path, maxFiles = context.settings.repositoryViewer.maxFiles)
if (fileList.isEmpty) { // file or NotFound if (fileList.isEmpty) { // file or NotFound
getFileInfo(git, refStr, path, ignoreCase) val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(refStr))
.flatMap { f => getPathObjectId(git, path, revCommit)
.flatMap { objectId =>
val largeFile = params.get("large_file").exists(s => s.equals("true")) 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 { request.getHeader("Accept") match {
case "application/vnd.github.v3.raw" => { case "application/vnd.github.v3.raw" => {
contentType = "application/vnd.github.v3.raw" contentType = "application/vnd.github.v3.raw"
content 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" contentType = "application/vnd.github.v3.html"
content.map { c => content.map { c =>
List( List(
@@ -115,11 +117,12 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
} }
} }
case _ => case _ =>
Some(JsonFormat(ApiContents(f, RepositoryName(repository), content))) getFileInfo(git, refStr, path).map { f =>
JsonFormat(ApiContents(f, RepositoryName(repository), content))
}
} }
} }
.getOrElse(NotFound()) .getOrElse(NotFound())
} else { // directory } else { // directory
JsonFormat(fileList.map { f => JsonFormat(fileList.map { f =>
ApiContents(f, RepositoryName(repository), None) ApiContents(f, RepositoryName(repository), None)
@@ -127,56 +130,86 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
} }
} }
} }
/*
/**
* iii. Create a file or iv. Update a file * iii. Create a file or iv. Update a file
* https://developer.github.com/v3/repos/contents/#create-a-file * https://docs.github.com/en/rest/reference/repos#create-or-update-file-contents
* https://developer.github.com/v3/repos/contents/#update-a-file
* if sha is presented, update a file else create a file. * if sha is presented, update a file else create a file.
* requested #2112 * requested #2112
*/ */
put("/api/v3/repos/:owner/:repository/contents/*")(writableUsersOnly { repository => put("/api/v3/repos/:owner/:repository/contents/*")(writableUsersOnly { repository =>
JsonFormat(for { context.withLoginAccount {
data <- extractFromJsonBody[CreateAFile] loginAccount =>
} yield { JsonFormat(for {
val branch = data.branch.getOrElse(repository.repository.defaultBranch) data <- extractFromJsonBody[CreateAFile]
val commit = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => } yield {
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch)) val branch = data.branch.getOrElse(repository.repository.defaultBranch)
revCommit.name val commit = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
} val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
val paths = multiParams("splat").head.split("/") revCommit.name
val path = paths.take(paths.size - 1).toList.mkString("/") }
if (data.sha.isDefined && data.sha.get != commit) { val paths = multiParams("splat").head.split("/")
ApiError("The blob SHA is not matched.", Some("https://developer.github.com/v3/repos/contents/#update-a-file")) val path = paths.take(paths.size - 1).toList.mkString("/")
} else { Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) {
val objectId = commitFile( git =>
repository, getFileInfo(git, commit, path) match {
branch, case Some(f) if !data.sha.contains(f.id.getName) =>
path, ApiError(
Some(paths.last), "The blob SHA is not matched.",
data.sha.map(_ => paths.last), Some("https://docs.github.com/en/rest/reference/repos#create-or-update-file-contents")
StringUtil.base64Decode(data.content), )
data.message, case _ =>
commit, val (commitId, blobId) = commitFile(
context.loginAccount.get, repository,
data.committer.map(_.name).getOrElse(context.loginAccount.get.fullName), branch,
data.committer.map(_.email).getOrElse(context.loginAccount.get.mailAddress), path,
context.settings Some(paths.last),
) data.sha.map(_ => paths.last),
ApiContents("file", paths.last, path, objectId.name, None, None)(RepositoryName(repository)) 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 * 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 * should be implemented
*/ */
/* /*
* vi. Get archive link * vi. Download a repository archive (tar/zip)
* https://developer.github.com/v3/repos/contents/#get-archive-link * 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.util.Implicits._
import gitbucket.core.model.Profile.profile.blockingApi._ import gitbucket.core.model.Profile.profile.blockingApi._
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.scalatra.Forbidden
import scala.concurrent.Await import scala.concurrent.Await
import scala.concurrent.duration.Duration import scala.concurrent.duration.Duration
@@ -26,7 +27,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
/** /**
* i. List your repositories * 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 { get("/api/v3/user/repos")(usersOnly {
JsonFormat(getVisibleRepositories(context.loginAccount, Option(context.loginAccount.get.userName)).map { r => JsonFormat(getVisibleRepositories(context.loginAccount, Option(context.loginAccount.get.userName)).map { r =>
@@ -36,7 +37,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
/** /**
* ii. List user repositories * 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") { get("/api/v3/users/:userName/repos") {
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("userName"))).map { r => JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("userName"))).map { r =>
@@ -46,7 +47,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
/** /**
* iii. List organization repositories * 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") { get("/api/v3/orgs/:orgName/repos") {
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("orgName"))).map { r => JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("orgName"))).map { r =>
@@ -56,7 +57,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
/** /**
* iv. List all public repositories * 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") { get("/api/v3/repositories") {
JsonFormat(getPublicRepositories().map { r => JsonFormat(getPublicRepositories().map { r =>
@@ -66,13 +67,12 @@ trait ApiRepositoryControllerBase extends ControllerBase {
/* /*
* v. Create * v. Create
* https://developer.github.com/v3/repos/#create
* Implemented with two methods (user/orgs) * Implemented with two methods (user/orgs)
*/ */
/** /**
* Create user repository * 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 { post("/api/v3/user/repos")(usersOnly {
val owner = context.loginAccount.get.userName val owner = context.loginAccount.get.userName
@@ -80,7 +80,12 @@ trait ApiRepositoryControllerBase extends ControllerBase {
data <- extractFromJsonBody[CreateARepository] if data.isValid data <- extractFromJsonBody[CreateARepository] if data.isValid
} yield { } yield {
LockUtil.lock(s"${owner}/${data.name}") { 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( val f = createRepository(
context.loginAccount.get, context.loginAccount.get,
owner, owner,
@@ -95,11 +100,6 @@ trait ApiRepositoryControllerBase extends ControllerBase {
getRepository(owner, data.name)(session).get getRepository(owner, data.name)(session).get
} }
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(owner).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() }) getOrElse NotFound()
@@ -107,15 +107,22 @@ trait ApiRepositoryControllerBase extends ControllerBase {
/** /**
* Create group repository * 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") val groupName = params("org")
(for { (for {
data <- extractFromJsonBody[CreateARepository] if data.isValid data <- extractFromJsonBody[CreateARepository] if data.isValid
} yield { } yield {
LockUtil.lock(s"${groupName}/${data.name}") { 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( val f = createRepository(
context.loginAccount.get, context.loginAccount.get,
groupName, groupName,
@@ -129,11 +136,6 @@ trait ApiRepositoryControllerBase extends ControllerBase {
getRepository(groupName, data.name).get getRepository(groupName, data.name).get
} }
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(groupName).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() }) getOrElse NotFound()
@@ -141,7 +143,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
/* /*
* vi. Get * 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 => get("/api/v3/repos/:owner/:repository")(referrersOnly { repository =>
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(repository.owner).get))) JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(repository.owner).get)))
@@ -149,32 +151,32 @@ trait ApiRepositoryControllerBase extends ControllerBase {
/* /*
* vii. Edit * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 { delete("/api/v3/users/:userName/suspended")(adminOnly {
val userName = params("userName") val userName = params("userName")
getAccountByUserName(userName) match { getAccountByUserName(userName, true) match {
case Some(targetAccount) => case Some(targetAccount) =>
updateAccount(targetAccount.copy(isRemoved = false)) updateAccount(targetAccount.copy(isRemoved = false))
NoContent() NoContent()

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,7 @@ trait AccountPreferenceComponent { self: Profile =>
val userName = column[String]("USER_NAME", O PrimaryKey) val userName = column[String]("USER_NAME", O PrimaryKey)
val highlighterTheme = column[String]("HIGHLIGHTER_THEME") val highlighterTheme = column[String]("HIGHLIGHTER_THEME")
def * = def * =
(userName, highlighterTheme) <> (AccountPreference.tupled, AccountPreference.unapply) (userName, highlighterTheme).<>(AccountPreference.tupled, AccountPreference.unapply)
def byPrimaryKey(userName: String): Rep[Boolean] = this.userName === userName.bind 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 => trait AccountWebHookComponent extends TemplateComponent { self: Profile =>
import profile.api._ import profile.api._
private implicit val whContentTypeColumnType = private implicit val whContentTypeColumnType: BaseColumnType[WebHookContentType] =
MappedColumnType.base[WebHookContentType, String](whct => whct.code, code => WebHookContentType.valueOf(code)) MappedColumnType.base[WebHookContentType, String](whct => whct.code, code => WebHookContentType.valueOf(code))
lazy val AccountWebHooks = TableQuery[AccountWebHooks] lazy val AccountWebHooks = TableQuery[AccountWebHooks]
@@ -12,7 +12,7 @@ trait AccountWebHookComponent extends TemplateComponent { self: Profile =>
val url = column[String]("URL") val url = column[String]("URL")
val token = column[Option[String]]("TOKEN") val token = column[Option[String]]("TOKEN")
val ctype = column[WebHookContentType]("CTYPE") val ctype = column[WebHookContentType]("CTYPE")
def * = (userName, url, ctype, token) <> ((AccountWebHook.apply _).tupled, AccountWebHook.unapply) def * = (userName, url, ctype, token).<>((AccountWebHook.apply _).tupled, AccountWebHook.unapply)
def byPrimaryKey(userName: String, url: String) = (this.userName === userName.bind) && (this.url === url.bind) def byPrimaryKey(userName: String, url: String) = (this.userName === userName.bind) && (this.url === url.bind)
} }

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,8 @@ trait CommitStatusComponent extends TemplateComponent { self: Profile =>
import profile.api._ import profile.api._
import self._ 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] lazy val CommitStatuses = TableQuery[CommitStatuses]
class CommitStatuses(tag: Tag) extends Table[CommitStatus](tag, "COMMIT_STATUS") with CommitTemplate { class CommitStatuses(tag: Tag) extends Table[CommitStatus](tag, "COMMIT_STATUS") with CommitTemplate {
@@ -29,7 +30,7 @@ trait CommitStatusComponent extends TemplateComponent { self: Profile =>
creator, creator,
registeredDate, registeredDate,
updatedDate updatedDate
) <> ((CommitStatus.apply _).tupled, CommitStatus.unapply) ).<>((CommitStatus.apply _).tupled, CommitStatus.unapply)
def byPrimaryKey(id: Int) = commitStatusId === id.bind def byPrimaryKey(id: Int) = commitStatusId === id.bind
} }
} }
@@ -77,7 +78,7 @@ object CommitState {
val values: Vector[CommitState] = Vector(PENDING, SUCCESS, ERROR, FAILURE) 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) 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 publicKey = column[String]("PUBLIC_KEY")
val allowWrite = column[Boolean]("ALLOW_WRITE") val allowWrite = column[Boolean]("ALLOW_WRITE")
def * = def * =
(userName, repositoryName, deployKeyId, title, publicKey, allowWrite) <> (DeployKey.tupled, DeployKey.unapply) (userName, repositoryName, deployKeyId, title, publicKey, allowWrite).<>(DeployKey.tupled, DeployKey.unapply)
def byPrimaryKey(userName: String, repositoryName: String, deployKeyId: Int) = def byPrimaryKey(userName: String, repositoryName: String, deployKeyId: Int) =
(this.userName === userName.bind) && (this.repositoryName === repositoryName.bind) && (this.deployKeyId === deployKeyId.bind) (this.userName === userName.bind) && (this.repositoryName === repositoryName.bind) && (this.deployKeyId === deployKeyId.bind)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,9 +2,7 @@ package gitbucket.core.model
import java.util.Date import java.util.Date
trait ReleaseAssetComponent extends TemplateComponent { trait ReleaseAssetComponent extends TemplateComponent { self: Profile =>
self: Profile =>
import profile.api._ import profile.api._
import self._ import self._
@@ -21,7 +19,8 @@ trait ReleaseAssetComponent extends TemplateComponent {
val updatedDate = column[Date]("UPDATED_DATE") val updatedDate = column[Date]("UPDATED_DATE")
def * = 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) = def byPrimaryKey(owner: String, repository: String, tag: String, fileName: String) =
byTag(owner, repository, tag) && (this.fileName === fileName.bind) byTag(owner, repository, tag) && (this.fileName === fileName.bind)
def byTag(owner: String, repository: String, tag: String) = def byTag(owner: String, repository: String, tag: String) =

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,7 @@ object WebHookContentType {
val values: Vector[WebHookContentType] = Vector(JSON, FORM) 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) def apply(code: String): WebHookContentType = map(code)

View File

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

View File

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

View File

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

View File

@@ -13,7 +13,6 @@ import gitbucket.core.model.activity.{
} }
import gitbucket.core.plugin.{IssueHook, PluginRegistry} import gitbucket.core.plugin.{IssueHook, PluginRegistry}
import gitbucket.core.service.RepositoryService.RepositoryInfo import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
trait HandleCommentService { trait HandleCommentService {
@@ -34,105 +33,104 @@ trait HandleCommentService {
actionOpt: Option[String] actionOpt: Option[String]
)(implicit context: Context, s: Session) = { )(implicit context: Context, s: Session) = {
context.loginAccount.flatMap { loginAccount => context.loginAccount.flatMap { loginAccount =>
defining(repository.owner, repository.name) { val owner = repository.owner
case (owner, name) => val name = repository.name
val userName = loginAccount.userName val userName = loginAccount.userName
actionOpt.collect { actionOpt.collect {
case "close" if !issue.closed => case "close" if !issue.closed =>
updateClosed(owner, name, issue.issueId, true) updateClosed(owner, name, issue.issueId, true)
case "reopen" if issue.closed => case "reopen" if issue.closed =>
updateClosed(owner, name, issue.issueId, false) 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 -> _)
} }
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] content: Option[String]
)(implicit context: Context, s: Session): Option[(Issue, Int)] = { )(implicit context: Context, s: Session): Option[(Issue, Int)] = {
context.loginAccount.flatMap { loginAccount => context.loginAccount.flatMap { loginAccount =>
defining(repository.owner, repository.name) { val owner = repository.owner
case (owner, name) => val name = repository.name
val userName = loginAccount.userName val userName = loginAccount.userName
content match { content match {
case Some(content) => case Some(content) =>
// Update comment // Update comment
val _commentId = Some(updateComment(issue.issueId, commentId.toInt, content)) val _commentId = Some(updateComment(issue.issueId, commentId.toInt, content))
// Record comment activity // Record comment activity
val commentInfo = if (issue.isPullRequest) { val commentInfo = if (issue.isPullRequest) {
PullRequestCommentInfo(owner, name, userName, content, issue.issueId) PullRequestCommentInfo(owner, name, userName, content, issue.issueId)
} else { } else {
IssueCommentInfo(owner, name, userName, content, issue.issueId) 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
} }
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.Implicits._
import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.controller.Context import gitbucket.core.controller.Context
import gitbucket.core.model.{ import gitbucket.core.model.{Account, Issue, IssueComment, IssueLabel, Label, PullRequest, Repository, Role}
Account,
CommitState,
Issue,
IssueComment,
IssueLabel,
Label,
PullRequest,
Repository,
Role
}
import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile._ import gitbucket.core.model.Profile.profile._
import gitbucket.core.model.Profile.profile.blockingApi._ import gitbucket.core.model.Profile.profile.blockingApi._
@@ -346,54 +336,72 @@ trait IssuesService {
implicit s: Session implicit s: Session
) = ) =
Issues filter { t1 => Issues filter { t1 =>
(if (repos.size == 1) { (if (repos.sizeIs == 1) {
t1.byRepository(repos.head._1, repos.head._2) t1.byRepository(repos.head._1, repos.head._2)
} else { } else {
((t1.userName ++ "/" ++ t1.repositoryName) inSetBind (repos.map { case (owner, repo) => s"$owner/$repo" })) ((t1.userName ++ "/" ++ t1.repositoryName) inSetBind (repos.map { case (owner, repo) => s"$owner/$repo" }))
}) && }) &&
(t1.closed === (condition.state == "closed").bind) && (condition.state match {
(t1.milestoneId.? isEmpty, condition.milestone == Some(None)) && case "open" => t1.closed === false
(t1.priorityId.? isEmpty, condition.priority == Some(None)) && case "closed" => t1.closed === true
(t1.assignedUserName.? isEmpty, condition.assigned == Some(None)) && case _ => t1.closed === true || t1.closed === false
(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) && }).&&(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 { (searchOption match {
case IssueSearchOption.Issues => t1.pullRequest === false case IssueSearchOption.Issues => t1.pullRequest === false
case IssueSearchOption.PullRequests => t1.pullRequest === true case IssueSearchOption.PullRequests => t1.pullRequest === true
case IssueSearchOption.Both => t1.pullRequest === false || t1.pullRequest === true case IssueSearchOption.Both => t1.pullRequest === false || t1.pullRequest === true
}) && })
// Milestone filter // Milestone filter
(Milestones filter { t2 => .&&(
(t2.byPrimaryKey(t1.userName, t1.repositoryName, t1.milestoneId)) && Milestones filter { t2 =>
(t2.title === condition.milestone.get.get.bind) (t2.byPrimaryKey(t1.userName, t1.repositoryName, t1.milestoneId)) &&
} exists, condition.milestone.flatten.isDefined) && (t2.title === condition.milestone.get.get.bind)
// Priority filter } exists,
(Priorities filter { t2 => condition.milestone.flatten.isDefined
(t2.byPrimaryKey(t1.userName, t1.repositoryName, t1.priorityId)) && )
(t2.priorityName === condition.priority.get.get.bind) // Priority filter
} exists, condition.priority.flatten.isDefined) && .&&(
// Assignee filter Priorities filter { t2 =>
(t1.assignedUserName === condition.assigned.get.get.bind, condition.assigned.flatten.isDefined) && (t2.byPrimaryKey(t1.userName, t1.repositoryName, t1.priorityId)) &&
// Label filter (t2.priorityName === condition.priority.get.get.bind)
(IssueLabels filter { t2 => } exists,
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) && condition.priority.flatten.isDefined
(t2.labelId in )
(Labels filter { t3 => // Assignee filter
(t3.byRepository(t1.userName, t1.repositoryName)) && .&&(t1.assignedUserName === condition.assigned.get.get.bind, condition.assigned.flatten.isDefined)
(t3.labelName inSetBind condition.labels) // Label filter
} map (_.labelId))) .&&(
} exists, condition.labels.nonEmpty) && IssueLabels filter { t2 =>
// Visibility filter (t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) &&
(Repositories filter { t2 => (t2.labelId in
(t2.byRepository(t1.userName, t1.repositoryName)) && (Labels filter { t3 =>
(t2.isPrivate === (condition.visibility == Some("private")).bind) (t3.byRepository(t1.userName, t1.repositoryName)) &&
} exists, condition.visibility.nonEmpty) && (t3.labelName inSetBind condition.labels)
// Organization (group) filter } map (_.labelId)))
(t1.userName inSetBind condition.groups, condition.groups.nonEmpty) && } exists,
// Mentioned filter condition.labels.nonEmpty
((t1.openedUserName === condition.mentioned.get.bind) || t1.assignedUserName === condition.mentioned.get.bind || )
(IssueComments filter { t2 => // Visibility filter
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) && (t2.commentedUserName === condition.mentioned.get.bind) .&&(
} exists), condition.mentioned.isDefined) 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( def insertIssue(
@@ -692,8 +700,8 @@ trait IssuesService {
case (t1, t2) => case (t1, t2) =>
keywords keywords
.map { keyword => .map { keyword =>
(t1.title.toLowerCase like (s"%${likeEncode(keyword)}%", '^')) || (t1.title.toLowerCase.like(s"%${likeEncode(keyword)}%", '^')) ||
(t1.content.toLowerCase like (s"%${likeEncode(keyword)}%", '^')) (t1.content.toLowerCase.like(s"%${likeEncode(keyword)}%", '^'))
} }
.reduceLeft(_ && _) .reduceLeft(_ && _)
} }
@@ -720,7 +728,7 @@ trait IssuesService {
t2.pullRequest === pullRequest.bind && t2.pullRequest === pullRequest.bind &&
keywords keywords
.map { query => .map { query =>
t1.content.toLowerCase like (s"%${likeEncode(query)}%", '^') t1.content.toLowerCase.like(s"%${likeEncode(query)}%", '^')
} }
.reduceLeft(_ && _) .reduceLeft(_ && _)
} }
@@ -763,16 +771,37 @@ trait IssuesService {
def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String, loginAccount: Account)( def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String, loginAccount: Account)(
implicit s: Session implicit s: Session
): Unit = { ): Unit = {
extractIssueId(message).foreach { issueId => extractGlobalIssueId(message).foreach {
val content = s"${fromIssue.issueId}:${fromIssue.title}" case (_referredOwner, _referredRepository, referredIssueId) =>
if (getIssue(owner, repository, issueId).isDefined) { val referredOwner = _referredOwner.getOrElse(owner)
// Not add if refer comment already exist. val referredRepository = _referredRepository.getOrElse(repository)
if (!getComments(owner, repository, issueId.toInt).exists { x => getRepository(referredOwner, referredRepository).foreach { repo =>
x.action == "refer" && x.content == content if (isReadable(repo.repository, Option(loginAccount))) {
}) { getIssue(referredOwner, referredRepository, referredIssueId.get).foreach { _ =>
createComment(owner, repository, loginAccount.userName, issueId.toInt, content, "refer") 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) case x => Some(x)
}, },
param(request, "mentioned"), 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, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"),
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"), param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
param(request, "visibility"), param(request, "visibility"),
@@ -934,7 +963,7 @@ object IssuesService {
case x => Some(x) case x => Some(x)
}, },
param(request, "mentioned"), 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, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"),
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"), param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
param(request, "visibility"), 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.Profile.profile.blockingApi._
import gitbucket.core.model.activity.{CloseIssueInfo, MergeInfo, PushInfo} import gitbucket.core.model.activity.{CloseIssueInfo, MergeInfo, PushInfo}
import gitbucket.core.service.SystemSettingsService.SystemSettings import gitbucket.core.service.SystemSettingsService.SystemSettings
import gitbucket.core.service.WebHookService.WebHookPushPayload
import gitbucket.core.util.JGitUtil.CommitInfo import gitbucket.core.util.JGitUtil.CommitInfo
import org.eclipse.jgit.merge.{MergeStrategy, Merger, RecursiveMerger} import org.eclipse.jgit.merge.{MergeStrategy, Merger, RecursiveMerger}
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
@@ -27,7 +28,8 @@ trait MergeService {
with IssuesService with IssuesService
with RepositoryService with RepositoryService
with PullRequestService with PullRequestService
with WebHookPullRequestService => with WebHookPullRequestService
with WebHookService =>
import MergeService._ import MergeService._
@@ -61,40 +63,91 @@ trait MergeService {
/** merge the pull request with a merge commit */ /** merge the pull request with a merge commit */
def mergeWithMergeCommit( def mergeWithMergeCommit(
git: Git, git: Git,
userName: String, repository: RepositoryInfo,
repositoryName: String,
branch: String, branch: String,
issueId: Int, issueId: Int,
message: String, message: String,
committer: PersonIdent loginAccount: Account,
)(implicit s: Session): ObjectId = { settings: SystemSettings
new MergeCacheInfo(git, userName, repositoryName, branch, issueId, getReceiveHooks()).merge(message, committer) )(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 */ /** rebase to the head of the pull request branch */
def mergeWithRebase( def mergeWithRebase(
git: Git, git: Git,
userName: String, repository: RepositoryInfo,
repositoryName: String,
branch: String, branch: String,
issueId: Int, issueId: Int,
commits: Seq[RevCommit], commits: Seq[RevCommit],
committer: PersonIdent loginAccount: Account,
)(implicit s: Session): ObjectId = { settings: SystemSettings
new MergeCacheInfo(git, userName, repositoryName, branch, issueId, getReceiveHooks()).rebase(committer, commits) )(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 */ /** squash commits in the pull request and append it */
def mergeWithSquash( def mergeWithSquash(
git: Git, git: Git,
userName: String, repository: RepositoryInfo,
repositoryName: String,
branch: String, branch: String,
issueId: Int, issueId: Int,
message: String, message: String,
committer: PersonIdent loginAccount: Account,
)(implicit s: Session): ObjectId = { settings: SystemSettings
new MergeCacheInfo(git, userName, repositoryName, branch, issueId, getReceiveHooks()).squash(message, committer) )(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 */ /** fetch remote branch to my repository refs/pull/{issueId}/head */
@@ -303,7 +356,8 @@ trait MergeService {
message, message,
strategy, strategy,
commits, commits,
getReceiveHooks() getReceiveHooks(),
settings
) match { ) match {
case Some(newCommitId) => case Some(newCommitId) =>
// mark issue as merged and close. // mark issue as merged and close.
@@ -428,8 +482,9 @@ trait MergeService {
message: String, message: String,
strategy: String, strategy: String,
commits: Seq[Seq[CommitInfo]], commits: Seq[Seq[CommitInfo]],
receiveHooks: Seq[ReceiveHook] receiveHooks: Seq[ReceiveHook],
)(implicit s: Session): Option[ObjectId] = { settings: SystemSettings
)(implicit s: Session, c: JsonFormat.Context): Option[ObjectId] = {
val revCommits = Using val revCommits = Using
.resource(new RevWalk(git.getRepository)) { revWalk => .resource(new RevWalk(git.getRepository)) { revWalk =>
commits.flatten.map { commit => commits.flatten.map { commit =>
@@ -443,36 +498,36 @@ trait MergeService {
Some( Some(
mergeWithMergeCommit( mergeWithMergeCommit(
git, git,
repository.owner, repository,
repository.name,
pullRequest.branch, pullRequest.branch,
issue.issueId, issue.issueId,
s"Merge pull request #${issue.issueId} from ${pullRequest.requestUserName}/${pullRequest.requestBranch}\n\n" + message, s"Merge pull request #${issue.issueId} from ${pullRequest.requestUserName}/${pullRequest.requestBranch}\n\n" + message,
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress) loginAccount,
settings
) )
) )
case "rebase" => case "rebase" =>
Some( Some(
mergeWithRebase( mergeWithRebase(
git, git,
repository.owner, repository,
repository.name,
pullRequest.branch, pullRequest.branch,
issue.issueId, issue.issueId,
revCommits, revCommits,
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress) loginAccount,
settings
) )
) )
case "squash" => case "squash" =>
Some( Some(
mergeWithSquash( mergeWithSquash(
git, git,
repository.owner, repository,
repository.name,
pullRequest.branch, pullRequest.branch,
issue.issueId, issue.issueId,
s"${issue.title} (#${issue.issueId})\n\n" + message, s"${issue.title} (#${issue.issueId})\n\n" + message,
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress) loginAccount,
settings
) )
) )
case _ => case _ =>
@@ -561,7 +616,7 @@ object MergeService {
} }
def checkConflict(): Option[String] = { def checkConflict(): Option[String] = {
checkConflictCache.getOrElse(checkConflictForce) checkConflictCache().getOrElse(checkConflictForce())
} }
def checkConflictForce(): Option[String] = { def checkConflictForce(): Option[String] = {
@@ -593,7 +648,7 @@ object MergeService {
} }
// update branch from cache // 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) { if (checkConflict().isDefined) {
throw new RuntimeException("This pull request can't merge automatically.") throw new RuntimeException("This pull request can't merge automatically.")
} }
@@ -610,7 +665,7 @@ object MergeService {
// call pre-commit hooks // call pre-commit hooks
val error = receiveHooks.flatMap { hook => val error = receiveHooks.flatMap { hook =>
hook.preReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true) hook.preReceive(userName, repositoryName, receivePack, receiveCommand, pusher, true)
}.headOption }.headOption
error.foreach { error => error.foreach { error =>
@@ -628,7 +683,7 @@ object MergeService {
objectId 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) { if (checkConflict().isDefined) {
throw new RuntimeException("This pull request can't merge automatically.") throw new RuntimeException("This pull request can't merge automatically.")
} }
@@ -664,7 +719,7 @@ object MergeService {
// call pre-commit hooks // call pre-commit hooks
val error = receiveHooks.flatMap { hook => val error = receiveHooks.flatMap { hook =>
hook.preReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true) hook.preReceive(userName, repositoryName, receivePack, receiveCommand, pusher, true)
}.headOption }.headOption
error.foreach { error => error.foreach { error =>
@@ -683,7 +738,7 @@ object MergeService {
objectId 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) { if (checkConflict().isDefined) {
throw new RuntimeException("This pull request can't merge automatically.") throw new RuntimeException("This pull request can't merge automatically.")
} }
@@ -714,7 +769,7 @@ object MergeService {
// call pre-commit hooks // call pre-commit hooks
val error = receiveHooks.flatMap { hook => val error = receiveHooks.flatMap { hook =>
hook.preReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true) hook.preReceive(userName, repositoryName, receivePack, receiveCommand, pusher, true)
}.headOption }.headOption
error.foreach { error => error.foreach { error =>

View File

@@ -1,9 +1,11 @@
package gitbucket.core.service package gitbucket.core.service
import gitbucket.core.api.ApiMilestone
import gitbucket.core.model.Milestone import gitbucket.core.model.Milestone
import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile.blockingApi._ import gitbucket.core.model.Profile.profile.blockingApi._
import gitbucket.core.model.Profile.dateColumnType import gitbucket.core.model.Profile.dateColumnType
import gitbucket.core.service.RepositoryService.RepositoryInfo
trait MilestonesService { trait MilestonesService {
@@ -73,4 +75,17 @@ trait MilestonesService {
.sortBy(t => (t.dueDate.asc, t.closedDate.desc, t.milestoneId.desc)) .sortBy(t => (t.dueDate.asc, t.closedDate.desc, t.milestoneId.desc))
.list .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 redirectURI: URI
): Option[AuthenticationSuccessResponse] = ): Option[AuthenticationSuccessResponse] =
try { try {
AuthenticationResponseParser.parse(redirectURI, params.asJava) match { AuthenticationResponseParser.parse(
redirectURI,
params.map { case (key, value) => (key, List(value).asJava) }.asJava
) match {
case response: AuthenticationSuccessResponse => case response: AuthenticationSuccessResponse =>
if (response.getState == state) { if (response.getState == state) {
Some(response) Some(response)

View File

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

View File

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

View File

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

View File

@@ -23,23 +23,29 @@ trait RepositoryCommitFileService {
with PullRequestService with PullRequestService
with WebHookPullRequestService with WebHookPullRequestService
with RepositoryService => with RepositoryService =>
import RepositoryCommitFileService._
/**
* Create multiple files by callback function.
* Returns commitId.
*/
def commitFiles( def commitFiles(
repository: RepositoryService.RepositoryInfo, repository: RepositoryService.RepositoryInfo,
files: Seq[CommitFile],
branch: String, branch: String,
path: String,
message: String, message: String,
loginAccount: Account, loginAccount: Account,
settings: SystemSettings settings: SystemSettings
)( )(
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit
)(implicit s: Session, c: JsonFormat.Context) = { )(implicit s: Session, c: JsonFormat.Context): ObjectId = {
// prepend path to the filename _createFiles(repository, branch, message, loginAccount, loginAccount.fullName, loginAccount.mailAddress, settings)(
_commitFile(repository, branch, message, loginAccount, loginAccount.fullName, loginAccount.mailAddress, settings)(f) f
)._1
} }
/**
* Create a file from string content.
* Returns commitId + blobId.
*/
def commitFile( def commitFile(
repository: RepositoryService.RepositoryInfo, repository: RepositoryService.RepositoryInfo,
branch: String, branch: String,
@@ -52,7 +58,7 @@ trait RepositoryCommitFileService {
commit: String, commit: String,
loginAccount: Account, loginAccount: Account,
settings: SystemSettings settings: SystemSettings
)(implicit s: Session, c: JsonFormat.Context): ObjectId = { )(implicit s: Session, c: JsonFormat.Context): (ObjectId, Option[ObjectId]) = {
commitFile( commitFile(
repository, repository,
branch, branch,
@@ -69,6 +75,10 @@ trait RepositoryCommitFileService {
) )
} }
/**
* Create a file from byte array content.
* Returns commitId + blobId.
*/
def commitFile( def commitFile(
repository: RepositoryService.RepositoryInfo, repository: RepositoryService.RepositoryInfo,
branch: String, branch: String,
@@ -78,11 +88,11 @@ trait RepositoryCommitFileService {
content: Array[Byte], content: Array[Byte],
message: String, message: String,
commit: String, commit: String,
loginAccount: Account, pusherAccount: Account,
fullName: String, committerName: String,
mailAddress: String, committerMailAddress: String,
settings: SystemSettings settings: SystemSettings
)(implicit s: Session, c: JsonFormat.Context): ObjectId = { )(implicit s: Session, c: JsonFormat.Context): (ObjectId, Option[ObjectId]) = {
val newPath = newFileName.map { newFileName => val newPath = newFileName.map { newFileName =>
if (path.length == 0) newFileName else s"${path}/${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}" 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) => case (git, headTip, builder, inserter) =>
if (headTip.getName == commit) { if (headTip.getName == commit) {
val permission = JGitUtil val permission = JGitUtil
@@ -105,28 +115,33 @@ trait RepositoryCommitFileService {
} }
.flatten .flatten
.headOption .headOption
.map { bits =>
newPath.foreach { newPath =>
builder.add(JGitUtil.createDirCacheEntry(newPath, permission.map { bits =>
FileMode.fromBits(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() builder.finish()
} objectId
} else None
} }
} }
private def _commitFile( private def _createFiles[R](
repository: RepositoryService.RepositoryInfo, repository: RepositoryService.RepositoryInfo,
branch: String, branch: String,
message: String, message: String,
loginAccount: Account, pusherAccount: Account,
committerName: String, committerName: String,
committerMailAddress: String, committerMailAddress: String,
settings: SystemSettings settings: SystemSettings
)( )(
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => R
)(implicit s: Session, c: JsonFormat.Context): ObjectId = { )(implicit s: Session, c: JsonFormat.Context): (ObjectId, R) = {
LockUtil.lock(s"${repository.owner}/${repository.name}") { LockUtil.lock(s"${repository.owner}/${repository.name}") {
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
@@ -135,7 +150,7 @@ trait RepositoryCommitFileService {
val headName = s"refs/heads/${branch}" val headName = s"refs/heads/${branch}"
val headTip = git.getRepository.resolve(headName) val headTip = git.getRepository.resolve(headName)
f(git, headTip, builder, inserter) val result = f(git, headTip, builder, inserter)
val commitId = JGitUtil.createNewCommit( val commitId = JGitUtil.createNewCommit(
git, git,
@@ -156,7 +171,7 @@ trait RepositoryCommitFileService {
// call pre-commit hook // call pre-commit hook
val error = PluginRegistry().getReceiveHooks.flatMap { 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 }.headOption
error match { error match {
@@ -177,12 +192,12 @@ trait RepositoryCommitFileService {
refUpdate.update() refUpdate.update()
// update pull request // update pull request
updatePullRequests(repository.owner, repository.name, branch, loginAccount, "synchronize", settings) updatePullRequests(repository.owner, repository.name, branch, pusherAccount, "synchronize", settings)
// record activity // record activity
updateLastActivityDate(repository.owner, repository.name) updateLastActivityDate(repository.owner, repository.name)
val commitInfo = new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId)) 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) recordActivity(pushInfo)
// create issue comment by commit message // create issue comment by commit message
@@ -192,17 +207,17 @@ trait RepositoryCommitFileService {
if (branch == repository.repository.defaultBranch) { if (branch == repository.repository.defaultBranch) {
closeIssuesFromMessage(message, committerName, repository.owner, repository.name).foreach { issueId => closeIssuesFromMessage(message, committerName, repository.owner, repository.name).foreach { issueId =>
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue => getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
callIssuesWebHook("closed", repository, issue, loginAccount, settings) callIssuesWebHook("closed", repository, issue, pusherAccount, settings)
val closeIssueInfo = CloseIssueInfo( val closeIssueInfo = CloseIssueInfo(
repository.owner, repository.owner,
repository.name, repository.name,
loginAccount.userName, pusherAccount.userName,
issue.issueId, issue.issueId,
issue.title issue.title
) )
recordActivity(closeIssueInfo) recordActivity(closeIssueInfo)
PluginRegistry().getIssueHooks 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 => getAccountByUserName(repository.owner).map { ownerAccount =>
WebHookPushPayload( WebHookPushPayload(
git, git,
loginAccount, pusherAccount,
headName, headName,
repository, repository,
List(commit), 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]]() private val Creating = new ConcurrentHashMap[String, Option[String]]()
def isCreating(owner: String, repository: String): Boolean = { 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 = { def startCreation(owner: String, repository: String): Unit = {
@@ -40,7 +40,7 @@ object RepositoryCreationService {
} }
def getCreationError(owner: String, repository: String): Option[String] = { 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 ActivityService
with PrioritiesService => with PrioritiesService =>
def canCreateRepository(repositoryOwner: String, loginAccount: Account)(implicit session: Session): Boolean = {
repositoryOwner == loginAccount.userName || getGroupsByUserName(loginAccount.userName)
.contains(repositoryOwner) || loginAccount.isAdmin
}
def createRepository( def createRepository(
loginAccount: Account, loginAccount: Account,
owner: String, owner: String,
@@ -76,7 +81,7 @@ trait RepositoryCreationService {
RepositoryCreationService.startCreation(owner, name) RepositoryCreationService.startCreation(owner, name)
try { try {
Database() withTransaction { implicit session => Database() withTransaction { implicit session =>
val ownerAccount = getAccountByUserName(owner).get //val ownerAccount = getAccountByUserName(owner).get
val loginUserName = loginAccount.userName val loginUserName = loginAccount.userName
val copyRepositoryDir = if (initOption == "COPY") { val copyRepositoryDir = if (initOption == "COPY") {

View File

@@ -2,7 +2,6 @@ package gitbucket.core.service
import gitbucket.core.controller.Context import gitbucket.core.controller.Context
import gitbucket.core.util._ import gitbucket.core.util._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.model.{CommitComments => _, Session => _, _} import gitbucket.core.model.{CommitComments => _, Session => _, _}
import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile.blockingApi._ 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 gitbucket.core.util.JGitUtil.FileInfo
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.{Repository => _, _} import org.eclipse.jgit.lib.{Repository => _}
import scala.util.Using import scala.util.Using
trait RepositoryService { trait RepositoryService {
@@ -61,7 +60,8 @@ trait RepositoryService {
externalWikiUrl = None, externalWikiUrl = None,
allowFork = true, allowFork = true,
mergeOptions = "merge-commit,squash,rebase", mergeOptions = "merge-commit,squash,rebase",
defaultMergeOption = "merge-commit" defaultMergeOption = "merge-commit",
safeMode = true
) )
) )
@@ -202,22 +202,19 @@ trait RepositoryService {
) )
// Move git repository // Move git repository
defining(getRepositoryDir(oldUserName, oldRepositoryName)) { dir => val repoDir = getRepositoryDir(oldUserName, oldRepositoryName)
if (dir.isDirectory) { if (repoDir.isDirectory) {
FileUtils.moveDirectory(dir, getRepositoryDir(newUserName, newRepositoryName)) FileUtils.moveDirectory(repoDir, getRepositoryDir(newUserName, newRepositoryName))
}
} }
// Move wiki repository // Move wiki repository
defining(getWikiRepositoryDir(oldUserName, oldRepositoryName)) { dir => val wikiDir = getWikiRepositoryDir(oldUserName, oldRepositoryName)
if (dir.isDirectory) { if (wikiDir.isDirectory) {
FileUtils.moveDirectory(dir, getWikiRepositoryDir(newUserName, newRepositoryName)) FileUtils.moveDirectory(wikiDir, getWikiRepositoryDir(newUserName, newRepositoryName))
}
} }
// Move files directory // Move files directory
defining(getRepositoryFilesDir(oldUserName, oldRepositoryName)) { dir => val filesDir = getRepositoryFilesDir(oldUserName, oldRepositoryName)
if (dir.isDirectory) { if (filesDir.isDirectory) {
FileUtils.moveDirectory(dir, getRepositoryFilesDir(newUserName, newRepositoryName)) FileUtils.moveDirectory(filesDir, getRepositoryFilesDir(newUserName, newRepositoryName))
}
} }
// Delete parent directory // Delete parent directory
FileUtil.deleteDirectoryIfEmpty(getRepositoryFilesDir(oldUserName, oldRepositoryName)) FileUtil.deleteDirectoryIfEmpty(getRepositoryFilesDir(oldUserName, oldRepositoryName))
@@ -237,10 +234,11 @@ trait RepositoryService {
LockUtil.lock(s"${repository.userName}/${repository.repositoryName}") { LockUtil.lock(s"${repository.userName}/${repository.repositoryName}") {
deleteRepositoryOnModel(repository.userName, repository.repositoryName) deleteRepositoryOnModel(repository.userName, repository.repositoryName)
FileUtils.deleteDirectory(getRepositoryDir(repository.userName, repository.repositoryName)) FileUtil.deleteRecursively(getRepositoryDir(repository.userName, repository.repositoryName))
FileUtils.deleteDirectory(getWikiRepositoryDir(repository.userName, repository.repositoryName))
FileUtils.deleteDirectory(getTemporaryDir(repository.userName, repository.repositoryName)) FileUtil.deleteRecursively(getWikiRepositoryDir(repository.userName, repository.repositoryName))
FileUtils.deleteDirectory(getRepositoryFilesDir(repository.userName, repository.repositoryName)) FileUtil.deleteRecursively(getTemporaryDir(repository.userName, repository.repositoryName))
FileUtil.deleteRecursively(getRepositoryFilesDir(repository.userName, repository.repositoryName))
// Call hooks // Call hooks
PluginRegistry().getRepositoryHooks.foreach(_.deleted(repository.userName, repository.repositoryName)) PluginRegistry().getRepositoryHooks.foreach(_.deleted(repository.userName, repository.repositoryName))
@@ -463,7 +461,7 @@ trait RepositoryService {
.filter { case (t1, t2) => t2.removed === false.bind } .filter { case (t1, t2) => t2.removed === false.bind }
.map { case (t1, t2) => t1 } .map { case (t1, t2) => t1 }
// for Normal Users // for Normal Users
case Some(x) if (!x.isAdmin || limit) => case Some(x) =>
Repositories Repositories
.join(Accounts) .join(Accounts)
.on(_.userName === _.userName) .on(_.userName === _.userName)
@@ -553,7 +551,8 @@ trait RepositoryService {
externalWikiUrl: Option[String], externalWikiUrl: Option[String],
allowFork: Boolean, allowFork: Boolean,
mergeOptions: Seq[String], mergeOptions: Seq[String],
defaultMergeOption: String defaultMergeOption: String,
safeMode: Boolean
)(implicit s: Session): Unit = { )(implicit s: Session): Unit = {
Repositories Repositories
@@ -569,6 +568,7 @@ trait RepositoryService {
r.allowFork, r.allowFork,
r.mergeOptions, r.mergeOptions,
r.defaultMergeOption, r.defaultMergeOption,
r.safeMode,
r.updatedDate r.updatedDate
) )
} }
@@ -582,6 +582,7 @@ trait RepositoryService {
allowFork, allowFork,
mergeOptions.mkString(","), mergeOptions.mkString(","),
defaultMergeOption, defaultMergeOption,
safeMode,
currentDate currentDate
) )
} }
@@ -765,7 +766,8 @@ trait RepositoryService {
JGitUtil.getContentFromId(git, file.id, true).collect { JGitUtil.getContentFromId(git, file.id, true).collect {
case bytes if FileUtil.isText(bytes) => StringUtil.convertFromByteArray(bytes) case bytes if FileUtil.isText(bytes) => StringUtil.convertFromByteArray(bytes)
} }
} getOrElse None }
.flatten
} getOrElse "" } getOrElse ""
} }
} }
@@ -819,11 +821,13 @@ object RepositoryService {
def sshUrl(implicit context: Context): Option[String] = RepositoryService.sshUrl(owner, name) def sshUrl(implicit context: Context): Option[String] = RepositoryService.sshUrl(owner, name)
def splitPath(path: String): (String, String) = { def splitPath(path: String): (String, String) = {
val id = branchList.collectFirst { val names = (branchList ++ tags.map(_.name)).sortBy(_.length).reverse
case branch if (path == branch || path.startsWith(branch + "/")) => branch
} orElse tags.collectFirst { val id = names.collectFirst {
case tag if (path == tag.name || path.startsWith(tag.name + "/")) => tag.name case name if (path == name || path.startsWith(name + "/")) => name
} getOrElse path.split("/")(0) } getOrElse {
path.split("/")(0)
}
(id, path.substring(id.length).stripPrefix("/")) (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.service.SystemSettingsService._
import gitbucket.core.util.ConfigUtil._ import gitbucket.core.util.ConfigUtil._
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory._
import gitbucket.core.util.SyntaxSugars._
import scala.util.Using import scala.util.Using
trait SystemSettingsService { trait SystemSettingsService {
@@ -15,175 +14,173 @@ trait SystemSettingsService {
def baseUrl(implicit request: HttpServletRequest): String = loadSystemSettings().baseUrl(request) def baseUrl(implicit request: HttpServletRequest): String = loadSystemSettings().baseUrl(request)
def saveSystemSettings(settings: SystemSettings): Unit = { def saveSystemSettings(settings: SystemSettings): Unit = {
defining(new java.util.Properties()) { props => val props = new java.util.Properties()
settings.baseUrl.foreach(x => props.setProperty(BaseURL, x.replaceFirst("/\\Z", ""))) settings.baseUrl.foreach(x => props.setProperty(BaseURL, x.replaceFirst("/\\Z", "")))
settings.information.foreach(x => props.setProperty(Information, x)) settings.information.foreach(x => props.setProperty(Information, x))
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString) props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
props.setProperty(AllowAnonymousAccess, settings.allowAnonymousAccess.toString) props.setProperty(AllowAnonymousAccess, settings.allowAnonymousAccess.toString)
props.setProperty(IsCreateRepoOptionPublic, settings.isCreateRepoOptionPublic.toString) props.setProperty(IsCreateRepoOptionPublic, settings.isCreateRepoOptionPublic.toString)
props.setProperty(RepositoryOperationCreate, settings.repositoryOperation.create.toString) props.setProperty(RepositoryOperationCreate, settings.repositoryOperation.create.toString)
props.setProperty(RepositoryOperationDelete, settings.repositoryOperation.delete.toString) props.setProperty(RepositoryOperationDelete, settings.repositoryOperation.delete.toString)
props.setProperty(RepositoryOperationRename, settings.repositoryOperation.rename.toString) props.setProperty(RepositoryOperationRename, settings.repositoryOperation.rename.toString)
props.setProperty(RepositoryOperationTransfer, settings.repositoryOperation.transfer.toString) props.setProperty(RepositoryOperationTransfer, settings.repositoryOperation.transfer.toString)
props.setProperty(RepositoryOperationFork, settings.repositoryOperation.fork.toString) props.setProperty(RepositoryOperationFork, settings.repositoryOperation.fork.toString)
props.setProperty(Gravatar, settings.gravatar.toString) props.setProperty(Gravatar, settings.gravatar.toString)
props.setProperty(Notification, settings.notification.toString) props.setProperty(Notification, settings.notification.toString)
props.setProperty(LimitVisibleRepositories, settings.limitVisibleRepositories.toString) props.setProperty(LimitVisibleRepositories, settings.limitVisibleRepositories.toString)
props.setProperty(SshEnabled, settings.ssh.enabled.toString) props.setProperty(SshEnabled, settings.ssh.enabled.toString)
settings.ssh.sshHost.foreach(x => props.setProperty(SshHost, x.trim)) settings.ssh.sshHost.foreach(x => props.setProperty(SshHost, x.trim))
settings.ssh.sshPort.foreach(x => props.setProperty(SshPort, x.toString)) settings.ssh.sshPort.foreach(x => props.setProperty(SshPort, x.toString))
props.setProperty(UseSMTP, settings.useSMTP.toString) props.setProperty(UseSMTP, settings.useSMTP.toString)
if (settings.useSMTP) { if (settings.useSMTP) {
settings.smtp.foreach { smtp => settings.smtp.foreach { smtp =>
props.setProperty(SmtpHost, smtp.host) props.setProperty(SmtpHost, smtp.host)
smtp.port.foreach(x => props.setProperty(SmtpPort, x.toString)) smtp.port.foreach(x => props.setProperty(SmtpPort, x.toString))
smtp.user.foreach(props.setProperty(SmtpUser, _)) smtp.user.foreach(props.setProperty(SmtpUser, _))
smtp.password.foreach(props.setProperty(SmtpPassword, _)) smtp.password.foreach(props.setProperty(SmtpPassword, _))
smtp.ssl.foreach(x => props.setProperty(SmtpSsl, x.toString)) smtp.ssl.foreach(x => props.setProperty(SmtpSsl, x.toString))
smtp.starttls.foreach(x => props.setProperty(SmtpStarttls, x.toString)) smtp.starttls.foreach(x => props.setProperty(SmtpStarttls, x.toString))
smtp.fromAddress.foreach(props.setProperty(SmtpFromAddress, _)) smtp.fromAddress.foreach(props.setProperty(SmtpFromAddress, _))
smtp.fromName.foreach(props.setProperty(SmtpFromName, _)) 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) { props.setProperty(SkinName, settings.skinName)
settings.ldap.foreach { ldap => settings.userDefinedCss.foreach(x => props.setProperty(UserDefinedCss, x))
props.setProperty(LdapHost, ldap.host) props.setProperty(ShowMailAddress, settings.showMailAddress.toString)
ldap.port.foreach(x => props.setProperty(LdapPort, x.toString)) props.setProperty(WebHookBlockPrivateAddress, settings.webHook.blockPrivateAddress.toString)
ldap.bindDN.foreach(x => props.setProperty(LdapBindDN, x)) props.setProperty(WebHookWhitelist, settings.webHook.whitelist.mkString("\n"))
ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x)) props.setProperty(UploadMaxFileSize, settings.upload.maxFileSize.toString)
props.setProperty(LdapBaseDN, ldap.baseDN) props.setProperty(UploadTimeout, settings.upload.timeout.toString)
props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute) props.setProperty(UploadLargeMaxFileSize, settings.upload.largeMaxFileSize.toString)
ldap.additionalFilterCondition.foreach(x => props.setProperty(LdapAdditionalFilterCondition, x)) props.setProperty(UploadLargeTimeout, settings.upload.largeTimeout.toString)
ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x)) props.setProperty(RepositoryViewerMaxFiles, settings.repositoryViewer.maxFiles.toString)
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)
Using.resource(new java.io.FileOutputStream(GitBucketConf)) { out => Using.resource(new java.io.FileOutputStream(GitBucketConf)) { out =>
props.store(out, null) props.store(out, null)
}
} }
} }
def loadSystemSettings(): SystemSettings = { def loadSystemSettings(): SystemSettings = {
defining(new java.util.Properties()) { props => val props = new java.util.Properties()
if (GitBucketConf.exists) { if (GitBucketConf.exists) {
Using.resource(new java.io.FileInputStream(GitBucketConf)) { in => Using.resource(new java.io.FileInputStream(GitBucketConf)) { in =>
props.load(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 = { private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = {
getConfigValue(key).getOrElse { getConfigValue(key).getOrElse {
defining(props.getProperty(key)) { value => val value = props.getProperty(key)
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
default default
} else { } else {
convertType(value).asInstanceOf[A] 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] = { private def getOptionValue[A: ClassTag](props: java.util.Properties, key: String, default: Option[A]): Option[A] = {
getConfigValue(key).orElse { getConfigValue(key).orElse {
defining(props.getProperty(key)) { value => val value = props.getProperty(key)
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
default default
} else { } else {
Some(convertType(value)).asInstanceOf[Option[A]] 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
import fr.brouillard.oss.security.xhub.XHub.{XHubConverter, XHubDigest} import fr.brouillard.oss.security.xhub.XHub.{XHubConverter, XHubDigest}
import gitbucket.core.api._ import gitbucket.core.api._
import gitbucket.core.controller.Context
import gitbucket.core.model.{ import gitbucket.core.model.{
Account, Account,
AccountWebHook, AccountWebHook,
@@ -21,7 +20,6 @@ import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile.blockingApi._ import gitbucket.core.model.Profile.profile.blockingApi._
import org.apache.http.client.utils.URLEncodedUtils import org.apache.http.client.utils.URLEncodedUtils
import gitbucket.core.util.JGitUtil.CommitInfo import gitbucket.core.util.JGitUtil.CommitInfo
import gitbucket.core.util.Implicits._
import gitbucket.core.util.{HttpClientUtil, RepositoryName, StringUtil} import gitbucket.core.util.{HttpClientUtil, RepositoryName, StringUtil}
import gitbucket.core.service.RepositoryService.RepositoryInfo import gitbucket.core.service.RepositoryService.RepositoryInfo
import org.apache.http.NameValuePair import org.apache.http.NameValuePair
@@ -37,6 +35,7 @@ import org.apache.http.HttpRequest
import org.apache.http.HttpResponse import org.apache.http.HttpResponse
import gitbucket.core.model.WebHookContentType import gitbucket.core.model.WebHookContentType
import gitbucket.core.service.SystemSettingsService.SystemSettings import gitbucket.core.service.SystemSettingsService.SystemSettings
import gitbucket.core.view.helpers.getApiMilestone
import org.apache.http.client.entity.EntityBuilder import org.apache.http.client.entity.EntityBuilder
import org.apache.http.entity.ContentType import org.apache.http.entity.ContentType
@@ -128,7 +127,7 @@ trait WebHookService {
ctype = ctype, ctype = ctype,
token = token token = token
) )
events.map { event: WebHook.Event => events.map { (event: WebHook.Event) =>
RepositoryWebHookEvents insert RepositoryWebHookEvent(owner, repository, url, event) RepositoryWebHookEvents insert RepositoryWebHookEvent(owner, repository, url, event)
} }
} }
@@ -146,7 +145,7 @@ trait WebHookService {
.map(w => (w.ctype, w.token)) .map(w => (w.ctype, w.token))
.update((ctype, token)) .update((ctype, token))
RepositoryWebHookEvents.filter(_.byRepositoryWebHook(owner, repository, url)).delete RepositoryWebHookEvents.filter(_.byRepositoryWebHook(owner, repository, url)).delete
events.map { event: WebHook.Event => events.map { (event: WebHook.Event) =>
RepositoryWebHookEvents insert RepositoryWebHookEvent(owner, repository, url, event) RepositoryWebHookEvents insert RepositoryWebHookEvent(owner, repository, url, event)
} }
} }
@@ -165,14 +164,16 @@ trait WebHookService {
.map(w => (w.url, w.ctype, w.token)) .map(w => (w.url, w.ctype, w.token))
.update((url, ctype, token)) .update((url, ctype, token))
RepositoryWebHookEvents.filter(_.byRepositoryWebHook(owner, repository, url)).delete RepositoryWebHookEvents.filter(_.byRepositoryWebHook(owner, repository, url)).delete
events.map { event: WebHook.Event => events.map { (event: WebHook.Event) =>
RepositoryWebHookEvents insert RepositoryWebHookEvent(owner, repository, url, 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 = def deleteWebHook(owner: String, repository: String, url: String)(implicit s: Session): Unit =
RepositoryWebHooks.filter(_.byRepositoryUrl(owner, repository, url)).delete 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 = def deleteWebHookById(id: Int)(implicit s: Session): Unit =
RepositoryWebHooks.filter(_.byId(id)).delete RepositoryWebHooks.filter(_.byId(id)).delete
@@ -228,7 +229,7 @@ trait WebHookService {
token: Option[String] token: Option[String]
)(implicit s: Session): Unit = { )(implicit s: Session): Unit = {
AccountWebHooks insert AccountWebHook(owner, url, ctype, token) AccountWebHooks insert AccountWebHook(owner, url, ctype, token)
events.map { event: WebHook.Event => events.map { (event: WebHook.Event) =>
AccountWebHookEvents insert AccountWebHookEvent(owner, url, event) AccountWebHookEvents insert AccountWebHookEvent(owner, url, event)
} }
} }
@@ -242,7 +243,7 @@ trait WebHookService {
)(implicit s: Session): Unit = { )(implicit s: Session): Unit = {
AccountWebHooks.filter(_.byPrimaryKey(owner, url)).map(w => (w.ctype, w.token)).update((ctype, token)) AccountWebHooks.filter(_.byPrimaryKey(owner, url)).map(w => (w.ctype, w.token)).update((ctype, token))
AccountWebHookEvents.filter(_.byAccountWebHook(owner, url)).delete AccountWebHookEvents.filter(_.byAccountWebHook(owner, url)).delete
events.map { event: WebHook.Event => events.map { (event: WebHook.Event) =>
AccountWebHookEvents insert AccountWebHookEvent(owner, url, event) AccountWebHookEvents insert AccountWebHookEvent(owner, url, event)
} }
} }
@@ -255,11 +256,11 @@ trait WebHookService {
)(implicit s: Session, c: JsonFormat.Context): Unit = { )(implicit s: Session, c: JsonFormat.Context): Unit = {
val webHooks = getWebHooksByEvent(owner, repository, event) val webHooks = getWebHooksByEvent(owner, repository, event)
if (webHooks.nonEmpty) { if (webHooks.nonEmpty) {
makePayload.map(callWebHook(event, webHooks, _, settings)) makePayload.foreach(callWebHook(event, webHooks, _, settings))
} }
val accountWebHooks = getAccountWebHooksByEvent(owner, event) val accountWebHooks = getAccountWebHooksByEvent(owner, event)
if (accountWebHooks.nonEmpty) { 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) val json = JsonFormat(payload)
webHooks.map { webHook => webHooks.map { webHook =>
val reqPromise = Promise[HttpRequest] val reqPromise = Promise[HttpRequest]()
val f = Future { val f = Future {
val itcp = new org.apache.http.HttpRequestInterceptor { val itcp = new org.apache.http.HttpRequestInterceptor {
def process(res: HttpRequest, ctx: HttpContext): Unit = { def process(res: HttpRequest, ctx: HttpContext): Unit = {
@@ -394,7 +395,8 @@ trait WebHookPullRequestService extends WebHookService {
ApiUser(issueUser), ApiUser(issueUser),
issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)), issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)),
getIssueLabels(repository.owner, repository.name, issue.issueId) getIssueLabels(repository.owner, repository.name, issue.issueId)
.map(ApiLabel(_, RepositoryName(repository))) .map(ApiLabel(_, RepositoryName(repository))),
getApiMilestone(repository, issue.milestoneId getOrElse (0))
), ),
sender = ApiUser(sender) sender = ApiUser(sender)
) )
@@ -576,6 +578,7 @@ trait WebHookIssueCommentService extends WebHookPullRequestService {
commenter <- users.get(issueComment.commentedUserName) commenter <- users.get(issueComment.commentedUserName)
assignedUser = issue.assignedUserName.flatMap(users.get(_)) assignedUser = issue.assignedUserName.flatMap(users.get(_))
labels = getIssueLabels(repository.owner, repository.name, issue.issueId) labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
milestone = getApiMilestone(repository, issue.milestoneId getOrElse (0))
} yield { } yield {
WebHookIssueCommentPayload( WebHookIssueCommentPayload(
issue = issue, issue = issue,
@@ -586,7 +589,8 @@ trait WebHookIssueCommentService extends WebHookPullRequestService {
repositoryUser = repoOwner, repositoryUser = repoOwner,
assignedUser = assignedUser, assignedUser = assignedUser,
sender = sender, sender = sender,
labels = labels labels = labels,
milestone = milestone
) )
} }
} }
@@ -760,7 +764,8 @@ object WebHookService {
repositoryUser: Account, repositoryUser: Account,
assignedUser: Option[Account], assignedUser: Option[Account],
sender: Account, sender: Account,
labels: List[Label] labels: List[Label],
milestone: Option[ApiMilestone]
): WebHookIssueCommentPayload = ): WebHookIssueCommentPayload =
WebHookIssueCommentPayload( WebHookIssueCommentPayload(
action = "created", action = "created",
@@ -770,7 +775,8 @@ object WebHookService {
RepositoryName(repository), RepositoryName(repository),
ApiUser(issueUser), ApiUser(issueUser),
assignedUser.map(ApiUser(_)), assignedUser.map(ApiUser(_)),
labels.map(ApiLabel(_, RepositoryName(repository))) labels.map(ApiLabel(_, RepositoryName(repository))),
milestone
), ),
comment = comment =
ApiComment(comment, RepositoryName(repository), issue.issueId, ApiUser(commentUser), issue.isPullRequest), 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.model.Account
import gitbucket.core.service.RepositoryService.RepositoryInfo import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.util._ import gitbucket.core.util._
import gitbucket.core.util.SyntaxSugars._
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.treewalk.CanonicalTreeParser import org.eclipse.jgit.treewalk.CanonicalTreeParser
import org.eclipse.jgit.lib._ import org.eclipse.jgit.lib._
@@ -54,20 +53,19 @@ trait WikiService {
def createWikiRepository(loginAccount: Account, owner: String, repository: String): Unit = def createWikiRepository(loginAccount: Account, owner: String, repository: String): Unit =
LockUtil.lock(s"${owner}/${repository}/wiki") { LockUtil.lock(s"${owner}/${repository}/wiki") {
defining(Directory.getWikiRepositoryDir(owner, repository)) { dir => val dir = Directory.getWikiRepositoryDir(owner, repository)
if (!dir.exists) { if (!dir.exists) {
JGitUtil.initRepository(dir) JGitUtil.initRepository(dir)
saveWikiPage( saveWikiPage(
owner, owner,
repository, repository,
"Home", "Home",
"Home", "Home",
s"Welcome to the ${repository} wiki!!", s"Welcome to the ${repository} wiki!!",
loginAccount, loginAccount,
"Initial Commit", "Initial Commit",
None None
) )
}
} }
} }
@@ -77,13 +75,15 @@ trait WikiService {
def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = { def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = {
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git => Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
if (!JGitUtil.isEmpty(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( WikiPageInfo(
file.name, fileName,
StringUtil.convertFromByteArray(git.getRepository.open(file.id).getBytes), StringUtil.convertFromByteArray(content.getOrElse(Array.empty)),
file.author, latestCommit.getAuthorIdent.getName,
file.time, latestCommit.getAuthorIdent.getWhen,
file.commitId latestCommit.getName
) )
} }
} else None } else None
@@ -147,7 +147,7 @@ trait WikiService {
if (!p.getErrors.isEmpty) { if (!p.getErrors.isEmpty) {
throw new PatchFormatException(p.getErrors()) throw new PatchFormatException(p.getErrors())
} }
val revertInfo = (p.getFiles.asScala.map { fh => val revertInfo = p.getFiles.asScala.flatMap { fh =>
fh.getChangeType match { fh.getChangeType match {
case DiffEntry.ChangeType.MODIFY => { case DiffEntry.ChangeType.MODIFY => {
val source = val source =
@@ -176,7 +176,7 @@ trait WikiService {
} }
case _ => Nil case _ => Nil
} }
}).flatten }
if (revertInfo.nonEmpty) { if (revertInfo.nonEmpty) {
val builder = DirCache.newInCore.builder() val builder = DirCache.newInCore.builder()
@@ -257,8 +257,7 @@ trait WikiService {
created = false created = false
updated = JGitUtil updated = JGitUtil
.getContentFromId(git, tree.getEntryObjectId, true) .getContentFromId(git, tree.getEntryObjectId, true)
.map(new String(_, "UTF-8") != content) .exists(new String(_, "UTF-8") != content)
.getOrElse(false)
} }
} }
} }

View File

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

View File

@@ -139,7 +139,7 @@ class GitAuthenticationFilter extends Filter with RepositoryService with Account
case _ => 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) 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 gitbucket.core.util.{FileUtil, StringUtil}
import org.apache.commons.io.{FileUtils, IOUtils} import org.apache.commons.io.{FileUtils, IOUtils}
import org.json4s.Formats
import org.json4s.jackson.Serialization._ import org.json4s.jackson.Serialization._
import org.apache.http.HttpStatus import org.apache.http.HttpStatus
@@ -17,7 +18,7 @@ import scala.util.Using
*/ */
class GitLfsTransferServlet extends HttpServlet { 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 LongObjectIdLength = 32
private val LongObjectIdStringLength = LongObjectIdLength * 2 private val LongObjectIdStringLength = LongObjectIdLength * 2

View File

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

View File

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

View File

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

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