Compare commits

...

101 Commits

Author SHA1 Message Date
Naoki Takezoe
1a4961c3e1 Merge pull request #2489 from gitbucket/release-4.34.0
GitBucket 4.34.0 release
2020-07-26 02:38:51 +09:00
Naoki Takezoe
561220237f Update README and CHANGELOG 2020-07-26 02:28:47 +09:00
Naoki Takezoe
f45b85aa71 Bump gist-plugin (refs #2443) and notifications-plugin (refs #2433) 2020-07-25 21:04:45 +09:00
Naoki Takezoe
83b3a7983e Update version to 4.34.0 2020-07-25 17:52:33 +09:00
Naoki Takezoe
63d4c5054e Deny empty password (#2488) 2020-07-25 17:49:58 +09:00
Naoki Takezoe
c8f6017be9 Migrate activity log to JSON-based file from database (#2468)
This also includes:
- Record rename and transfer repository in activity log
- Check existence of repsoitory when rendering activity logs
- Drop Akka dependency
2020-07-18 23:28:40 +09:00
Naoki Takezoe
f9fcb54861 Fix coursier cache path (#2481) 2020-07-18 17:17:41 +09:00
Naoki Takezoe
3534b7172d Modify CI workflow name 2020-07-05 14:17:23 +09:00
Naoki Takezoe
42a7f974e9 Update README.md 2020-07-05 13:41:51 +09:00
Naoki Takezoe
b8e02d995a Modify artifact name 2020-07-05 13:34:44 +09:00
Naoki Takezoe
e0d038aa92 Tweak artifacts uploading (#2477) 2020-07-05 13:23:24 +09:00
Naoki Takezoe
3d01df2bdc Fix GitHuib Actions configuration 2020-07-05 12:12:17 +09:00
Naoki Takezoe
6f08f1fd23 Update README.md 2020-07-05 12:04:02 +09:00
Naoki Takezoe
3dd366b394 Replace CI badge by GitHib Actions' one 2020-07-05 12:00:59 +09:00
Naoki Takezoe
8cf4528959 Setup GitHub Actions workflow (#2476) 2020-07-05 11:30:32 +09:00
Jookia
5d77bc5d98 executable: Allow saving sessions to disk (#2474)
This commit allows saving sessions to disk in ~/.gitbucket/sessions
or wherever the gitbucket home is so that logins and other session
data persist between restarts of the program.

Usage: java -jar gitbucket.war --save_sessions
2020-07-05 11:19:21 +09:00
YHojo
572c9ef558 Add get-commits-list-api (#2470) 2020-07-04 22:08:48 +09:00
Jookia
7c736c526e theme: Add title to sidebar toggle button (#2473)
This makes it so users can mouse over the button and see what it does.
2020-07-04 22:06:54 +09:00
Naoki Takezoe
696d354f3c Repository operation restrictions (#2469) 2020-07-04 22:03:10 +09:00
Hirohisa Yamaguchi
501cbf54ab fix TLS/SSL LDAP under IBM JVM (#2472)
Co-authored-by: Hirohisa Yamaguchi <umq@ueo.co.jp>
2020-07-04 15:07:34 +09:00
kenji yoshida
c15d69d566 Update sbt-assembly to 0.15.0 2020-06-28 14:55:04 +09:00
Naoki Takezoe
83eb933230 Improve file list performance (#2466)
- Simplify an internal method to retrieving commit info
- Introduce in-memory cache for large repositories
- Introduce the upper limit for the number of files to retrieving commit info
2020-06-26 15:59:20 +09:00
kenji yoshida
43bec9b0df jgit 5.8.0.202006091008-r 2020-06-21 09:27:16 +09:00
kenji yoshida
279305c202 scalafmt 2.6.1 2020-06-20 22:55:23 +09:00
Naoki Takezoe
ecbb86c006 Support both /ref and /refs in Get Reference API (#2465) 2020-06-20 16:50:51 +09:00
kenji yoshida
921fb17ef0 scalafmt 2.6.0 2020-06-19 11:41:25 +09:00
kenji yoshida
0362de7d35 json4s 3.6.9 2020-06-16 06:34:10 +09:00
Adrian
cf0d8ea2d0 Apply the trustStore setting when ssl is true - Fixes #1782 (#2449)
Previously the truststore configuration option was applied only when StartTLS is called on a plain LDAP connection, however, the trust store is equally applicable to a full TLS connection (i.e. when ldap.ssl is set to true).
2020-06-15 01:48:03 +09:00
Naoki Takezoe
0e9026447d Fix get a reference API path (#2459) 2020-06-15 01:46:29 +09:00
kenji yoshida
cf4d9cb03c jetty 9.4.30.v20200611 2020-06-13 14:37:16 +09:00
Naoki Takezoe
2a1edeaca3 Use-defined CSS (#2458) 2020-06-08 15:50:37 +09:00
kenji yoshida
eebabf9b08 sbt 1.3.12 2020-05-31 14:08:24 +09:00
kenji yoshida
d882fcad12 Update commons-io to 2.7 2020-05-30 09:31:55 +09:00
kenji yoshida
f976290282 update testcontainers 2020-05-30 09:26:13 +09:00
kenji yoshida
f3f9d5dae2 sbt 1.3.11 2020-05-30 09:25:30 +09:00
kenji yoshida
71dffd1089 Update sbt-scalatra to 1.0.4 2020-05-22 08:54:32 +09:00
kenji yoshida
f411e98c9a Update scalafmt to 2.5.2 2020-05-22 08:51:01 +09:00
kenji yoshida
1baa489bc7 Update json4s to 3.6.8 2020-05-22 08:48:46 +09:00
kenji yoshida
f996b0fc4a update HikariCP to 3.4.5 2020-05-22 08:46:54 +09:00
kenji yoshida
eb6ba1c800 Update jetty to 9.4.29.v20200521 2020-05-22 08:45:31 +09:00
SIkebe
8fac1baa3c Fix file upload on IE11 (#2452)
Bump dropzone from 5.2.0 to 5.5.1
v5.6.0 or later version of dropzone has a compatibility problem with IE.
It breaks file upload function in issue/PR pages.
So you have to stick to v5.5.1 for now or use polyfill.
https://gitlab.com/meno/dropzone/-/issues/225
2020-05-16 23:33:49 +09:00
kenji yoshida
2d327543b9 update testcontainers 2020-05-16 08:45:40 +09:00
SIkebe
8a080efe9d Bump jQuery to 3.5.1 (#2447) 2020-05-07 09:03:05 +09:00
Naoki Takezoe
aaaf61e29e Fix browser_download_url of releases APIs (#2448) 2020-05-07 09:02:24 +09:00
kenji yoshida
1294323df5 Update testcontainers-scala to 0.37.0 2020-05-05 09:17:21 +09:00
kenji yoshida
9ef4e75746 Update scalafmt to 2.5.0 2020-05-01 22:02:37 +09:00
Pedro Rijo
ded4ab702d Implement release pagination (#2444) 2020-04-26 20:10:41 +09:00
kenji yoshida
f893d045c7 HikariCP 3.4.3 2020-04-26 13:57:12 +09:00
kenji yoshida
ef2e3adcfb Update testcontainers 2020-04-24 22:44:56 +09:00
kenji yoshida
a1908c5398 Update tika-core to 1.24.1 2020-04-22 07:44:20 +09:00
kenji yoshida
ec4f0d6531 sbt 1.3.10 2020-04-15 22:45:25 +09:00
xuwei-k
9ef366237c Update testcontainers 2020-04-14 11:45:57 +09:00
xuwei-k
9197ad2600 Update dependencies 2020-04-13 01:17:22 +09:00
Naoki Takezoe
2cb7ecd851 Drop password validation (#2439) 2020-04-11 10:58:28 +09:00
Nicolas.C
7d1ad4ce66 Delete AddPrimaryKey tag which is not required with not composed inde… (#2435)
* Delete AddPrimaryKey tag which is not required with not composed index (#2361)
* Add mariadb driver for liquibase

Co-authored-by: nico57c <>
2020-04-11 03:20:19 +09:00
SIkebe
c274acc8f4 Restrict information visibility if allowAnonymousAccess is false (#2438) 2020-04-11 02:41:05 +09:00
kenji yoshida
7a0d48dd7a sbt 1.3.9 2020-04-02 11:17:48 +09:00
kenji yoshida
9c6f9048e1 Update testcontainers-scala to 0.36.1 2020-03-16 13:07:08 +09:00
kenji yoshida
4ff4acfd7e Update mockito to 3.3.3 2020-03-14 15:52:25 +09:00
kenji yoshida
d8e9f07721 Update jgit to 5.7.0.202003110725-r 2020-03-14 15:52:02 +09:00
kenji yoshida
662c5638dd Update jgit to 5.7.0.202003090808-r 2020-03-11 07:57:39 +09:00
kenji yoshida
02b830d034 Update testcontainers-scala to 0.36.0 2020-03-09 10:25:44 +09:00
kenji yoshida
3734529e5c Update httpclient to 4.5.12 2020-03-08 09:08:37 +09:00
kenji yoshida
715ec24389 Update testcontainers 2020-03-06 07:57:13 +09:00
kenji yoshida
5615b23548 Update jetty to 9.4.27.v20200227 2020-02-29 00:42:15 +09:00
kenji yoshida
ca2eeb48cf Update mockito-core to 3.3.1 2020-02-27 11:10:59 +09:00
kenji yoshida
b063c0a80c Update thumbnailator to 0.4.11 2020-02-25 09:53:08 +09:00
kenji yoshida
ee8b5692bd Update scalafmt to 2.4.2 2020-02-23 21:53:57 +09:00
kenji yoshida
7a749dca67 Update thumbnailator to 0.4.10 2020-02-23 21:53:21 +09:00
Kasan
0378f26ee6 Ignore Limited settings, when searching repositories. (#2427) 2020-02-22 13:25:23 +09:00
kenji yoshida
4d281273c1 Update mockito-core to 3.3.0 2020-02-22 07:47:03 +09:00
kenji yoshida
e23e11e1a8 Update jgit to 5.6.1.202002131546-r 2020-02-18 07:27:26 +09:00
SIkebe
772835dd22 Fix line-through rendering in markdown (#2430) 2020-02-16 23:30:00 +09:00
kenji yoshida
bbf2e57548 Update scalafmt to 2.4.1 2020-02-16 12:34:54 +09:00
kenji yoshida
aecda130b6 Update thumbnailator to 0.4.9 2020-02-12 11:02:16 +09:00
kenji yoshida
1d48030e7c Update testcontainers-scala to 0.35.2 2020-02-10 11:54:49 +09:00
kenji yoshida
929ba2fa19 Update commons-compress to 1.20 2020-02-09 05:06:27 +09:00
kenji yoshida
6fefa947ca Update testcontainers-scala to 0.35.1 2020-02-07 20:34:19 +09:00
kenji yoshida
072cd2e846 Update sbt to 1.3.8 2020-02-04 11:50:27 +09:00
Naoki Takezoe
5d20cd0365 Remove useless configuration template 2020-01-29 23:12:04 +09:00
kenji yoshida
03ec055f66 Update mariadb-java-client to 2.5.4 2020-01-28 00:24:34 +09:00
kenji yoshida
4b6a5e5d49 Update dependencies 2020-01-24 13:12:13 +09:00
kenji yoshida
6d21e38159 Update httpclient to 4.5.11 2020-01-19 08:39:36 +09:00
kenji yoshida
a47c8249bf Update jetty to 9.4.26.v20200117 2020-01-18 16:43:20 +09:00
Kasan
59d7a672b3 Limit the visible repositories in the sidebar. (#2421) 2020-01-18 10:34:43 +09:00
SIkebe
564e95d36e Correct activity message of reopening pull request (#2420)
* Correct activity message of reopening pull request
* Fix docs
2020-01-18 10:15:54 +09:00
kenji yoshida
9aee99be74 sbt 1.3.7 2020-01-15 22:13:29 +09:00
kenji yoshida
aecd8b503d Update dependencies 2020-01-15 22:08:13 +09:00
kenji yoshida
fa65eeea35 Update scalafmt to 2.3.2 2020-01-15 22:06:48 +09:00
Naoki Takezoe
fe43584c3d Ignore metals.sbt 2020-01-15 01:42:05 +09:00
Naoki Takezoe
c255b13dfc Set scalafmt version to 2.0.1 2020-01-15 01:42:05 +09:00
Naoki Takezoe
917b204e5b Restructure settings UI (#2413) 2020-01-13 02:28:30 +09:00
SIkebe
6225fd79fc Merge pull request #2417 from gitbucket/fix-compare-view
Fix diff in compare view
2020-01-12 12:44:21 +09:00
SIkebe
cbec567ef4 Make Authorization header auth-scheme case-insencitive (#2416) 2020-01-12 10:26:04 +09:00
Naoki Takezoe
84b62242d3 Fix diff in compare view 2020-01-12 03:10:04 +09:00
kenji yoshida
f17f74c30b sbt 1.3.6 2020-01-10 12:16:01 +09:00
Naoki Takezoe
63b04d5a27 Fix docs 2020-01-03 13:17:34 +09:00
Naoki Takezoe
f02073a24c Move description of CLI options to Wiki 2020-01-03 13:04:23 +09:00
Naoki Takezoe
cb87d126de (refs #2391) Fix online editor preview 2020-01-01 23:11:22 +09:00
SIkebe
941f8002e5 Implement draft pull request related APIs (#2388) 2020-01-01 01:07:19 +09:00
Naoki Takezoe
90f4f5cd89 Fix typos in docs 2019-12-31 21:19:21 +09:00
88 changed files with 2166 additions and 1425 deletions

39
.github/workflows/build.yml vendored Normal file
View File

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

3
.gitignore vendored
View File

@@ -27,4 +27,5 @@ project/plugins/project/
# Metals specific
.metals
.bloop
.bloop
project/metals.sbt

View File

@@ -1,3 +1,4 @@
version = "1.5.1"
project.git = true
maxColumn = 120

View File

@@ -1,17 +0,0 @@
language: scala
sudo: true
jdk:
- openjdk8
- openjdk11
script:
- sbt scalafmtSbtCheck scalafmtCheck test:scalafmtCheck test
before_script:
- sudo /etc/init.d/mysql stop
- sudo /etc/init.d/postgresql stop
- sudo chmod +x /usr/local/bin/sbt
cache:
directories:
- $HOME/.ivy2/cache
- $HOME/.sbt/boot
- $HOME/.sbt/launchers
- $HOME/.coursier

View File

@@ -1,12 +1,28 @@
# Changelog
All changes to the project will be documented in this file.
### 4.34.0 - 26 Jul 2020
- Enhancement admin settings UI
- File upload settings
- Restrict repository operations
- User-defined CSS
- Limit the repository list in the sidebar
- Improve MariaDB support
- Improve activity logging
- CLI option to persist session on disk in the standalone mode
- Web API updates
- Add [list commits API](https://developer.github.com/v3/repos/commits/#list-commits)
- Bundled plugins updates
- [gitbucket-gist-plugin](https://github.com/gitbucket/gitbucket-gist-plugin) 4.18.0 -> 4.19.0
- [gitbucket-notifications-plugin](https://github.com/gitbucket/gitbucket-notifications-plugin) 1.8.0 -> 1.9.0
### 4.33.0 - 31 Dec 2019
- All CLI options are configurable by environmental variables
- All CLI options are configurable by environment variables
- Folding pull request files
- WebHook security options
- Add asignee and asignees properties to some Web APIs' response
- Add assignee and assignees properties to some Web APIs' response
### 4.32.0 - 7 Aug 2019

View File

@@ -1,4 +1,4 @@
GitBucket [![Gitter chat](https://badges.gitter.im/gitbucket/gitbucket.svg)](https://gitter.im/gitbucket/gitbucket) [![Build Status](https://travis-ci.org/gitbucket/gitbucket.svg?branch=master)](https://travis-ci.org/gitbucket/gitbucket) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.gitbucket/gitbucket_2.13/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.gitbucket/gitbucket_2.13) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/gitbucket/gitbucket/blob/master/LICENSE)
GitBucket [![Gitter chat](https://badges.gitter.im/gitbucket/gitbucket.svg)](https://gitter.im/gitbucket/gitbucket) [![build](https://github.com/gitbucket/gitbucket/workflows/build/badge.svg?branch=master)](https://github.com/gitbucket/gitbucket/actions?query=workflow%3Abuild+branch%3Amaster) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.gitbucket/gitbucket_2.13/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.gitbucket/gitbucket_2.13) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/gitbucket/gitbucket/blob/master/LICENSE)
=========
GitBucket is a Git web platform powered by Scala offering:
@@ -31,20 +31,6 @@ GitBucket requires **Java8**. You have to install it, if it is not already insta
1. Download the latest **gitbucket.war** from [the releases page](https://github.com/gitbucket/gitbucket/releases) and run it by `java -jar gitbucket.war`.
2. Go to `http://[hostname]:8080/` and log in with ID: **root** / Pass: **root**.
You can specify following options:
- `--port=[NUMBER]`
- `--prefix=[CONTEXTPATH]`
- `--host=[HOSTNAME]`
- `--gitbucket.home=[DATA_DIR]`
- `--temp_dir=[TEMP_DIR]`
- `--max_file_size=[MAX_FILE_SIZE]`
- `--upload_timeout=[MAX_UPLOAD_TIMEOUT]`
`TEMP_DIR` is used as the [temporary directory for the jetty application context](https://www.eclipse.org/jetty/documentation/9.3.x/ref-temporary-directories.html). This is the directory into which the `gitbucket.war` file is unpacked, the source files are compiled, etc. If given this parameter **must** match the path of an existing directory or the application will quit reporting an error; if not given the path used will be a `tmp` directory inside the gitbucket home.
`MAX_FILE_SIZE` is the max file size in bytes for upload files *( default is 3 MB -> 3 x 1024 x 1024 )*.
You can also deploy `gitbucket.war` to a servlet container which supports Servlet 3.0 (like Jetty, Tomcat, JBoss, etc)
For more information about installation on Mac or Windows Server (with IIS), or configuration of Apache or Nginx and also integration with other tools or services such as Jenkins or Slack, see [Wiki](https://github.com/gitbucket/gitbucket/wiki).
@@ -69,13 +55,22 @@ Support
- If you can't find same question and report, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
- The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles.
What's New in 4.32.x
What's New in 4.33.x
-------------
### 4.33.0 - 31 Dec 2019
### 4.34.0 - 26 Jul 2020
- All CLI options are configurable by environmental variables
- Folding pull request files
- WebHook security options
- Add asignee and asignees properties to some Web APIs' response
- Enhancement admin settings UI
- File upload settings
- Restrict repository operations
- User-defined CSS
- Limit the repository list in the sidebar
- Improve MariaDB support
- Improve activity logging
- CLI option to persist session on disk in the standalone mode
- Web API updates
- Add [list commits API](https://developer.github.com/v3/repos/commits/#list-commits)
- Bundled plugins updates
- [gitbucket-gist-plugin](https://github.com/gitbucket/gitbucket-gist-plugin) 4.18.0 -> 4.19.0
- [gitbucket-notifications-plugin](https://github.com/gitbucket/gitbucket-notifications-plugin) 1.8.0 -> 1.9.0
See the [change log](CHANGELOG.md) for all of the updates.

View File

@@ -3,10 +3,10 @@ import com.typesafe.sbt.pgp.PgpKeys._
val Organization = "io.github.gitbucket"
val Name = "gitbucket"
val GitBucketVersion = "4.33.0"
val GitBucketVersion = "4.34.0"
val ScalatraVersion = "2.7.0-RC1"
val JettyVersion = "9.4.25.v20191220"
val JgitVersion = "5.6.0.201912101111-r"
val JettyVersion = "9.4.30.v20200611"
val JgitVersion = "5.8.0.202006091008-r"
lazy val root = (project in file("."))
.enablePlugins(SbtTwirl, ScalatraPlugin)
@@ -36,42 +36,40 @@ libraryDependencies ++= Seq(
"org.scalatra" %% "scalatra" % ScalatraVersion,
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
"org.scalatra" %% "scalatra-forms" % ScalatraVersion,
"org.json4s" %% "json4s-jackson" % "3.6.7",
"commons-io" % "commons-io" % "2.6",
"org.json4s" %% "json4s-jackson" % "3.6.9",
"commons-io" % "commons-io" % "2.7",
"io.github.gitbucket" % "solidbase" % "1.0.3",
"io.github.gitbucket" % "markedj" % "1.0.16",
"org.apache.commons" % "commons-compress" % "1.19",
"org.apache.commons" % "commons-compress" % "1.20",
"org.apache.commons" % "commons-email" % "1.5",
"commons-net" % "commons-net" % "3.6",
"org.apache.httpcomponents" % "httpclient" % "4.5.10",
"org.apache.httpcomponents" % "httpclient" % "4.5.12",
"org.apache.sshd" % "apache-sshd" % "2.1.0" exclude ("org.slf4j", "slf4j-jdk14") exclude ("org.apache.sshd", "sshd-mina") exclude ("org.apache.sshd", "sshd-netty"),
"org.apache.tika" % "tika-core" % "1.23",
"org.apache.tika" % "tika-core" % "1.24.1",
"com.github.takezoe" %% "blocking-slick-32" % "0.0.12",
"com.novell.ldap" % "jldap" % "2009-10-07",
"com.h2database" % "h2" % "1.4.199",
"org.mariadb.jdbc" % "mariadb-java-client" % "2.5.2",
"org.mariadb.jdbc" % "mariadb-java-client" % "2.6.0",
"org.postgresql" % "postgresql" % "42.2.6",
"ch.qos.logback" % "logback-classic" % "1.2.3",
"com.zaxxer" % "HikariCP" % "3.4.1",
"com.zaxxer" % "HikariCP" % "3.4.5",
"com.typesafe" % "config" % "1.4.0",
"com.typesafe.akka" %% "akka-actor" % "2.5.27",
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.1.0",
"com.github.bkromhout" % "java-diff-utils" % "2.1.1",
"org.cache2k" % "cache2k-all" % "1.2.4.Final",
"com.enragedginger" %% "akka-quartz-scheduler" % "1.8.1-akka-2.5.x" exclude ("com.mchange", "c3p0") exclude ("com.zaxxer", "HikariCP-java6"),
"net.coobird" % "thumbnailator" % "0.4.8",
"net.coobird" % "thumbnailator" % "0.4.11",
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
"com.nimbusds" % "oauth2-oidc-sdk" % "5.64.4",
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
"junit" % "junit" % "4.12" % "test",
"junit" % "junit" % "4.13" % "test",
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test",
"org.mockito" % "mockito-core" % "3.2.4" % "test",
"com.dimafeng" %% "testcontainers-scala" % "0.34.2" % "test",
"org.testcontainers" % "mysql" % "1.12.4" % "test",
"org.testcontainers" % "postgresql" % "1.12.4" % "test",
"org.mockito" % "mockito-core" % "3.3.3" % "test",
"com.dimafeng" %% "testcontainers-scala" % "0.37.0" % "test",
"org.testcontainers" % "mysql" % "1.14.3" % "test",
"org.testcontainers" % "postgresql" % "1.14.3" % "test",
"net.i2p.crypto" % "eddsa" % "0.3.0",
"is.tagomor.woothee" % "woothee-java" % "1.10.1",
"is.tagomor.woothee" % "woothee-java" % "1.11.0",
"org.ec4j.core" % "ec4j-core" % "0.0.3"
)

View File

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

View File

@@ -2,8 +2,8 @@ scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.1")
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.0")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.10")
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.3")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0")
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.4")
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2")
addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0")
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.2")

View File

@@ -4,6 +4,10 @@ import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.server.session.DefaultSessionCache;
import org.eclipse.jetty.server.session.FileSessionDataStore;
import org.eclipse.jetty.server.session.SessionCache;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.webapp.WebAppContext;
import java.io.File;
@@ -21,6 +25,7 @@ public class JettyLauncher {
String contextPath = "/";
String tmpDirPath="";
boolean forceHttps = false;
boolean saveSessions = false;
host = getEnvironmentVariable("gitbucket.host");
port = getEnvironmentVariable("gitbucket.port");
@@ -28,6 +33,9 @@ public class JettyLauncher {
tmpDirPath = getEnvironmentVariable("gitbucket.tempDir");
for(String arg: args) {
if(arg.equals("--save_sessions")) {
saveSessions = true;
}
if(arg.startsWith("--") && arg.contains("=")) {
String[] dim = arg.split("=");
if(dim.length >= 2) {
@@ -41,12 +49,6 @@ public class JettyLauncher {
case "--prefix":
contextPath = dim[1];
break;
case "--max_file_size":
System.setProperty("gitbucket.maxFileSize", dim[1]);
break;
case "--upload_timeout":
System.setProperty("gitbucket.UploadTimeout", dim[1]);
break;
case "--gitbucket.home":
System.setProperty("gitbucket.home", dim[1]);
break;
@@ -56,9 +58,6 @@ public class JettyLauncher {
case "--plugin_dir":
System.setProperty("gitbucket.pluginDir", dim[1]);
break;
case "--validate_password":
System.setProperty("gitbucket.validate.password", dim[1]);
break;
}
}
}
@@ -96,6 +95,19 @@ public class JettyLauncher {
WebAppContext context = new WebAppContext();
if(saveSessions) {
File sessDir = new File(getGitBucketHome(), "sessions");
if(!sessDir.exists()){
sessDir.mkdirs();
}
SessionHandler sessions = context.getSessionHandler();
SessionCache cache = new DefaultSessionCache(sessions);
FileSessionDataStore fsds = new FileSessionDataStore();
fsds.setStoreDir(sessDir);
cache.setSessionDataStore(fsds);
sessions.setSessionCache(cache);
}
File tmpDir;
if(tmpDirPath == null || tmpDirPath.equals("")){
tmpDir = new File(getGitBucketHome(), "tmp");

View File

@@ -1,4 +1,4 @@
notifications:1.8.0
gist:4.18.0
notifications:1.9.0
gist:4.19.0
emoji:4.6.0
pages:1.8.0

View File

@@ -60,13 +60,12 @@
<!-- ACCESS_TOKEN -->
<!--================================================================================================-->
<createTable tableName="ACCESS_TOKEN">
<column name="ACCESS_TOKEN_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
<column name="ACCESS_TOKEN_ID" type="int" nullable="false" autoIncrement="true" unique="true" primaryKeyName="IDX_ACCESS_TOKEN_PK" primaryKey="true" />
<column name="TOKEN_HASH" type="varchar(40)" nullable="false"/>
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
<column name="NOTE" type="text" nullable="false"/>
</createTable>
<addPrimaryKey constraintName="IDX_ACCESS_TOKEN_PK" tableName="ACCESS_TOKEN" columnNames="ACCESS_TOKEN_ID"/>
<addUniqueConstraint constraintName="IDX_ACCESS_TOKEN_TOKEN_HASH" tableName="ACCESS_TOKEN" columnNames="TOKEN_HASH"/>
<addForeignKeyConstraint constraintName="IDX_ACCESS_TOKEN_FK0" baseTableName="ACCESS_TOKEN" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME" onDelete="CASCADE" onUpdate="CASCADE"/>
@@ -74,7 +73,7 @@
<!-- ACTIVITY -->
<!--================================================================================================-->
<createTable tableName="ACTIVITY">
<column name="ACTIVITY_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
<column name="ACTIVITY_ID" type="int" nullable="false" autoIncrement="true" unique="true" primaryKeyName="IDX_ACTIVITY_PK" primaryKey="true"/>
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
<column name="ACTIVITY_USER_NAME" type="varchar(100)" nullable="false"/>
@@ -84,7 +83,6 @@
<column name="ACTIVITY_DATE" type="datetime" nullable="false"/>
</createTable>
<addPrimaryKey constraintName="IDX_ACTIVITY_PK" tableName="ACTIVITY" columnNames="ACTIVITY_ID"/>
<addForeignKeyConstraint constraintName="IDX_ACTIVITY_FK1" baseTableName="ACTIVITY" baseColumnNames="ACTIVITY_USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
<addForeignKeyConstraint constraintName="IDX_ACTIVITY_FK0" baseTableName="ACTIVITY" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
@@ -108,7 +106,7 @@
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
<column name="COMMIT_ID" type="varchar(100)" nullable="false"/>
<column name="COMMENT_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
<column name="COMMENT_ID" type="int" nullable="false" autoIncrement="true" unique="true" primaryKeyName="IDX_COMMIT_COMMENT_PK" primaryKey="true" />
<column name="COMMENTED_USER_NAME" type="varchar(100)" nullable="false"/>
<column name="CONTENT" type="text" nullable="false"/>
<column name="FILE_NAME" type="varchar(260)" nullable="true"/>
@@ -119,14 +117,13 @@
<column name="ISSUE_ID" type="int" nullable="true"/>
</createTable>
<addPrimaryKey constraintName="IDX_COMMIT_COMMENT_PK" tableName="COMMIT_COMMENT" columnNames="COMMENT_ID"/>
<addForeignKeyConstraint constraintName="IDX_COMMIT_COMMENT_FK0" baseTableName="COMMIT_COMMENT" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
<!--================================================================================================-->
<!-- COMMIT_STATUS -->
<!--================================================================================================-->
<createTable tableName="COMMIT_STATUS">
<column name="COMMIT_STATUS_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
<column name="COMMIT_STATUS_ID" type="int" nullable="false" autoIncrement="true" unique="true" primaryKeyName="IDX_COMMIT_STATUS_PK" primaryKey="true" />
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
<column name="COMMIT_ID" type="varchar(40)" nullable="false"/>
@@ -139,7 +136,6 @@
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
</createTable>
<addPrimaryKey constraintName="IDX_COMMIT_STATUS_PK" tableName="COMMIT_STATUS" columnNames="COMMIT_STATUS_ID"/>
<addUniqueConstraint constraintName="IDX_COMMIT_STATUS_1" tableName="COMMIT_STATUS" columnNames="USER_NAME, REPOSITORY_NAME, COMMIT_ID, CONTEXT"/>
<addForeignKeyConstraint constraintName="IDX_COMMIT_STATUS_FK3" baseTableName="COMMIT_STATUS" baseColumnNames="CREATOR" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME" onDelete="CASCADE" onUpdate="CASCADE"/>
<addForeignKeyConstraint constraintName="IDX_COMMIT_STATUS_FK2" baseTableName="COMMIT_STATUS" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME" onDelete="CASCADE" onUpdate="CASCADE"/>
@@ -218,7 +214,7 @@
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
<column name="ISSUE_ID" type="int" nullable="false"/>
<column name="COMMENT_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
<column name="COMMENT_ID" type="int" nullable="false" autoIncrement="true" unique="true" primaryKeyName="IDX_ISSUE_COMMENT_PK" primaryKey="true" />
<column name="ACTION" type="varchar(20)" nullable="false"/>
<column name="COMMENTED_USER_NAME" type="varchar(100)" nullable="false"/>
<column name="CONTENT" type="text" nullable="false"/>
@@ -226,7 +222,6 @@
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
</createTable>
<addPrimaryKey constraintName="IDX_ISSUE_COMMENT_PK" tableName="ISSUE_COMMENT" columnNames="COMMENT_ID"/>
<addForeignKeyConstraint constraintName="IDX_ISSUE_COMMENT_FK0" baseTableName="ISSUE_COMMENT" baseColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID" referencedTableName="ISSUE" referencedColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID"/>
<!--================================================================================================-->

View File

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

View File

@@ -16,7 +16,7 @@ class ScalatraBootstrap extends LifeCycle with SystemSettingsService {
context.getSessionCookieConfig.setSecure(true)
}
// Register TransactionFilter and BasicAuthenticationFilter at first
// Register TransactionFilter at first
context.addFilter("transactionFilter", new TransactionFilter)
context
.getFilterRegistration("transactionFilter")

View File

@@ -1,7 +1,22 @@
package gitbucket.core
import io.github.gitbucket.solidbase.migration.{SqlMigration, LiquibaseMigration}
import io.github.gitbucket.solidbase.model.{Version, Module}
import java.io.FileOutputStream
import java.nio.charset.StandardCharsets
import java.sql.Connection
import java.util
import java.util.UUID
import gitbucket.core.model.Activity
import gitbucket.core.util.Directory.ActivityLog
import gitbucket.core.util.JDBCUtil
import io.github.gitbucket.solidbase.Solidbase
import io.github.gitbucket.solidbase.migration.{LiquibaseMigration, Migration, SqlMigration}
import io.github.gitbucket.solidbase.model.{Module, Version}
import org.json4s.NoTypeHints
import org.json4s.jackson.Serialization
import org.json4s.jackson.Serialization.write
import scala.util.Using
object GitBucketCoreModule
extends Module(
@@ -65,5 +80,38 @@ object GitBucketCoreModule
new Version("4.31.1"),
new Version("4.31.2"),
new Version("4.32.0", new LiquibaseMigration("update/gitbucket-core_4.32.xml")),
new Version("4.33.0")
new Version("4.33.0"),
new Version(
"4.34.0",
new Migration() {
override def migrate(moduleId: String, version: String, context: util.Map[String, AnyRef]): Unit = {
implicit val formats = Serialization.formats(NoTypeHints)
import JDBCUtil._
val conn = context.get(Solidbase.CONNECTION).asInstanceOf[Connection]
val list = conn.select("SELECT * FROM ACTIVITY ORDER BY ACTIVITY_ID") {
rs =>
Activity(
activityId = UUID.randomUUID().toString,
userName = rs.getString("USER_NAME"),
repositoryName = rs.getString("REPOSITORY_NAME"),
activityUserName = rs.getString("ACTIVITY_USER_NAME"),
activityType = rs.getString("ACTIVITY_TYPE"),
message = rs.getString("MESSAGE"),
additionalInfo = {
val additionalInfo = rs.getString("ADDITIONAL_INFO")
if (rs.wasNull()) None else Some(additionalInfo)
},
activityDate = rs.getTimestamp("ACTIVITY_DATE")
)
}
Using.resource(new FileOutputStream(ActivityLog, true)) { out =>
list.foreach { activity =>
out.write((write(activity) + "\n").getBytes(StandardCharsets.UTF_8))
}
}
}
},
new LiquibaseMigration("update/gitbucket-core_4.34.xml")
)
)

View File

@@ -21,7 +21,8 @@ case class ApiPullRequest(
body: String,
user: ApiUser,
labels: List[ApiLabel],
assignee: Option[ApiUser]
assignee: Option[ApiUser],
draft: Option[Boolean]
) {
val id = 0 // dummy id
val html_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}")
@@ -62,7 +63,8 @@ object ApiPullRequest {
body = issue.content.getOrElse(""),
user = user,
labels = labels,
assignee = assignee
assignee = assignee,
draft = Some(pullRequest.isDraft)
)
case class Commit(sha: String, ref: String, repo: ApiRepository)(baseOwner: String) {

View File

@@ -6,7 +6,7 @@ case class ApiReleaseAsset(name: String, size: Long)(tag: String, fileName: Stri
val label = name
val file_id = fileName
val browser_download_url = ApiPath(
s"/api/v3/repos/${repositoryName.fullName}/releases/${tag}/assets/${fileName}"
s"/${repositoryName.fullName}/releases/${tag}/assets/${fileName}"
)
}

View File

@@ -5,7 +5,8 @@ case class CreateAPullRequest(
head: String,
base: String,
body: Option[String],
maintainer_can_modify: Option[Boolean]
maintainer_can_modify: Option[Boolean],
draft: Option[Boolean]
)
case class CreateAPullRequestAlt(

View File

@@ -16,6 +16,7 @@ import gitbucket.core.util._
import org.scalatra.i18n.Messages
import org.scalatra.BadRequest
import org.scalatra.forms._
import org.scalatra.Forbidden
class AccountController
extends AccountControllerBase
@@ -34,6 +35,7 @@ class AccountController
with WebHookService
with PrioritiesService
with RepositoryCreationService
with RequestCache
trait AccountControllerBase extends AccountManagementControllerBase {
self: AccountService
@@ -82,7 +84,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
val newForm = mapping(
"userName" -> trim(label("User name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
"password" -> trim(label("Password", text(required, maxlength(20), password))),
"password" -> trim(label("Password", text(required, maxlength(20)))),
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))),
"extraMailAddresses" -> list(
@@ -94,7 +96,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
)(AccountNewForm.apply)
val editForm = mapping(
"password" -> trim(label("Password", optional(text(maxlength(20), password)))),
"password" -> trim(label("Password", optional(text(maxlength(20))))),
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))),
"extraMailAddresses" -> list(
@@ -691,27 +693,28 @@ trait AccountControllerBase extends AccountManagementControllerBase {
* Create new repository.
*/
post("/new", newRepositoryForm)(usersOnly { form =>
LockUtil.lock(s"${form.owner}/${form.name}") {
if (getRepository(form.owner, form.name).isEmpty) {
createRepository(
context.loginAccount.get,
form.owner,
form.name,
form.description,
form.isPrivate,
form.initOption,
form.sourceUrl
)
if (context.settings.repositoryOperation.create || context.loginAccount.get.isAdmin) {
LockUtil.lock(s"${form.owner}/${form.name}") {
if (getRepository(form.owner, form.name).isEmpty) {
createRepository(
context.loginAccount.get,
form.owner,
form.name,
form.description,
form.isPrivate,
form.initOption,
form.sourceUrl
)
}
}
}
// redirect to the repository
redirect(s"/${form.owner}/${form.name}")
// redirect to the repository
redirect(s"/${form.owner}/${form.name}")
} else Forbidden()
})
get("/:owner/:repository/fork")(readableUsersOnly { repository =>
if (repository.repository.options.allowFork) {
val loginAccount = context.loginAccount.get
val loginAccount = context.loginAccount.get
if (repository.repository.options.allowFork && (context.settings.repositoryOperation.fork || loginAccount.isAdmin)) {
val loginUserName = loginAccount.userName
val groups = getGroupsByUserName(loginUserName)
groups match {
@@ -735,8 +738,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
})
post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) =>
if (repository.repository.options.allowFork) {
val loginAccount = context.loginAccount.get
val loginAccount = context.loginAccount.get
if (repository.repository.options.allowFork && (context.settings.repositoryOperation.fork || loginAccount.isAdmin)) {
val loginUserName = loginAccount.userName
val accountName = form.accountName
@@ -750,7 +753,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
// redirect to the repository
redirect(s"/${accountName}/${repository.name}")
}
} else BadRequest()
} else Forbidden()
})
private def existsAccount: Constraint = new Constraint() {

View File

@@ -52,6 +52,7 @@ class ApiController
with ReferrerAuthenticator
with ReadableUsersAuthenticator
with WritableUsersAuthenticator
with RequestCache
trait ApiControllerBase extends ControllerBase {

View File

@@ -22,12 +22,18 @@ class DashboardController
with WebHookPullRequestReviewCommentService
with MilestonesService
with UsersAuthenticator
with RequestCache
trait DashboardControllerBase extends ControllerBase {
self: IssuesService with PullRequestService with RepositoryService with AccountService with UsersAuthenticator =>
get("/dashboard/repos")(usersOnly {
val repos = getVisibleRepositories(context.loginAccount, withoutPhysicalInfo = true)
val repos = getVisibleRepositories(
context.loginAccount,
None,
withoutPhysicalInfo = true,
limit = context.settings.limitVisibleRepositories
)
html.repos(getGroupNames(context.loginAccount.get.userName), repos, repos)
})
@@ -93,7 +99,12 @@ trait DashboardControllerBase extends ControllerBase {
},
filter,
getGroupNames(userName),
getVisibleRepositories(context.loginAccount, withoutPhysicalInfo = true)
getVisibleRepositories(
context.loginAccount,
None,
withoutPhysicalInfo = true,
limit = context.settings.limitVisibleRepositories
)
)
}
@@ -118,7 +129,12 @@ trait DashboardControllerBase extends ControllerBase {
},
filter,
getGroupNames(userName),
getVisibleRepositories(context.loginAccount, withoutPhysicalInfo = true)
getVisibleRepositories(
context.loginAccount,
None,
withoutPhysicalInfo = true,
limit = context.settings.limitVisibleRepositories
)
)
}

View File

@@ -17,6 +17,7 @@ import org.scalatra.servlet.{FileItem, FileUploadSupport, MultipartConfig}
import org.apache.commons.io.{FileUtils, IOUtils}
import scala.util.Using
import gitbucket.core.service.SystemSettingsService
/**
* Provides Ajax based file upload functionality.
@@ -28,11 +29,11 @@ class FileUploadController
with FileUploadSupport
with RepositoryService
with AccountService
with ReleaseService {
configureMultipartHandling(MultipartConfig(maxFileSize = Some(FileUtil.MaxFileSize)))
with ReleaseService
with SystemSettingsService {
post("/image") {
setMultipartConfig()
execute(
{ (file, fileId) =>
FileUtils
@@ -44,6 +45,7 @@ class FileUploadController
}
post("/tmp") {
setMultipartConfig()
execute(
{ (file, fileId) =>
FileUtils
@@ -55,6 +57,7 @@ class FileUploadController
}
post("/file/:owner/:repository") {
setMultipartConfig()
execute(
{ (file, fileId) =>
FileUtils.writeByteArrayToFile(
@@ -70,6 +73,7 @@ class FileUploadController
}
post("/wiki/:owner/:repository") {
setMultipartConfig()
// Don't accept not logged-in users
session.get(Keys.Session.LoginAccount).collect {
case loginAccount: Account =>
@@ -128,6 +132,7 @@ class FileUploadController
}
post("/release/:owner/:repository/:tag") {
setMultipartConfigForLargeFile()
session
.get(Keys.Session.LoginAccount)
.collect {
@@ -150,6 +155,7 @@ class FileUploadController
post("/import") {
import JDBCUtil._
setMultipartConfig()
session.get(Keys.Session.LoginAccount).collect {
case loginAccount: Account if loginAccount.isAdmin =>
execute({ (file, fileId) =>
@@ -159,6 +165,18 @@ class FileUploadController
redirect("/admin/data")
}
private def setMultipartConfig(): Unit = {
val settings = loadSystemSettings()
val config = MultipartConfig(maxFileSize = Some(settings.upload.maxFileSize))
config.apply(request.getServletContext())
}
private def setMultipartConfigForLargeFile(): Unit = {
val settings = loadSystemSettings()
val config = MultipartConfig(maxFileSize = Some(settings.upload.largeMaxFileSize))
config.apply(request.getServletContext())
}
private def onlyWikiEditable(owner: String, repository: String, loginAccount: Account)(action: => Any): Any = {
implicit val session = Database.getSession(request)
getRepository(owner, repository) match {

View File

@@ -29,6 +29,7 @@ class IndexController
with AccessTokenService
with AccountFederationService
with OpenIDConnectService
with RequestCache
trait IndexControllerBase extends ControllerBase {
self: RepositoryService
@@ -65,7 +66,12 @@ trait IndexControllerBase extends ControllerBase {
val visibleOwnerSet: Set[String] = Set(account.userName) ++ getGroupsByUserName(account.userName)
gitbucket.core.html.index(
getRecentActivitiesByOwners(visibleOwnerSet),
getVisibleRepositories(Some(account), withoutPhysicalInfo = true),
getVisibleRepositories(
Some(account),
None,
withoutPhysicalInfo = true,
limit = context.settings.limitVisibleRepositories
),
showBannerToCreatePersonalAccessToken = hasAccountFederation(account.userName) && !hasAccessToken(
account.userName
)
@@ -73,7 +79,7 @@ trait IndexControllerBase extends ControllerBase {
}
.getOrElse {
gitbucket.core.html.index(
getRecentActivities(),
getRecentPublicActivities(),
getVisibleRepositories(None, withoutPhysicalInfo = true),
showBannerToCreatePersonalAccessToken = false
)
@@ -156,7 +162,7 @@ trait IndexControllerBase extends ControllerBase {
get("/activities.atom") {
contentType = "application/atom+xml; type=feed"
xml.feed(getRecentActivities())
xml.feed(getRecentPublicActivities())
}
post("/sidebar-collapse") {
@@ -279,11 +285,28 @@ trait IndexControllerBase extends ControllerBase {
get("/search") {
val query = params.getOrElse("query", "").trim.toLowerCase
val visibleRepositories =
getVisibleRepositories(context.loginAccount, repositoryUserName = None, withoutPhysicalInfo = true)
val repositories = visibleRepositories.filter { repository =>
getVisibleRepositories(
context.loginAccount,
None,
withoutPhysicalInfo = true,
limit = context.settings.limitVisibleRepositories
)
val repositories = {
context.settings.limitVisibleRepositories match {
case true =>
getVisibleRepositories(
context.loginAccount,
None,
withoutPhysicalInfo = true,
limit = false
)
case false => visibleRepositories
}
}.filter { repository =>
repository.name.toLowerCase.indexOf(query) >= 0 || repository.owner.toLowerCase.indexOf(query) >= 0
}
gitbucket.core.search.html.repositories(query, repositories, visibleRepositories)
}
}

View File

@@ -30,6 +30,7 @@ class IssuesController
with WebHookPullRequestReviewCommentService
with CommitsService
with PrioritiesService
with RequestCache
trait IssuesControllerBase extends ControllerBase {
self: IssuesService

View File

@@ -36,6 +36,7 @@ class PullRequestsController
with MergeService
with ProtectedBranchService
with PrioritiesService
with RequestCache
trait PullRequestsControllerBase extends ControllerBase {
self: RepositoryService

View File

@@ -2,7 +2,14 @@ package gitbucket.core.controller
import java.io.File
import gitbucket.core.service.{AccountService, ActivityService, ReleaseService, RepositoryService}
import gitbucket.core.service.{
AccountService,
ActivityService,
PaginationHelper,
ReleaseService,
RepositoryService,
RequestCache
}
import gitbucket.core.util._
import gitbucket.core.util.Directory._
import gitbucket.core.util.Implicits._
@@ -10,6 +17,7 @@ import org.scalatra.forms._
import gitbucket.core.releases.html
import org.apache.commons.io.FileUtils
import org.eclipse.jgit.api.Git
import scala.util.Using
class ReleaseController
@@ -21,6 +29,7 @@ class ReleaseController
with ReadableUsersAuthenticator
with ReferrerAuthenticator
with WritableUsersAuthenticator
with RequestCache
trait ReleaseControllerBase extends ControllerBase {
self: RepositoryService
@@ -42,17 +51,14 @@ trait ReleaseControllerBase extends ControllerBase {
)(ReleaseForm.apply)
get("/:owner/:repository/releases")(referrersOnly { repository =>
val releases = getReleases(repository.owner, repository.name)
val assets = getReleaseAssetsMap(repository.owner, repository.name)
val page = PaginationHelper.page(params.get("page"))
html.list(
repository,
repository.tags.reverse.map { tag =>
(tag, releases.find(_.tag == tag.name).map { release =>
(release, assets(release))
})
},
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
fetchReleases(repository, page),
hasDeveloperRole(repository.owner, repository.name, context.loginAccount),
page,
repository.tags.size
)
})
@@ -215,4 +221,21 @@ trait ReleaseControllerBase extends ControllerBase {
redirect(s"/${repository.owner}/${repository.name}/releases")
})
private def fetchReleases(repository: RepositoryService.RepositoryInfo, page: Int) = {
import gitbucket.core.service.ReleaseService._
val (offset, limit) = ((page - 1) * ReleaseLimit, ReleaseLimit)
val tagsToDisplay = repository.tags.reverse.slice(offset, offset + limit)
val releases = getReleases(repository.owner, repository.name, tagsToDisplay)
val assets = getReleaseAssetsMap(repository.owner, repository.name, releases)
val tagsWithReleases = tagsToDisplay.map { tag =>
(tag, releases.find(_.tag == tag.name).map { release =>
(release, assets(release))
})
}
tagsWithReleases
}
}

View File

@@ -20,6 +20,7 @@ import org.eclipse.jgit.lib.Constants
import org.eclipse.jgit.lib.ObjectId
import scala.util.Using
import org.scalatra.Forbidden
class RepositorySettingsController
extends RepositorySettingsControllerBase
@@ -29,8 +30,10 @@ class RepositorySettingsController
with ProtectedBranchService
with CommitStatusService
with DeployKeyService
with ActivityService
with OwnerAuthenticator
with UsersAuthenticator
with RequestCache
trait RepositorySettingsControllerBase extends ControllerBase {
self: RepositoryService
@@ -39,12 +42,12 @@ trait RepositorySettingsControllerBase extends ControllerBase {
with ProtectedBranchService
with CommitStatusService
with DeployKeyService
with ActivityService
with OwnerAuthenticator
with UsersAuthenticator =>
// for repository options
case class OptionsForm(
repositoryName: String,
description: Option[String],
isPrivate: Boolean,
issuesOption: String,
@@ -57,9 +60,6 @@ trait RepositorySettingsControllerBase extends ControllerBase {
)
val optionsForm = mapping(
"repositoryName" -> trim(
label("Repository Name", text(required, maxlength(100), repository, renameRepositoryName))
),
"description" -> trim(label("Description", optional(text()))),
"isPrivate" -> trim(label("Repository Type", boolean())),
"issuesOption" -> trim(label("Issues Option", text(required, featureOption))),
@@ -100,9 +100,16 @@ trait RepositorySettingsControllerBase extends ControllerBase {
"events" -> webhookEvents,
"ctype" -> label("ctype", text()),
"token" -> optional(trim(label("token", text(maxlength(100)))))
)(
(url, events, ctype, token) => WebHookForm(url, events, WebHookContentType.valueOf(ctype), token)
)((url, events, ctype, token) => WebHookForm(url, events, WebHookContentType.valueOf(ctype), token))
// for rename repository
case class RenameRepositoryForm(repositoryName: String)
val renameForm = mapping(
"repositoryName" -> trim(
label("New repository name", text(required, maxlength(100), repository, renameRepositoryName))
)
)(RenameRepositoryForm.apply)
// for transfer ownership
case class TransferOwnerShipForm(newOwner: String)
@@ -144,13 +151,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
form.mergeOptions,
form.defaultMergeOption
)
// Change repository name
if (repository.name != form.repositoryName) {
// Update database
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
}
flash.update("info", "Repository settings has been updated.")
redirect(s"/${repository.owner}/${form.repositoryName}/settings/options")
redirect(s"/${repository.owner}/${repository.name}/settings/options")
})
/** branch settings */
@@ -250,9 +252,10 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Send the test request to registered web hook URLs.
*/
ajaxPost("/:owner/:repository/settings/hooks/test")(ownerOnly { repository =>
def _headers(h: Array[org.apache.http.Header]): Array[Array[String]] = h.map { h =>
Array(h.getName, h.getValue)
}
def _headers(h: Array[org.apache.http.Header]): Array[Array[String]] =
h.map { h =>
Array(h.getName, h.getValue)
}
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
git =>
@@ -364,24 +367,56 @@ trait RepositorySettingsControllerBase extends ControllerBase {
html.danger(_, flash.get("info"))
})
/**
* Rename repository.
*/
post("/:owner/:repository/settings/rename", renameForm)(ownerOnly { (form, repository) =>
if (context.settings.repositoryOperation.rename || context.loginAccount.get.isAdmin) {
if (repository.name != form.repositoryName) {
// Update database and move git repository
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
// Record activity log
recordRenameRepositoryActivity(
repository.owner,
form.repositoryName,
repository.name,
context.loginAccount.get.userName
)
}
redirect(s"/${repository.owner}/${form.repositoryName}")
} else Forbidden()
})
/**
* Transfer repository ownership.
*/
post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) =>
// Change repository owner
if (repository.owner != form.newOwner) {
renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
}
redirect(s"/${form.newOwner}/${repository.name}")
if (context.settings.repositoryOperation.transfer || context.loginAccount.get.isAdmin) {
// Change repository owner
if (repository.owner != form.newOwner) {
// Update database and move git repository
renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
// Record activity log
recordRenameRepositoryActivity(
form.newOwner,
repository.name,
repository.owner,
context.loginAccount.get.userName
)
}
redirect(s"/${form.newOwner}/${repository.name}")
} else Forbidden()
})
/**
* Delete the repository.
*/
post("/:owner/:repository/settings/delete")(ownerOnly { repository =>
// Delete the repository and related files
deleteRepository(repository.repository)
redirect(s"/${repository.owner}")
if (context.settings.repositoryOperation.delete || context.loginAccount.get.isAdmin) {
// Delete the repository and related files
deleteRepository(repository.repository)
redirect(s"/${repository.owner}")
} else Forbidden()
})
/**
@@ -418,32 +453,34 @@ trait RepositorySettingsControllerBase extends ControllerBase {
/**
* Provides duplication check for web hook url.
*/
private def webHook(needExists: Boolean): Constraint = new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] =
if (getWebHook(params("owner"), params("repository"), value).isDefined != needExists) {
Some(if (needExists) {
"URL had not been registered yet."
private def webHook(needExists: Boolean): Constraint =
new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] =
if (getWebHook(params("owner"), params("repository"), value).isDefined != needExists) {
Some(if (needExists) {
"URL had not been registered yet."
} else {
"URL had been registered already."
})
} else {
"URL had been registered already."
})
} else {
None
}
}
private def webhookEvents = new ValueType[Set[WebHook.Event]] {
def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Set[WebHook.Event] = {
WebHook.Event.values.flatMap { t =>
params.get(name + "." + t.name).map(_ => t)
}.toSet
None
}
}
def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] =
if (convert(name, params, messages).isEmpty) {
Seq(name -> messages("error.required").format(name))
} else {
Nil
private def webhookEvents =
new ValueType[Set[WebHook.Event]] {
def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Set[WebHook.Event] = {
WebHook.Event.values.flatMap { t =>
params.get(name + "." + t.name).map(_ => t)
}.toSet
}
}
def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] =
if (convert(name, params, messages).isEmpty) {
Seq(name -> messages("error.required").format(name))
} else {
Nil
}
}
// /**
// * Provides Constraint to validate the collaborator name.
@@ -463,70 +500,77 @@ trait RepositorySettingsControllerBase extends ControllerBase {
/**
* Duplicate check for the rename repository name.
*/
private def renameRepositoryName: Constraint = new Constraint() {
override def validate(
name: String,
value: String,
params: Map[String, Seq[String]],
messages: Messages
): Option[String] = {
for {
repoName <- params.optionValue("repository") if repoName != value
userName <- params.optionValue("owner")
_ <- getRepositoryNamesOfUser(userName).find(_ == value)
} yield {
"Repository already exists."
private def renameRepositoryName: Constraint =
new Constraint() {
override def validate(
name: String,
value: String,
params: Map[String, Seq[String]],
messages: Messages
): Option[String] = {
for {
repoName <- params.optionValue("repository") if repoName != value
userName <- params.optionValue("owner")
_ <- getRepositoryNamesOfUser(userName).find(_ == value)
} yield {
"Repository already exists."
}
}
}
}
/**
*
*/
private def featureOption: Constraint = new Constraint() {
override def validate(
name: String,
value: String,
params: Map[String, Seq[String]],
messages: Messages
): Option[String] =
if (Seq("DISABLE", "PRIVATE", "PUBLIC", "ALL").contains(value)) None else Some("Option is invalid.")
}
private def featureOption: Constraint =
new Constraint() {
override def validate(
name: String,
value: String,
params: Map[String, Seq[String]],
messages: Messages
): Option[String] =
if (Seq("DISABLE", "PRIVATE", "PUBLIC", "ALL").contains(value)) None else Some("Option is invalid.")
}
/**
* Provides Constraint to validate the repository transfer user.
*/
private def transferUser: Constraint = new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] =
getAccountByUserName(value) match {
case None => Some("User does not exist.")
case Some(x) =>
if (x.userName == params("owner")) {
Some("This is current repository owner.")
} else {
params.get("repository").flatMap { repositoryName =>
getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map { _ =>
"User already has same repository."
private def transferUser: Constraint =
new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] =
getAccountByUserName(value) match {
case None => Some("User does not exist.")
case Some(x) =>
if (x.userName == params("owner")) {
Some("This is current repository owner.")
} else {
params.get("repository").flatMap { repositoryName =>
getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map { _ =>
"User already has same repository."
}
}
}
}
}
}
}
}
private def mergeOptions = new ValueType[Seq[String]] {
override def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[String] = {
params.getOrElse("mergeOptions", Nil)
}
override def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] = {
val mergeOptions = params.getOrElse("mergeOptions", Nil)
if (mergeOptions.isEmpty) {
Seq("mergeOptions" -> "At least one option must be enabled.")
} else if (!mergeOptions.forall(x => Seq("merge-commit", "squash", "rebase").contains(x))) {
Seq("mergeOptions" -> "mergeOptions are invalid.")
} else {
Nil
private def mergeOptions =
new ValueType[Seq[String]] {
override def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[String] = {
params.getOrElse("mergeOptions", Nil)
}
override def validate(
name: String,
params: Map[String, Seq[String]],
messages: Messages
): Seq[(String, String)] = {
val mergeOptions = params.getOrElse("mergeOptions", Nil)
if (mergeOptions.isEmpty) {
Seq("mergeOptions" -> "At least one option must be enabled.")
} else if (!mergeOptions.forall(x => Seq("merge-commit", "squash", "rebase").contains(x))) {
Seq("mergeOptions" -> "mergeOptions are invalid.")
} else {
Nil
}
}
}
}
}

View File

@@ -60,6 +60,7 @@ class RepositoryViewerController
with WebHookPullRequestService
with WebHookPullRequestReviewCommentService
with ProtectedBranchService
with RequestCache
/**
* The repository viewer.
@@ -928,8 +929,9 @@ trait RepositoryViewerControllerBase extends ControllerBase {
defining(JGitUtil.getRevCommitFromId(git, objectId)) { revCommit =>
val lastModifiedCommit =
if (path == ".") revCommit else JGitUtil.getLastModifiedCommit(git, revCommit, path)
val commitCount = JGitUtil.getCommitCount(git, lastModifiedCommit.getName)
// get files
val files = JGitUtil.getFileList(git, revision, path, context.settings.baseUrl)
val files = JGitUtil.getFileList(git, revision, path, context.settings.baseUrl, commitCount)
val parentPath = if (path == ".") Nil else path.split("/").toList
// process README.md or README.markdown
val readme = files
@@ -950,7 +952,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
repository,
if (path == ".") Nil else path.split("/").toList, // current path
new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit
JGitUtil.getCommitCount(git, lastModifiedCommit.getName),
commitCount,
files,
readme,
hasDeveloperRole(repository.owner, repository.name, context.loginAccount),

View File

@@ -38,14 +38,22 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
"information" -> trim(label("Information", optional(text()))),
"allowAccountRegistration" -> trim(label("Account registration", boolean())),
"allowAnonymousAccess" -> trim(label("Anonymous access", boolean())),
"isCreateRepoOptionPublic" -> trim(label("Default option to create a new repository", boolean())),
"isCreateRepoOptionPublic" -> trim(label("Default visibility of new repository", boolean())),
"repositoryOperation" -> mapping(
"create" -> trim(label("Allow all users to create repository", boolean())),
"delete" -> trim(label("Allow all users to delete repository", boolean())),
"rename" -> trim(label("Allow all users to rename repository", boolean())),
"transfer" -> trim(label("Allow all users to transfer repository", boolean())),
"fork" -> trim(label("Allow all users to fork repository", boolean()))
)(RepositoryOperation.apply),
"gravatar" -> trim(label("Gravatar", boolean())),
"notification" -> trim(label("Notification", boolean())),
"activityLogLimit" -> trim(label("Limit of activity logs", optional(number()))),
"limitVisibleRepositories" -> trim(label("limitVisibleRepositories", boolean())),
"ssh" -> mapping(
"enabled" -> trim(label("SSH access", boolean())),
"host" -> trim(label("SSH host", optional(text()))),
"port" -> trim(label("SSH port", optional(number()))),
"port" -> trim(label("SSH port", optional(number())))
)(Ssh.apply),
"useSMTP" -> trim(label("SMTP", boolean())),
"smtp" -> optionalIfNotChecked(
@@ -90,11 +98,18 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
)(OIDC.apply)
),
"skinName" -> trim(label("AdminLTE skin name", text(required))),
"userDefinedCss" -> trim(label("User-defined CSS", optional(text()))),
"showMailAddress" -> trim(label("Show mail address", boolean())),
"webhook" -> mapping(
"blockPrivateAddress" -> trim(label("Block private address", boolean())),
"whitelist" -> trim(label("Whitelist", multiLineText()))
)(WebHook.apply)
)(WebHook.apply),
"upload" -> mapping(
"maxFileSize" -> trim(label("Max file size", long(required))),
"timeout" -> trim(label("Timeout", long(required))),
"largeMaxFileSize" -> trim(label("Max file size for large file", long(required))),
"largeTimeout" -> trim(label("Timeout for large file", long(required)))
)(Upload.apply)
)(SystemSettings.apply).verifying { settings =>
Vector(
if (settings.ssh.enabled && settings.baseUrl.isEmpty) {
@@ -170,7 +185,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
val newUserForm = mapping(
"userName" -> trim(label("Username", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
"password" -> trim(label("Password", text(required, maxlength(20), password))),
"password" -> trim(label("Password", text(required, maxlength(20)))),
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))),
"extraMailAddresses" -> list(
@@ -184,7 +199,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
val editUserForm = mapping(
"userName" -> trim(label("Username", text(required, maxlength(100), identifier))),
"password" -> trim(label("Password", optional(text(maxlength(20), password)))),
"password" -> trim(label("Password", optional(text(maxlength(20))))),
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))),
"extraMailAddresses" -> list(
@@ -529,24 +544,26 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
}
}
private def members: Constraint = new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] = {
if (value.split(",").exists {
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
}) None
else Some("Must select one manager at least.")
}
}
protected def disableByNotYourself(paramName: String): Constraint = new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] = {
params.get(paramName).flatMap { userName =>
if (userName == context.loginAccount.get.userName && params.get("removed") == Some("true"))
Some("You can't disable your account yourself")
else
None
private def members: Constraint =
new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] = {
if (value.split(",").exists {
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
}) None
else Some("Must select one manager at least.")
}
}
protected def disableByNotYourself(paramName: String): Constraint =
new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] = {
params.get(paramName).flatMap { userName =>
if (userName == context.loginAccount.get.userName && params.get("removed") == Some("true"))
Some("You can't disable your account yourself")
else
None
}
}
}
}
}

View File

@@ -24,6 +24,7 @@ class WikiController
with WebHookService
with ReadableUsersAuthenticator
with ReferrerAuthenticator
with RequestCache
trait WikiControllerBase extends ControllerBase {
self: WikiService

View File

@@ -5,6 +5,7 @@ import gitbucket.core.util.Directory.getRepositoryDir
import gitbucket.core.util.ReferrerAuthenticator
import gitbucket.core.util.Implicits._
import org.eclipse.jgit.api.Git
import org.slf4j.LoggerFactory
import scala.jdk.CollectionConverters._
import scala.util.Using
@@ -12,11 +13,23 @@ import scala.util.Using
trait ApiGitReferenceControllerBase extends ControllerBase {
self: ReferrerAuthenticator =>
private val logger = LoggerFactory.getLogger(classOf[ApiGitReferenceControllerBase])
/*
* i. Get a reference
* https://developer.github.com/v3/git/refs/#get-a-reference
*/
get("/api/v3/repos/:owner/:repository/git/ref/*")(referrersOnly { repository =>
getRef()
})
// Some versions of GHE support this path
get("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { repository =>
logger.warn("git/refs/ endpoint may not be compatible with GitHub API v3. Consider using git/ref/ endpoint instead")
getRef()
})
private def getRef() = {
val revstr = multiParams("splat").head
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
val ref = git.getRepository().findRef(revstr)
@@ -38,7 +51,8 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
})
}
}
})
}
/*
* ii. Get all references
* https://developer.github.com/v3/git/refs/#get-all-references

View File

@@ -114,7 +114,7 @@ trait ApiPullRequestControllerBase extends ControllerBase {
requestBranch = reqBranch,
commitIdFrom = commitIdFrom.getName,
commitIdTo = commitIdTo.getName,
isDraft = false,
isDraft = createPullReq.draft.getOrElse(false),
loginAccount = context.loginAccount.get,
settings = context.settings
)

View File

@@ -9,6 +9,7 @@ import gitbucket.core.util.JGitUtil.CommitInfo
import gitbucket.core.util.{JGitUtil, ReferrerAuthenticator, RepositoryName}
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.revwalk.RevWalk
import scala.jdk.CollectionConverters._
import scala.util.Using
trait ApiRepositoryCommitControllerBase extends ControllerBase {
@@ -17,6 +18,33 @@ trait ApiRepositoryCommitControllerBase extends ControllerBase {
* i. List commits on a repository
* https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository
*/
get("/api/v3/repos/:owner/:repository/commits")(referrersOnly { repository =>
val owner = repository.owner
val name = repository.name
// TODO: The following parameters need to be implemented. [:path, :author, :since, :until]
val sha = if (request.body.nonEmpty) (parse(request.body) \ "sha").extract[String] else "refs/heads/master";
Using.resource(Git.open(getRepositoryDir(owner, name))) {
git =>
val repo = git.getRepository
Using.resource(new RevWalk(repo)) {
revWalk =>
val objectId = repo.resolve(sha)
revWalk.markStart(revWalk.parseCommit(objectId))
JsonFormat(revWalk.asScala.take(30).map {
commit =>
val commitInfo = new CommitInfo(commit)
ApiCommits(
repositoryName = RepositoryName(repository),
commitInfo = commitInfo,
diffs = JGitUtil.getDiffs(git, commitInfo.parents.headOption, commitInfo.id, false, true),
author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress),
committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress),
commentCount = getCommitComment(repository.owner, repository.name, commitInfo.id.toString).size
)
})
}
}
})
/*
* ii. Get a single commit

View File

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

View File

@@ -45,7 +45,7 @@ trait CoreProfile
with Profile
with AccessTokenComponent
with AccountComponent
with ActivityComponent
with ActivityComponent // ActivityComponent has been deprecated, but keep it for binary compatibility
with CollaboratorComponent
with CommitCommentComponent
with CommitStatusComponent

View File

@@ -17,7 +17,9 @@ trait AccountService {
def authenticate(settings: SystemSettings, userName: String, password: String)(
implicit s: Session
): Option[Account] = {
val account = if (settings.ldapAuthentication) {
val account = if (password.isEmpty) {
None
} else if (settings.ldapAuthentication) {
ldapAuthentication(settings, userName, password)
} else {
defaultAuthentication(userName, password)

View File

@@ -3,65 +3,169 @@ package gitbucket.core.service
import gitbucket.core.model.Activity
import gitbucket.core.util.JGitUtil
import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile.blockingApi._
import gitbucket.core.util.Directory._
import org.json4s._
import org.json4s.jackson.Serialization
import org.json4s.jackson.Serialization.{read, write}
import scala.util.Using
import java.io.FileOutputStream
import java.nio.charset.StandardCharsets
import java.util.UUID
import gitbucket.core.controller.Context
import org.apache.commons.io.input.ReversedLinesFileReader
import scala.collection.mutable.ListBuffer
trait ActivityService {
self: RequestCache =>
def deleteOldActivities(limit: Int)(implicit s: Session): Int = {
Activities.map(_.activityId).sortBy(_ desc).drop(limit).firstOption.map { id =>
Activities.filter(_.activityId <= id.bind).delete
} getOrElse 0
private implicit val formats = Serialization.formats(NoTypeHints)
private def writeLog(activity: Activity): Unit = {
Using.resource(new FileOutputStream(ActivityLog, true)) { out =>
out.write((write(activity) + "\n").getBytes(StandardCharsets.UTF_8))
}
}
def getActivitiesByUser(activityUserName: String, isPublic: Boolean)(implicit s: Session): List[Activity] =
Activities
.join(Repositories)
.on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName))
.filter {
case (t1, t2) =>
if (isPublic) {
(t1.activityUserName === activityUserName.bind) && (t2.isPrivate === false.bind)
} else {
(t1.activityUserName === activityUserName.bind)
def getActivitiesByUser(activityUserName: String, isPublic: Boolean)(implicit context: Context): List[Activity] = {
if (!ActivityLog.exists()) {
List.empty
} else {
val list = new ListBuffer[Activity]
Using.resource(new ReversedLinesFileReader(ActivityLog, StandardCharsets.UTF_8)) { reader =>
var json: String = null
while (list.length < 50 && { json = reader.readLine(); json } != null) {
val activity = read[Activity](json)
if (activity.activityUserName == activityUserName) {
if (isPublic == false) {
list += activity
} else {
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName)
.map(_.isPrivate)
.getOrElse(true)) {
list += activity
}
}
}
}
}
.sortBy { case (t1, t2) => t1.activityId desc }
.map { case (t1, t2) => t1 }
.take(30)
.list
list.toList
}
}
def getRecentActivities()(implicit s: Session): List[Activity] =
Activities
.join(Repositories)
.on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName))
.filter { case (t1, t2) => t2.isPrivate === false.bind }
.sortBy { case (t1, t2) => t1.activityId desc }
.map { case (t1, t2) => t1 }
.take(30)
.list
def getRecentPublicActivities()(implicit context: Context): List[Activity] = {
if (!ActivityLog.exists()) {
List.empty
} else {
val list = new ListBuffer[Activity]
Using.resource(new ReversedLinesFileReader(ActivityLog, StandardCharsets.UTF_8)) { reader =>
var json: String = null
while (list.length < 50 && { json = reader.readLine(); json } != null) {
val activity = read[Activity](json)
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName)
.map(_.isPrivate)
.getOrElse(true)) {
list += activity
}
}
}
list.toList
}
}
def getRecentActivitiesByOwners(owners: Set[String])(implicit s: Session): List[Activity] =
Activities
.join(Repositories)
.on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName))
.filter { case (t1, t2) => (t2.isPrivate === false.bind) || (t2.userName inSetBind owners) }
.sortBy { case (t1, t2) => t1.activityId desc }
.map { case (t1, t2) => t1 }
.take(30)
.list
def getRecentActivitiesByOwners(owners: Set[String])(implicit context: Context): List[Activity] = {
if (!ActivityLog.exists()) {
List.empty
} else {
val list = new ListBuffer[Activity]
Using.resource(new ReversedLinesFileReader(ActivityLog, StandardCharsets.UTF_8)) { reader =>
var json: String = null
while (list.length < 50 && { json = reader.readLine(); json } != null) {
val activity = read[Activity](json)
if (owners.contains(activity.userName)) {
list += activity
} else if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName)
.map(_.isPrivate)
.getOrElse(true)) {
list += activity
}
}
}
list.toList
}
}
def recordCreateRepositoryActivity(userName: String, repositoryName: String, activityUserName: String)(
implicit s: Session
): Unit =
Activities insert Activity(
userName,
repositoryName,
activityUserName,
"create_repository",
s"[user:${activityUserName}] created [repo:${userName}/${repositoryName}]",
None,
currentDate
def recordCreateRepositoryActivity(userName: String, repositoryName: String, activityUserName: String): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"create_repository",
s"[user:${activityUserName}] created [repo:${userName}/${repositoryName}]",
None,
currentDate,
UUID.randomUUID().toString
)
)
}
def recordDeleteRepositoryActivity(userName: String, repositoryName: String, activityUserName: String): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"delete_repository",
s"[user:${activityUserName}] deleted [repo:${userName}/${repositoryName}]",
None,
currentDate,
UUID.randomUUID().toString
)
)
}
def recordTransferRepositoryActivity(
userName: String,
repositoryName: String,
oldUserName: String,
activityUserName: String
): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"transfer_repository",
s"[user:${activityUserName}] transfered [repo:${oldUserName}/${repositoryName}] to [repo:${userName}/${repositoryName}]",
None,
currentDate,
UUID.randomUUID().toString
)
)
}
def recordRenameRepositoryActivity(
userName: String,
repositoryName: String,
oldRepositoryName: String,
activityUserName: String
): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"rename_repository",
s"[user:${activityUserName}] renamed [repo:${userName}/${oldRepositoryName}] at [repo:${userName}/${repositoryName}]",
None,
currentDate,
UUID.randomUUID().toString
)
)
}
def recordCreateIssueActivity(
userName: String,
@@ -69,16 +173,20 @@ trait ActivityService {
activityUserName: String,
issueId: Int,
title: String
)(implicit s: Session): Unit =
Activities insert Activity(
userName,
repositoryName,
activityUserName,
"open_issue",
s"[user:${activityUserName}] opened issue [issue:${userName}/${repositoryName}#${issueId}]",
Some(title),
currentDate
): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"open_issue",
s"[user:${activityUserName}] opened issue [issue:${userName}/${repositoryName}#${issueId}]",
Some(title),
currentDate,
UUID.randomUUID().toString
)
)
}
def recordCloseIssueActivity(
userName: String,
@@ -86,16 +194,20 @@ trait ActivityService {
activityUserName: String,
issueId: Int,
title: String
)(implicit s: Session): Unit =
Activities insert Activity(
userName,
repositoryName,
activityUserName,
"close_issue",
s"[user:${activityUserName}] closed issue [issue:${userName}/${repositoryName}#${issueId}]",
Some(title),
currentDate
): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"close_issue",
s"[user:${activityUserName}] closed issue [issue:${userName}/${repositoryName}#${issueId}]",
Some(title),
currentDate,
UUID.randomUUID().toString
)
)
}
def recordClosePullRequestActivity(
userName: String,
@@ -103,16 +215,20 @@ trait ActivityService {
activityUserName: String,
issueId: Int,
title: String
)(implicit s: Session): Unit =
Activities insert Activity(
userName,
repositoryName,
activityUserName,
"close_issue",
s"[user:${activityUserName}] closed pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
Some(title),
currentDate
): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"close_issue",
s"[user:${activityUserName}] closed pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
Some(title),
currentDate,
UUID.randomUUID().toString
)
)
}
def recordReopenIssueActivity(
userName: String,
@@ -120,16 +236,41 @@ trait ActivityService {
activityUserName: String,
issueId: Int,
title: String
)(implicit s: Session): Unit =
Activities insert Activity(
userName,
repositoryName,
activityUserName,
"reopen_issue",
s"[user:${activityUserName}] reopened issue [issue:${userName}/${repositoryName}#${issueId}]",
Some(title),
currentDate
): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"reopen_issue",
s"[user:${activityUserName}] reopened issue [issue:${userName}/${repositoryName}#${issueId}]",
Some(title),
currentDate,
UUID.randomUUID().toString
)
)
}
def recordReopenPullRequestActivity(
userName: String,
repositoryName: String,
activityUserName: String,
issueId: Int,
title: String
): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"reopen_issue",
s"[user:${activityUserName}] reopened pull request [issue:${userName}/${repositoryName}#${issueId}]",
Some(title),
currentDate,
UUID.randomUUID().toString
)
)
}
def recordCommentIssueActivity(
userName: String,
@@ -137,16 +278,20 @@ trait ActivityService {
activityUserName: String,
issueId: Int,
comment: String
)(implicit s: Session): Unit =
Activities insert Activity(
userName,
repositoryName,
activityUserName,
"comment_issue",
s"[user:${activityUserName}] commented on issue [issue:${userName}/${repositoryName}#${issueId}]",
Some(cut(comment, 200)),
currentDate
): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"comment_issue",
s"[user:${activityUserName}] commented on issue [issue:${userName}/${repositoryName}#${issueId}]",
Some(cut(comment, 200)),
currentDate,
UUID.randomUUID().toString
)
)
}
def recordCommentPullRequestActivity(
userName: String,
@@ -154,16 +299,20 @@ trait ActivityService {
activityUserName: String,
issueId: Int,
comment: String
)(implicit s: Session): Unit =
Activities insert Activity(
userName,
repositoryName,
activityUserName,
"comment_issue",
s"[user:${activityUserName}] commented on pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
Some(cut(comment, 200)),
currentDate
): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"comment_issue",
s"[user:${activityUserName}] commented on pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
Some(cut(comment, 200)),
currentDate,
UUID.randomUUID().toString
)
)
}
def recordCommentCommitActivity(
userName: String,
@@ -171,32 +320,40 @@ trait ActivityService {
activityUserName: String,
commitId: String,
comment: String
)(implicit s: Session): Unit =
Activities insert Activity(
userName,
repositoryName,
activityUserName,
"comment_commit",
s"[user:${activityUserName}] commented on commit [commit:${userName}/${repositoryName}@${commitId}]",
Some(cut(comment, 200)),
currentDate
): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"comment_commit",
s"[user:${activityUserName}] commented on commit [commit:${userName}/${repositoryName}@${commitId}]",
Some(cut(comment, 200)),
currentDate,
UUID.randomUUID().toString
)
)
}
def recordCreateWikiPageActivity(
userName: String,
repositoryName: String,
activityUserName: String,
pageName: String
)(implicit s: Session): Unit =
Activities insert Activity(
userName,
repositoryName,
activityUserName,
"create_wiki",
s"[user:${activityUserName}] created the [repo:${userName}/${repositoryName}] wiki",
Some(pageName),
currentDate
): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"create_wiki",
s"[user:${activityUserName}] created the [repo:${userName}/${repositoryName}] wiki",
Some(pageName),
currentDate,
UUID.randomUUID().toString
)
)
}
def recordEditWikiPageActivity(
userName: String,
@@ -204,16 +361,20 @@ trait ActivityService {
activityUserName: String,
pageName: String,
commitId: String
)(implicit s: Session): Unit =
Activities insert Activity(
userName,
repositoryName,
activityUserName,
"edit_wiki",
s"[user:${activityUserName}] edited the [repo:${userName}/${repositoryName}] wiki",
Some(pageName + ":" + commitId),
currentDate
): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"edit_wiki",
s"[user:${activityUserName}] edited the [repo:${userName}/${repositoryName}] wiki",
Some(pageName + ":" + commitId),
currentDate,
UUID.randomUUID().toString
)
)
}
def recordPushActivity(
userName: String,
@@ -221,23 +382,27 @@ trait ActivityService {
activityUserName: String,
branchName: String,
commits: List[JGitUtil.CommitInfo]
)(implicit s: Session): Unit =
Activities insert Activity(
userName,
repositoryName,
activityUserName,
"push",
s"[user:${activityUserName}] pushed to [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]",
Some(
commits
.take(5)
.map { commit =>
commit.id + ":" + commit.shortMessage
}
.mkString("\n")
),
currentDate
): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"push",
s"[user:${activityUserName}] pushed to [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]",
Some(
commits
.take(5)
.map { commit =>
commit.id + ":" + commit.shortMessage
}
.mkString("\n")
),
currentDate,
UUID.randomUUID().toString
)
)
}
def recordCreateTagActivity(
userName: String,
@@ -245,16 +410,20 @@ trait ActivityService {
activityUserName: String,
tagName: String,
commits: List[JGitUtil.CommitInfo]
)(implicit s: Session): Unit =
Activities insert Activity(
userName,
repositoryName,
activityUserName,
"create_tag",
s"[user:${activityUserName}] created tag [tag:${userName}/${repositoryName}#${tagName}] at [repo:${userName}/${repositoryName}]",
None,
currentDate
): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"create_tag",
s"[user:${activityUserName}] created tag [tag:${userName}/${repositoryName}#${tagName}] at [repo:${userName}/${repositoryName}]",
None,
currentDate,
UUID.randomUUID().toString
)
)
}
def recordDeleteTagActivity(
userName: String,
@@ -262,61 +431,80 @@ trait ActivityService {
activityUserName: String,
tagName: String,
commits: List[JGitUtil.CommitInfo]
)(implicit s: Session): Unit =
Activities insert Activity(
userName,
repositoryName,
activityUserName,
"delete_tag",
s"[user:${activityUserName}] deleted tag ${tagName} at [repo:${userName}/${repositoryName}]",
None,
currentDate
): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"delete_tag",
s"[user:${activityUserName}] deleted tag ${tagName} at [repo:${userName}/${repositoryName}]",
None,
currentDate,
UUID.randomUUID().toString
)
)
}
def recordCreateBranchActivity(
userName: String,
repositoryName: String,
activityUserName: String,
branchName: String
)(implicit s: Session): Unit =
Activities insert Activity(
userName,
repositoryName,
activityUserName,
"create_branch",
s"[user:${activityUserName}] created branch [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]",
None,
currentDate
): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"create_branch",
s"[user:${activityUserName}] created branch [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]",
None,
currentDate,
UUID.randomUUID().toString
)
)
}
def recordDeleteBranchActivity(
userName: String,
repositoryName: String,
activityUserName: String,
branchName: String
)(implicit s: Session): Unit =
Activities insert Activity(
userName,
repositoryName,
activityUserName,
"delete_branch",
s"[user:${activityUserName}] deleted branch ${branchName} at [repo:${userName}/${repositoryName}]",
None,
currentDate
): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"delete_branch",
s"[user:${activityUserName}] deleted branch ${branchName} at [repo:${userName}/${repositoryName}]",
None,
currentDate,
UUID.randomUUID().toString
)
)
}
def recordForkActivity(userName: String, repositoryName: String, activityUserName: String, forkedUserName: String)(
implicit s: Session
): Unit =
Activities insert Activity(
userName,
repositoryName,
activityUserName,
"fork",
s"[user:${activityUserName}] forked [repo:${userName}/${repositoryName}] to [repo:${forkedUserName}/${repositoryName}]",
None,
currentDate
def recordForkActivity(
userName: String,
repositoryName: String,
activityUserName: String,
forkedUserName: String
): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"fork",
s"[user:${activityUserName}] forked [repo:${userName}/${repositoryName}] to [repo:${forkedUserName}/${repositoryName}]",
None,
currentDate,
UUID.randomUUID().toString
)
)
}
def recordPullRequestActivity(
userName: String,
@@ -324,16 +512,20 @@ trait ActivityService {
activityUserName: String,
issueId: Int,
title: String
)(implicit s: Session): Unit =
Activities insert Activity(
userName,
repositoryName,
activityUserName,
"open_pullreq",
s"[user:${activityUserName}] opened pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
Some(title),
currentDate
): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"open_pullreq",
s"[user:${activityUserName}] opened pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
Some(title),
currentDate,
UUID.randomUUID().toString
)
)
}
def recordMergeActivity(
userName: String,
@@ -341,16 +533,20 @@ trait ActivityService {
activityUserName: String,
issueId: Int,
message: String
)(implicit s: Session): Unit =
Activities insert Activity(
userName,
repositoryName,
activityUserName,
"merge_pullreq",
s"[user:${activityUserName}] merged pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
Some(message),
currentDate
): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"merge_pullreq",
s"[user:${activityUserName}] merged pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
Some(message),
currentDate,
UUID.randomUUID().toString
)
)
}
def recordReleaseActivity(
userName: String,
@@ -358,16 +554,20 @@ trait ActivityService {
activityUserName: String,
releaseName: String,
tagName: String
)(implicit s: Session): Unit =
Activities insert Activity(
userName,
repositoryName,
activityUserName,
"release",
s"[user:${activityUserName}] released [release:${userName}/${repositoryName}/${tagName}:${releaseName}] at [repo:${userName}/${repositoryName}]",
None,
currentDate
): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"release",
s"[user:${activityUserName}] released [release:${userName}/${repositoryName}/${tagName}:${releaseName}] at [repo:${userName}/${repositoryName}]",
None,
currentDate,
UUID.randomUUID().toString
)
)
}
private def cut(value: String, length: Int): String =
if (value.length > length) value.substring(0, length) + "..." else value

View File

@@ -39,7 +39,10 @@ trait HandleCommentService {
))
case "reopen" if (issue.closed) =>
false ->
(Some("reopen") -> Some(recordReopenIssueActivity _))
(Some("reopen") -> Some(
if (issue.isPullRequest) recordReopenPullRequestActivity _
else recordReopenIssueActivity _
))
}
.map {
case (closed, t) =>

View File

@@ -203,7 +203,7 @@ trait IssuesService {
}
/**
* Returns the search result against issues.
* Returns the search result against issues.
*
* @param condition the search condition
* @param pullRequest if true then returns only pull requests, false then returns only issues.
@@ -910,13 +910,9 @@ object IssuesService {
param(request, "groups").map(_.split(",").toSet).getOrElse(Set.empty)
)
def page(request: HttpServletRequest) =
try {
val i = param(request, "page").getOrElse("1").toInt
if (i <= 0) 1 else i
} catch {
case e: NumberFormatException => 1
}
def page(request: HttpServletRequest) = {
PaginationHelper.page(param(request, "page"))
}
}
case class CommitStatusInfo(

View File

@@ -0,0 +1,15 @@
package gitbucket.core.service
import scala.util.Try
object PaginationHelper {
def page(page: Option[String]) = {
page
.flatMap(pageStr => Try(pageStr.toInt).toOption)
.map(Math.max(1, _)) // remove negative pages
.getOrElse(1)
}
}

View File

@@ -1,10 +1,11 @@
package gitbucket.core.service
import gitbucket.core.controller.Context
import gitbucket.core.model.{Account, ReleaseTag, ReleaseAsset}
import gitbucket.core.model.{Account, ReleaseAsset, ReleaseTag}
import gitbucket.core.model.Profile.profile.blockingApi._
import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.dateColumnType
import gitbucket.core.util.JGitUtil
trait ReleaseService {
self: AccountService with RepositoryService =>
@@ -35,10 +36,9 @@ trait ReleaseService {
ReleaseAssets.filter(x => x.byTag(owner, repository, tag)).list
}
def getReleaseAssetsMap(owner: String, repository: String)(
def getReleaseAssetsMap(owner: String, repository: String, releases: Seq[ReleaseTag])(
implicit s: Session
): Map[ReleaseTag, Seq[ReleaseAsset]] = {
val releases = getReleases(owner, repository)
releases.map(rel => (rel -> getReleaseAssets(owner, repository, rel.tag))).toMap
}
@@ -76,20 +76,18 @@ trait ReleaseService {
ReleaseTags.filter(x => x.byRepository(owner, repository)).sortBy(x => x.updatedDate).list
}
def getRelease(owner: String, repository: String, tag: String)(implicit s: Session): Option[ReleaseTag] = {
//Releases filter (_.byPrimaryKey(owner, repository, releaseId)) firstOption
ReleaseTags filter (_.byTag(owner, repository, tag)) firstOption
def getReleases(owner: String, repository: String, tags: Seq[JGitUtil.TagInfo])(
implicit s: Session
): Seq[ReleaseTag] = {
ReleaseTags
.filter(x => x.byRepository(owner, repository))
.filter(x => x.tag inSetBind tags.map(_.name))
.sortBy(x => x.updatedDate)
.list
}
def getRelease(owner: String, repository: String, tag: String)(implicit s: Session): Option[ReleaseTag] = {
ReleaseTags.filter(_.byTag(owner, repository, tag)).firstOption
}
// def getReleaseByTag(owner: String, repository: String, tag: String)(implicit s: Session): Option[Release] = {
// Releases filter (_.byTag(owner, repository, tag)) firstOption
// }
//
// def getRelease(owner: String, repository: String, releaseId: String)(implicit s: Session): Option[Release] = {
// if (isInteger(releaseId))
// getRelease(owner, repository, releaseId.toInt)
// else None
// }
def updateRelease(owner: String, repository: String, tag: String, title: String, content: Option[String])(
implicit s: Session
@@ -107,3 +105,9 @@ trait ReleaseService {
ReleaseTags filter (_.byPrimaryKey(owner, repository, tag)) delete
}
}
object ReleaseService {
val ReleaseLimit = 10
}

View File

@@ -1,6 +1,5 @@
package gitbucket.core.service
import gitbucket.core.api.JsonFormat
import gitbucket.core.controller.Context
import gitbucket.core.util._
import gitbucket.core.util.SyntaxSugars._
@@ -9,14 +8,11 @@ import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile.blockingApi._
import gitbucket.core.model.Profile.dateColumnType
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service.WebHookService.WebHookPushPayload
import gitbucket.core.util.Directory.{getRepositoryDir, getRepositoryFilesDir, getTemporaryDir, getWikiRepositoryDir}
import gitbucket.core.util.JGitUtil.{CommitInfo, FileInfo}
import gitbucket.core.util.JGitUtil.FileInfo
import org.apache.commons.io.FileUtils
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder}
import org.eclipse.jgit.lib.{Repository => _, _}
import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack}
import scala.util.Using
trait RepositoryService {
@@ -119,15 +115,6 @@ trait RepositoryService {
}
.update(newUserName, newRepositoryName)
// Updates activity fk before deleting repository because activity is sorted by activityId
// and it can't be changed by deleting-and-inserting record.
Activities.filter(_.byRepository(oldUserName, oldRepositoryName)).list.foreach { activity =>
Activities
.filter(_.activityId === activity.activityId.bind)
.map(x => (x.userName, x.repositoryName))
.update(newUserName, newRepositoryName)
}
deleteRepositoryOnModel(oldUserName, oldRepositoryName)
RepositoryWebHooks.insertAll(
@@ -213,50 +200,6 @@ trait RepositoryService {
collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*
)
// Update activity messages
Activities
.filter { t =>
(t.message like s"%:${oldUserName}/${oldRepositoryName}]%") ||
(t.message like s"%:${oldUserName}/${oldRepositoryName}#%") ||
(t.message like s"%:${oldUserName}/${oldRepositoryName}@%")
}
.map { t =>
t.activityId -> t.message
}
.list
.foreach {
case (activityId, message) =>
Activities
.filter(_.activityId === activityId.bind)
.map(_.message)
.update(
message
.replace(
s"[repo:${oldUserName}/${oldRepositoryName}]",
s"[repo:${newUserName}/${newRepositoryName}]"
)
.replace(
s"[branch:${oldUserName}/${oldRepositoryName}#",
s"[branch:${newUserName}/${newRepositoryName}#"
)
.replace(
s"[tag:${oldUserName}/${oldRepositoryName}#",
s"[tag:${newUserName}/${newRepositoryName}#"
)
.replace(
s"[pullreq:${oldUserName}/${oldRepositoryName}#",
s"[pullreq:${newUserName}/${newRepositoryName}#"
)
.replace(
s"[issue:${oldUserName}/${oldRepositoryName}#",
s"[issue:${newUserName}/${newRepositoryName}#"
)
.replace(
s"[commit:${oldUserName}/${oldRepositoryName}@",
s"[commit:${newUserName}/${newRepositoryName}@"
)
)
}
// Move git repository
defining(getRepositoryDir(oldUserName, oldRepositoryName)) { dir =>
if (dir.isDirectory) {
@@ -304,7 +247,7 @@ trait RepositoryService {
}
private def deleteRepositoryOnModel(userName: String, repositoryName: String)(implicit s: Session): Unit = {
Activities.filter(_.byRepository(userName, repositoryName)).delete
// Activities.filter(_.byRepository(userName, repositoryName)).delete
Collaborators.filter(_.byRepository(userName, repositoryName)).delete
CommitComments.filter(_.byRepository(userName, repositoryName)).delete
IssueLabels.filter(_.byRepository(userName, repositoryName)).delete
@@ -459,6 +402,7 @@ trait RepositoryService {
/**
* Returns the list of visible repositories for the specified user.
* If repositoryUserName is given then filters results by repository owner.
* This function is for plugin compatibility.
*
* @param loginAccount the logged in account
* @param repositoryUserName the repository owner (if None then returns all repositories which are visible for logged in user)
@@ -470,23 +414,42 @@ trait RepositoryService {
loginAccount: Option[Account],
repositoryUserName: Option[String] = None,
withoutPhysicalInfo: Boolean = false
)(implicit s: Session): List[RepositoryInfo] =
getVisibleRepositories(loginAccount, repositoryUserName, withoutPhysicalInfo, false)
/**
* Returns the list of visible repositories for the specified user.
* If repositoryUserName is given then filters results by repository owner.
*
* @param loginAccount the logged in account
* @param repositoryUserName the repository owner (if None then returns all repositories which are visible for logged in user)
* @param withoutPhysicalInfo if true then the result does not include physical repository information such as commit count,
* branches and tags
* @param limit if true then result will include only repositories associated with the login account.
* @return the repository information which is sorted in descending order of lastActivityDate.
*/
def getVisibleRepositories(
loginAccount: Option[Account],
repositoryUserName: Option[String],
withoutPhysicalInfo: Boolean,
limit: Boolean
)(implicit s: Session): List[RepositoryInfo] = {
(loginAccount match {
// for Administrators
case Some(x) if (x.isAdmin) =>
case Some(x) if (x.isAdmin && !limit) =>
Repositories
.join(Accounts)
.on(_.userName === _.userName)
.filter { case (t1, t2) => t2.removed === false.bind }
.map { case (t1, t2) => t1 }
// for Normal Users
case Some(x) if (!x.isAdmin) =>
case Some(x) if (!x.isAdmin || limit) =>
Repositories
.join(Accounts)
.on(_.userName === _.userName)
.filter {
case (t1, t2) =>
(t2.removed === false.bind) && ((t1.isPrivate === false.bind) || (t1.userName === x.userName) ||
(t2.removed === false.bind) && ((t1.isPrivate === false.bind && !limit.bind) || (t1.userName === x.userName) ||
(t1.userName in GroupMembers.filter(_.userName === x.userName.bind).map(_.groupName)) ||
(Collaborators.filter { t3 =>
t3.byRepository(t1.userName, t1.repositoryName) &&

View File

@@ -1,9 +1,11 @@
package gitbucket.core.service
import gitbucket.core.model.{Session, Issue, Account}
import gitbucket.core.model.{Account, Issue, Repository, Session}
import gitbucket.core.util.Implicits
import gitbucket.core.controller.Context
import Implicits.request2Session
import gitbucket.core.model.Profile.{Accounts, Repositories}
import gitbucket.core.model.Profile.profile.blockingApi._
/**
* This service is used for a view helper mainly.
@@ -23,21 +25,41 @@ trait RequestCache
private implicit def context2Session(implicit context: Context): Session =
request2Session(context.request)
def getIssue(userName: String, repositoryName: String, issueId: String)(implicit context: Context): Option[Issue] = {
def getIssueFromCache(userName: String, repositoryName: String, issueId: String)(
implicit context: Context
): Option[Issue] = {
context.cache(s"issue.${userName}/${repositoryName}#${issueId}") {
super.getIssue(userName, repositoryName, issueId)
}
}
def getAccountByUserName(userName: String)(implicit context: Context): Option[Account] = {
def getAccountByUserNameFromCache(userName: String)(implicit context: Context): Option[Account] = {
context.cache(s"account.${userName}") {
super.getAccountByUserName(userName)
}
}
def getAccountByMailAddress(mailAddress: String)(implicit context: Context): Option[Account] = {
def getAccountByMailAddressFromCache(mailAddress: String)(implicit context: Context): Option[Account] = {
context.cache(s"account.${mailAddress}") {
super.getAccountByMailAddress(mailAddress)
}
}
def getRepositoryInfoFromCache(userName: String, repositoryName: String)(
implicit context: Context
): Option[Repository] = {
context.cache(s"repository.${userName}/${repositoryName}") {
Repositories
.join(Accounts)
.on(_.userName === _.userName)
.filter {
case (t1, t2) =>
t1.byRepository(userName, repositoryName) && t2.removed === false.bind
}
.map {
case (t1, t2) => t1
}
.firstOption
}
}
}

View File

@@ -21,9 +21,15 @@ trait SystemSettingsService {
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
props.setProperty(AllowAnonymousAccess, settings.allowAnonymousAccess.toString)
props.setProperty(IsCreateRepoOptionPublic, settings.isCreateRepoOptionPublic.toString)
props.setProperty(RepositoryOperationCreate, settings.repositoryOperation.create.toString)
props.setProperty(RepositoryOperationDelete, settings.repositoryOperation.delete.toString)
props.setProperty(RepositoryOperationRename, settings.repositoryOperation.rename.toString)
props.setProperty(RepositoryOperationTransfer, settings.repositoryOperation.transfer.toString)
props.setProperty(RepositoryOperationFork, settings.repositoryOperation.fork.toString)
props.setProperty(Gravatar, settings.gravatar.toString)
props.setProperty(Notification, settings.notification.toString)
settings.activityLogLimit.foreach(x => props.setProperty(ActivityLogLimit, x.toString))
props.setProperty(LimitVisibleRepositories, settings.limitVisibleRepositories.toString)
props.setProperty(SshEnabled, settings.ssh.enabled.toString)
settings.ssh.sshHost.foreach(x => props.setProperty(SshHost, x.trim))
settings.ssh.sshPort.foreach(x => props.setProperty(SshPort, x.toString))
@@ -69,9 +75,14 @@ trait SystemSettingsService {
}
}
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)
Using.resource(new java.io.FileOutputStream(GitBucketConf)) { out =>
props.store(out, null)
@@ -92,15 +103,27 @@ trait SystemSettingsService {
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),
getOptionValue[Int](props, ActivityLogLimit, None),
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
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(
@@ -148,8 +171,15 @@ trait SystemSettingsService {
None
},
getValue(props, SkinName, "skin-blue"),
getOptionValue(props, UserDefinedCss, None),
getValue(props, ShowMailAddress, false),
WebHook(getValue(props, WebHookBlockPrivateAddress, false), getSeqValue(props, WebHookWhitelist, ""))
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)
)
)
}
}
@@ -167,9 +197,11 @@ object SystemSettingsService {
allowAccountRegistration: Boolean,
allowAnonymousAccess: Boolean,
isCreateRepoOptionPublic: Boolean,
repositoryOperation: RepositoryOperation,
gravatar: Boolean,
notification: Boolean,
activityLogLimit: Option[Int],
limitVisibleRepositories: Boolean,
ssh: Ssh,
useSMTP: Boolean,
smtp: Option[Smtp],
@@ -178,8 +210,10 @@ object SystemSettingsService {
oidcAuthentication: Boolean,
oidc: Option[OIDC],
skinName: String,
userDefinedCss: Option[String],
showMailAddress: Boolean,
webHook: WebHook
webHook: WebHook,
upload: Upload
) {
def baseUrl(request: HttpServletRequest): String =
@@ -198,12 +232,21 @@ object SystemSettingsService {
.fold(base)(_ + base.dropWhile(_ != ':'))
}
def sshAddress: Option[SshAddress] = ssh.sshHost.collect {
case host if ssh.enabled =>
SshAddress(host, ssh.sshPort.getOrElse(DefaultSshPort), "git")
}
def sshAddress: Option[SshAddress] =
ssh.sshHost.collect {
case host if ssh.enabled =>
SshAddress(host, ssh.sshPort.getOrElse(DefaultSshPort), "git")
}
}
case class RepositoryOperation(
create: Boolean,
delete: Boolean,
rename: Boolean,
transfer: Boolean,
fork: Boolean
)
case class Ssh(
enabled: Boolean,
sshHost: Option[String],
@@ -251,13 +294,15 @@ object SystemSettingsService {
host: String,
port: Int,
user: Option[String],
password: Option[String],
password: Option[String]
)
case class SshAddress(host: String, port: Int, genericUser: String)
case class WebHook(blockPrivateAddress: Boolean, whitelist: Seq[String])
case class Upload(maxFileSize: Long, timeout: Long, largeMaxFileSize: Long, largeTimeout: Long)
val DefaultSshPort = 29418
val DefaultSmtpPort = 25
val DefaultLdapPort = 389
@@ -267,9 +312,15 @@ object SystemSettingsService {
private val AllowAccountRegistration = "allow_account_registration"
private val AllowAnonymousAccess = "allow_anonymous_access"
private val IsCreateRepoOptionPublic = "is_create_repository_option_public"
private val RepositoryOperationCreate = "repository_operation_create"
private val RepositoryOperationDelete = "repository_operation_delete"
private val RepositoryOperationRename = "repository_operation_rename"
private val RepositoryOperationTransfer = "repository_operation_transfer"
private val RepositoryOperationFork = "repository_operation_fork"
private val Gravatar = "gravatar"
private val Notification = "notification"
private val ActivityLogLimit = "activity_log_limit"
private val LimitVisibleRepositories = "limitVisibleRepositories"
private val SshEnabled = "ssh"
private val SshHost = "ssh.host"
private val SshPort = "ssh.port"
@@ -301,14 +352,14 @@ object SystemSettingsService {
private val OidcClientSecret = "oidc.client_secret"
private val OidcJwsAlgorithm = "oidc.jws_algorithm"
private val SkinName = "skinName"
private val UserDefinedCss = "userDefinedCss"
private val ShowMailAddress = "showMailAddress"
private val PluginNetworkInstall = "plugin.networkInstall"
private val PluginProxyHost = "plugin.proxy.host"
private val PluginProxyPort = "plugin.proxy.port"
private val PluginProxyUser = "plugin.proxy.user"
private val PluginProxyPassword = "plugin.proxy.password"
private val WebHookBlockPrivateAddress = "webhook.block_private_address"
private val WebHookWhitelist = "webhook.whitelist"
private val UploadMaxFileSize = "upload.maxFileSize"
private val UploadTimeout = "upload.timeout"
private val UploadLargeMaxFileSize = "upload.largeMaxFileSize"
private val UploadLargeTimeout = "upload.largeTimeout"
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = {
getConfigValue(key).getOrElse {

View File

@@ -24,7 +24,7 @@ class ApiAuthenticationFilter extends Filter with AccessTokenService with Accoun
val response = res.asInstanceOf[HttpServletResponse]
Option(request.getHeader("Authorization"))
.map {
case auth if auth.startsWith("token ") =>
case auth if auth.toLowerCase().startsWith("token ") =>
AccessTokenService.getAccountByAccessToken(auth.substring(6).trim).toRight(())
case auth if auth.startsWith("Basic ") => doBasicAuth(auth, loadSystemSettings(), request).toRight(())
case _ => Left(())

View File

@@ -240,7 +240,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
with WebHookPullRequestService
with WebHookPullRequestReviewCommentService
with CommitsService
with SystemSettingsService {
with SystemSettingsService
with RequestCache {
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
private var existIds: Seq[String] = Nil

View File

@@ -2,11 +2,10 @@ package gitbucket.core.servlet
import java.io.{File, FileOutputStream}
import akka.event.Logging
import com.typesafe.config.ConfigFactory
import gitbucket.core.GitBucketCoreModule
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service.{ActivityService, SystemSettingsService}
import gitbucket.core.service.SystemSettingsService
import gitbucket.core.util.DatabaseConfig
import gitbucket.core.util.Directory._
import gitbucket.core.util.JDBCUtil._
@@ -21,8 +20,6 @@ import javax.servlet.{ServletContextEvent, ServletContextListener}
import org.apache.commons.io.{FileUtils, IOUtils}
import org.slf4j.LoggerFactory
import akka.actor.{Actor, ActorSystem, Props}
import com.typesafe.akka.extension.quartz.QuartzSchedulerExtension
import scala.jdk.CollectionConverters._
import scala.util.Using
@@ -35,23 +32,23 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
private val logger = LoggerFactory.getLogger(classOf[InitializeListener])
// ActorSystem for Quartz scheduler
private val system = ActorSystem(
"job",
ConfigFactory.parseString("""
|akka {
| daemonic = on
| coordinated-shutdown.run-by-jvm-shutdown-hook = off
| quartz {
| schedules {
| Daily {
| expression = "0 0 0 * * ?"
| }
| }
| }
|}
""".stripMargin)
)
// // ActorSystem for Quartz scheduler
// private val system = ActorSystem(
// "job",
// ConfigFactory.parseString("""
// |akka {
// | daemonic = on
// | coordinated-shutdown.run-by-jvm-shutdown-hook = off
// | quartz {
// | schedules {
// | Daily {
// | expression = "0 0 0 * * ?"
// | }
// | }
// | }
// |}
// """.stripMargin)
// )
override def contextInitialized(event: ServletContextEvent): Unit = {
val dataDir = event.getServletContext.getInitParameter("gitbucket.home")
@@ -95,10 +92,10 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
PluginRegistry.initialize(event.getServletContext, loadSystemSettings(), conn)
}
// Start Quartz scheduler
val scheduler = QuartzSchedulerExtension(system)
scheduler.schedule("Daily", system.actorOf(Props[DeleteOldActivityActor]), "DeleteOldActivity")
// // Start Quartz scheduler
// val scheduler = QuartzSchedulerExtension(system)
//
// scheduler.schedule("Daily", system.actorOf(Props[DeleteOldActivityActor]), "DeleteOldActivity")
}
private def checkVersion(manager: JDBCVersionManager, conn: java.sql.Connection): Unit = {
@@ -172,8 +169,8 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
}
override def contextDestroyed(event: ServletContextEvent): Unit = {
// Shutdown Quartz scheduler
system.terminate()
// // Shutdown Quartz scheduler
// system.terminate()
// Shutdown plugins
PluginRegistry.shutdown(event.getServletContext, loadSystemSettings())
// Close datasource
@@ -181,21 +178,3 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
}
}
class DeleteOldActivityActor extends Actor with SystemSettingsService with ActivityService {
private val logger = Logging(context.system, this)
def receive = {
case s: String => {
loadSystemSettings().activityLogLimit.foreach { limit =>
if (limit > 0) {
Database() withTransaction { implicit session =>
val rows = deleteOldActivities(limit)
logger.info(s"Deleted ${rows} activity logs")
}
}
}
}
}
}

View File

@@ -7,7 +7,7 @@ import Directory._
import ConfigUtil._
import com.github.takezoe.slick.blocking.{BlockingH2Driver, BlockingJdbcProfile, BlockingMySQLDriver}
import liquibase.database.AbstractJdbcDatabase
import liquibase.database.core.{H2Database, MySQLDatabase, PostgresDatabase}
import liquibase.database.core.{H2Database, MariaDBDatabase, MySQLDatabase, PostgresDatabase}
import org.apache.commons.io.FileUtils
import scala.reflect.ClassTag
@@ -78,6 +78,8 @@ object DatabaseType {
H2
} else if (url.startsWith("jdbc:mysql:")) {
MySQL
} else if (url.startsWith("jdbc:mariadb:")) {
MariaDb
} else if (url.startsWith("jdbc:postgresql:")) {
PostgreSQL
} else {
@@ -97,6 +99,12 @@ object DatabaseType {
val liquiDriver = new MySQLDatabase()
}
object MariaDb extends DatabaseType {
val jdbcDriver = "org.mariadb.jdbc.Driver"
val slickDriver = BlockingMySQLDriver
val liquiDriver = new MariaDBDatabase()
}
object PostgreSQL extends DatabaseType {
val jdbcDriver = "org.postgresql.Driver2"
val slickDriver = BlockingPostgresDriver

View File

@@ -29,6 +29,8 @@ object Directory {
val GitBucketConf = new File(GitBucketHome, "gitbucket.conf")
val ActivityLog = new File(GitBucketHome, "activity.log")
val RepositoryHome = s"${GitBucketHome}/repositories"
val DatabaseHome = s"${GitBucketHome}/data"
@@ -62,7 +64,7 @@ object Directory {
new File(getRepositoryFilesDir(owner, repository), "releases")
/**
* Directory for files which are attached to issue.
* Directory for Git LFS files.
*/
def getLfsDir(owner: String, repository: String): File =
new File(getRepositoryFilesDir(owner, repository), "lfs")

View File

@@ -92,12 +92,4 @@ object FileUtil {
name
}
lazy val MaxFileSize: Long = {
ConfigUtil.getConfigValue[Long]("gitbucket.maxFileSize").getOrElse(3 * 1024 * 1024)
}
lazy val UploadTimeout: Long = {
ConfigUtil.getConfigValue[Long]("gitbucket.UploadTimeout").getOrElse(3 * 10000)
}
}

View File

@@ -290,6 +290,11 @@ object JGitUtil {
.entryCapacity(10000)
.build()
private val objectCommitCache = new Cache2kBuilder[ObjectId, RevCommit]() {}
.name("object-commit")
.entryCapacity(10000)
.build()
def removeCache(git: Git): Unit = {
val dir = git.getRepository.getDirectory
val keyPrefix = dir.getAbsolutePath + "@"
@@ -368,9 +373,16 @@ object JGitUtil {
* @param revision the branch name or commit id
* @param path the directory path (optional)
* @param baseUrl the base url of GitBucket instance. This parameter is used to generate links of submodules (optional)
* @return HTML of the file list
* @param commitCount the number of commit of this repository (optional). If this number is greater than threshold, the commit info is cached in memory.
* @return The list of files in the specified directory. If the number of files are greater than threshold, the returned file list won't include the commit info.
*/
def getFileList(git: Git, revision: String, path: String = ".", baseUrl: Option[String] = None): List[FileInfo] = {
def getFileList(
git: Git,
revision: String,
path: String = ".",
baseUrl: Option[String] = None,
commitCount: Int = 0
): List[FileInfo] = {
Using.resource(new RevWalk(git.getRepository)) { revWalk =>
val objectId = git.getRepository.resolve(revision)
if (objectId == null) return Nil
@@ -388,104 +400,85 @@ object JGitUtil {
Using.resource(treeWalk)(f)
}
}
@tailrec
def simplifyPath(
tuple: (ObjectId, FileMode, String, String, Option[String], RevCommit)
): (ObjectId, FileMode, String, String, Option[String], RevCommit) = tuple match {
case (oid, FileMode.TREE, name, path, _, commit) =>
(Using.resource(new TreeWalk(git.getRepository)) { walk =>
walk.addTree(oid)
// single tree child, or None
if (walk.next() && walk.getFileMode(0) == FileMode.TREE) {
Some(
(
walk.getObjectId(0),
walk.getFileMode(0),
name + "/" + walk.getNameString,
path + "/" + walk.getNameString,
None,
commit
)
).filterNot(_ => walk.next())
} else {
None
tuple: (ObjectId, FileMode, String, String, Option[String], Option[RevCommit])
): (ObjectId, FileMode, String, String, Option[String], Option[RevCommit]) =
tuple match {
case (oid, FileMode.TREE, name, path, _, commit) =>
(Using.resource(new TreeWalk(git.getRepository)) { walk =>
walk.addTree(oid)
// single tree child, or None
if (walk.next() && walk.getFileMode(0) == FileMode.TREE) {
Some(
(
walk.getObjectId(0),
walk.getFileMode(0),
name + "/" + walk.getNameString,
path + "/" + walk.getNameString,
None,
commit
)
).filterNot(_ => walk.next())
} else {
None
}
}) match {
case Some(child) => simplifyPath(child)
case _ => tuple
}
}) match {
case Some(child) => simplifyPath(child)
case _ => tuple
}
case _ => tuple
}
case _ => tuple
}
def tupleAdd(tuple: (ObjectId, FileMode, String, String, Option[String]), rev: RevCommit) = tuple match {
case (oid, fmode, name, path, opt) => (oid, fmode, name, path, opt, rev)
}
@tailrec
def findLastCommits(
result: List[(ObjectId, FileMode, String, String, Option[String], RevCommit)],
restList: List[((ObjectId, FileMode, String, String, Option[String]), Map[RevCommit, RevCommit])],
revIterator: java.util.Iterator[RevCommit]
): List[(ObjectId, FileMode, String, String, Option[String], RevCommit)] = {
if (restList.isEmpty) {
result
} else if (!revIterator.hasNext) { // maybe, revCommit has only 1 log. other case, restList be empty
result ++ restList.map { case (tuple, map) => tupleAdd(tuple, map.values.headOption.getOrElse(revCommit)) }
} else {
val newCommit = revIterator.next
val (thisTimeChecks, skips) = restList.partition {
case (tuple, parentsMap) => parentsMap.contains(newCommit)
}
if (thisTimeChecks.isEmpty) {
findLastCommits(result, restList, revIterator)
} else {
var nextRest = skips
var nextResult = result
// Map[(name, oid), (tuple, parentsMap)]
val rest = scala.collection.mutable.Map(thisTimeChecks.map { t =>
(t._1._3 -> t._1._1) -> t
}: _*)
lazy val newParentsMap = newCommit.getParents.map(_ -> newCommit).toMap
useTreeWalk(newCommit) { walk =>
while (walk.next) {
rest.remove(walk.getNameString -> walk.getObjectId(0)).foreach {
case (tuple, _) =>
if (newParentsMap.isEmpty) {
nextResult +:= tupleAdd(tuple, newCommit)
} else {
nextRest +:= tuple -> newParentsMap
}
}
def appendLastCommits(
fileList: List[(ObjectId, FileMode, String, String, Option[String])]
): List[(ObjectId, FileMode, String, String, Option[String], Option[RevCommit])] = {
fileList.map {
case (id, mode, name, path, opt) =>
// Don't attempt to get the last commit if the number of files is very large.
if (fileList.size >= 100) {
(id, mode, name, path, opt, None)
} else if (commitCount < 10000) {
val i = git
(id, mode, name, path, opt, Some(getCommit(path)))
} else {
val cached = objectCommitCache.getEntry(id)
if (cached == null) {
val commit = getCommit(path)
objectCommitCache.put(id, commit)
(id, mode, name, path, opt, Some(commit))
} else {
(id, mode, name, path, opt, Some(cached.getValue))
}
}
rest.values.foreach {
case (tuple, parentsMap) =>
val restParentsMap = parentsMap - newCommit
if (restParentsMap.isEmpty) {
nextResult +:= tupleAdd(tuple, parentsMap(newCommit))
} else {
nextRest +:= tuple -> restParentsMap
}
}
findLastCommits(nextResult, nextRest, revIterator)
}
}
}
def getCommit(path: String): RevCommit = {
git
.log()
.addPath(path)
.add(revCommit)
.setMaxCount(1)
.call()
.iterator()
.next()
}
var fileList: List[(ObjectId, FileMode, String, String, Option[String])] = Nil
useTreeWalk(revCommit) { treeWalk =>
while (treeWalk.next()) {
val linkUrl = if (treeWalk.getFileMode(0) == FileMode.GITLINK) {
getSubmodules(git, revCommit.getTree, baseUrl).find(_.path == treeWalk.getPathString).map(_.viewerUrl)
} else None
fileList +:= (treeWalk.getObjectId(0), treeWalk.getFileMode(0), treeWalk.getNameString, treeWalk.getPathString, linkUrl)
fileList +:= (treeWalk.getObjectId(0), treeWalk.getFileMode(
0
), treeWalk.getNameString, treeWalk.getPathString, linkUrl)
}
}
revWalk.markStart(revCommit)
val it = revWalk.iterator
val lastCommit = it.next
val nextParentsMap = Option(lastCommit).map(_.getParents.map(_ -> lastCommit).toMap).getOrElse(Map())
findLastCommits(List.empty, fileList.map(a => a -> nextParentsMap), it)
appendLastCommits(fileList)
.map(simplifyPath)
.map {
case (objectId, fileMode, name, path, linkUrl, commit) =>
@@ -494,11 +487,14 @@ object JGitUtil {
fileMode == FileMode.TREE || fileMode == FileMode.GITLINK,
name,
path,
getSummaryMessage(commit.getFullMessage, commit.getShortMessage),
commit.getName,
commit.getAuthorIdent.getWhen,
commit.getAuthorIdent.getName,
commit.getAuthorIdent.getEmailAddress,
getSummaryMessage(
commit.map(_.getFullMessage).getOrElse(""),
commit.map(_.getShortMessage).getOrElse("")
),
commit.map(_.getName).getOrElse(""),
commit.map(_.getAuthorIdent.getWhen).orNull,
commit.map(_.getAuthorIdent.getName).getOrElse(""),
commit.map(_.getAuthorIdent.getEmailAddress).getOrElse(""),
linkUrl
)
}
@@ -1004,11 +1000,12 @@ object JGitUtil {
*/
def getContentFromPath(git: Git, revTree: RevTree, path: String, fetchLargeFile: Boolean): Option[Array[Byte]] = {
@scala.annotation.tailrec
def getPathObjectId(path: String, walk: TreeWalk): Option[ObjectId] = walk.next match {
case true if (walk.getPathString == path) => Some(walk.getObjectId(0))
case true => getPathObjectId(path, walk)
case false => None
}
def getPathObjectId(path: String, walk: TreeWalk): Option[ObjectId] =
walk.next match {
case true if (walk.getPathString == path) => Some(walk.getObjectId(0))
case true => getPathObjectId(path, walk)
case false => None
}
Using.resource(new TreeWalk(git.getRepository)) { treeWalk =>
treeWalk.addTree(revTree)

View File

@@ -127,8 +127,14 @@ object LDAPUtil {
private def getSslProvider(): Provider = {
val cachedInstance = provider.get()
if (cachedInstance == null) {
val newInstance = Class
.forName("com.sun.net.ssl.internal.ssl.Provider")
val cls = try {
Class.forName("com.sun.net.ssl.internal.ssl.Provider")
} catch {
case e: ClassNotFoundException =>
Class.forName("com.ibm.jsse.IBMJSSEProvider")
case e: Throwable => throw e
}
val newInstance = cls
.getDeclaredConstructor()
.newInstance()
.asInstanceOf[Provider]
@@ -149,7 +155,7 @@ object LDAPUtil {
keystore: String,
error: String
)(f: LDAPConnection => Either[String, A]): Either[String, A] = {
if (tls) {
if (tls || ssl) {
// Dynamically set Sun as the security provider
Security.addProvider(getSslProvider())

View File

@@ -19,21 +19,6 @@ trait Validations {
}
}
/**
* Constraint for the password.
*/
val password: Constraint = new Constraint() {
lazy val validatePassword = ConfigUtil.getConfigValue[Boolean]("gitbucket.validate.password").getOrElse(true)
override def validate(name: String, value: String, messages: Messages): Option[String] = {
if (validatePassword == true && !value.matches("[a-zA-Z0-9\\-_.]+")) {
Some(s"${name} contains invalid character.")
} else {
None
}
}
}
/**
* Constraint for the repository identifier.
*/

View File

@@ -17,7 +17,7 @@ trait AvatarImageProvider { self: RequestCache =>
val src = if (mailAddress.isEmpty) {
// by user name
getAccountByUserName(userName).map { account =>
getAccountByUserNameFromCache(userName).map { account =>
if (account.image.isEmpty && context.settings.gravatar) {
s"""https://www.gravatar.com/avatar/${StringUtil.md5(account.mailAddress.toLowerCase)}?s=${size}&d=retro&r=g"""
} else {
@@ -28,7 +28,7 @@ trait AvatarImageProvider { self: RequestCache =>
}
} else {
// by mail address
getAccountByMailAddress(mailAddress).map { account =>
getAccountByMailAddressFromCache(mailAddress).map { account =>
if (account.image.isEmpty && context.settings.gravatar) {
s"""https://www.gravatar.com/avatar/${StringUtil.md5(account.mailAddress.toLowerCase)}?s=${size}&d=retro&r=g"""
} else {

View File

@@ -16,7 +16,7 @@ trait LinkConverter { self: RequestCache =>
val userName = repository.repository.userName
val repositoryName = repository.repository.repositoryName
getIssue(userName, repositoryName, issueId.toString) match {
getIssueFromCache(userName, repositoryName, issueId.toString) match {
case Some(issue) =>
s"""<a href="${context.path}/${userName}/${repositoryName}/${if (issue.isPullRequest) "pull" else "issues"}/${issueId}"><strong>${StringUtil
.escapeHtml(title)}</strong> #${issueId}</a>"""
@@ -43,7 +43,7 @@ trait LinkConverter { self: RequestCache =>
escaped
// convert username/project@SHA to link
.replaceBy("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)/([a-zA-Z0-9\\-_\\.]+)@([a-f0-9]{40})(?=(\\W|$))".r) { m =>
getAccountByUserName(m.group(2)).map { _ =>
getAccountByUserNameFromCache(m.group(2)).map { _ =>
s"""<code><a href="${context.path}/${m.group(2)}/${m.group(3)}/commit/${m.group(4)}">${m.group(2)}/${m.group(
3
)}@${m.group(4).substring(0, 7)}</a></code>"""
@@ -53,7 +53,7 @@ trait LinkConverter { self: RequestCache =>
// convert username/project#Num to link
.replaceBy(("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)/([a-zA-Z0-9\\-_\\.]+)" + issueIdPrefix + "([0-9]+)(?=(\\W|$))").r) {
m =>
getIssue(m.group(2), m.group(3), m.group(4)) match {
getIssueFromCache(m.group(2), m.group(3), m.group(4)) match {
case Some(issue) if (issue.isPullRequest) =>
Some(s"""<a href="${context.path}/${m.group(2)}/${m.group(3)}/pull/${m.group(4)}">${m.group(2)}/${m.group(
3
@@ -68,7 +68,7 @@ trait LinkConverter { self: RequestCache =>
// convert username@SHA to link
.replaceBy(("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)@([a-f0-9]{40})(?=(\\W|$))").r) { m =>
getAccountByUserName(m.group(2)).map { _ =>
getAccountByUserNameFromCache(m.group(2)).map { _ =>
s"""<code><a href="${context.path}/${m.group(2)}/${repository.name}/commit/${m.group(3)}">${m.group(2)}@${m
.group(3)
.substring(0, 7)}</a></code>"""
@@ -77,7 +77,7 @@ trait LinkConverter { self: RequestCache =>
// convert username#Num to link
.replaceBy(("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)" + issueIdPrefix + "([0-9]+)(?=(\\W|$))").r) { m =>
getIssue(m.group(2), repository.name, m.group(3)) match {
getIssueFromCache(m.group(2), repository.name, m.group(3)) match {
case Some(issue) if (issue.isPullRequest) =>
Some(s"""<a href="${context.path}/${m.group(2)}/${repository.name}/pull/${m.group(3)}">${m.group(2)}#${m
.group(3)}</a>""")
@@ -92,7 +92,7 @@ trait LinkConverter { self: RequestCache =>
// convert issue id to link
.replaceBy(("(?<=(^|\\W))(GH-|(?<!&)" + issueIdPrefix + ")([0-9]+)(?=(\\W|$))").r) { m =>
val prefix = if (m.group(2) == "issue:") "#" else m.group(2)
getIssue(repository.owner, repository.name, m.group(3)) match {
getIssueFromCache(repository.owner, repository.name, m.group(3)) match {
case Some(issue) if (issue.isPullRequest) =>
Some(s"""<a href="${context.path}/${repository.owner}/${repository.name}/pull/${m.group(3)}">${prefix}${m
.group(3)}</a>""")
@@ -106,7 +106,7 @@ trait LinkConverter { self: RequestCache =>
// convert @username to link
.replaceBy("(?<=(^|\\W))@([a-zA-Z0-9\\-_\\.]+)(?=(\\W|$))".r) { m =>
getAccountByUserName(m.group(2)).map { _ =>
getAccountByUserNameFromCache(m.group(2)).map { _ =>
s"""<a href="${context.path}/${m.group(2)}">@${m.group(2)}</a>"""
}
}

View File

@@ -3,7 +3,6 @@ package gitbucket.core.view
import java.text.SimpleDateFormat
import java.util.{Date, Locale, TimeZone}
import com.nimbusds.jose.util.JSONObjectUtils
import gitbucket.core.controller.Context
import gitbucket.core.model.CommitState
import gitbucket.core.model.PullRequest
@@ -196,7 +195,7 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
import scala.util.matching.Regex._
implicit class RegexReplaceString(private val s: String) extends AnyVal {
def replaceAll(pattern: String, replacer: (Match) => String): String = {
def replaceAll(pattern: String)(replacer: Match => String): String = {
pattern.r.replaceAllIn(s, (m: Match) => replacer(m).replace("$", "\\$"))
}
}
@@ -204,50 +203,64 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
/**
* Convert link notations in the activity message.
*/
// format: off
def activityMessage(message: String)(implicit context: Context): Html =
Html(
message
.replaceAll(
"\\[issue:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]",
s"""<a href="${context.path}/$$1/$$2/issues/$$3">$$1/$$2#$$3</a>"""
)
.replaceAll(
"\\[pullreq:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]",
s"""<a href="${context.path}/$$1/$$2/pull/$$3">$$1/$$2#$$3</a>"""
)
.replaceAll("\\[repo:([^\\s]+?)/([^\\s]+?)\\]", s"""<a href="${context.path}/$$1/$$2\">$$1/$$2</a>""")
.replaceAll(
"\\[branch:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]",
(m: Match) =>
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/tree/${encodeRefName(m.group(3))}">${StringUtil
.escapeHtml(
m.group(3)
)}</a>"""
)
.replaceAll(
"\\[tag:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]",
(m: Match) =>
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/tree/${encodeRefName(m.group(3))}">${StringUtil
.escapeHtml(
m.group(3)
)}</a>"""
)
.replaceAll("\\[user:([^\\s]+?)\\]", (m: Match) => user(m.group(1)).body)
.replaceAll(
"\\[commit:([^\\s]+?)/([^\\s]+?)\\@([^\\s]+?)\\]",
(m: Match) =>
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/commit/${m.group(3)}">${m.group(1)}/${m
.group(2)}@${m.group(3).substring(0, 7)}</a>"""
)
.replaceAll(
"\\[release:([^\\s]+?)/([^\\s]+?)/([^\\s]+?):(.+)\\]",
(m: Match) =>
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/releases/${encodeRefName(m.group(3))}">${StringUtil
.escapeHtml(
m.group(4)
)}</a>"""
)
.replaceAll("\\[issue:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]"){ m =>
if (getRepositoryInfoFromCache(m.group(1), m.group(2)).isDefined) {
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/issues/${m.group(3)}">${m.group(1)}/${m.group(2)}#${m.group(3)}</a>"""
} else {
s"${m.group(1)}/${m.group(2)}#${m.group(3)}"
}
}
.replaceAll("\\[pullreq:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]"){ m =>
if (getRepositoryInfoFromCache(m.group(1), m.group(2)).isDefined) {
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/pull/${m.group(3)}">${m.group(1)}/${m.group(2)}#${m.group(3)}</a>"""
} else {
s"${m.group(1)}/${m.group(2)}#${m.group(3)}"
}
}
.replaceAll("\\[repo:([^\\s]+?)/([^\\s]+?)\\]") { m =>
if (getRepositoryInfoFromCache(m.group(1), m.group(2)).isDefined) {
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}">${m.group(1)}/${m.group(2)}</a>"""
} else {
s"${m.group(1)}/${m.group(2)}"
}
}
.replaceAll("\\[branch:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]") { m =>
if (getRepositoryInfoFromCache(m.group(1), m.group(2)).isDefined) {
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/tree/${encodeRefName(m.group(3))}">${StringUtil.escapeHtml(m.group(3))}</a>"""
} else {
StringUtil.escapeHtml(m.group(3))
}
}
.replaceAll("\\[tag:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]") { m =>
if (getRepositoryInfoFromCache(m.group(1), m.group(2)).isDefined) {
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/tree/${encodeRefName(m.group(3))}">${StringUtil.escapeHtml(m.group(3))}</a>"""
} else {
StringUtil.escapeHtml(m.group(3))
}
}
.replaceAll("\\[user:([^\\s]+?)\\]") { m =>
user(m.group(1)).body
}
.replaceAll("\\[commit:([^\\s]+?)/([^\\s]+?)\\@([^\\s]+?)\\]") { m =>
if (getRepositoryInfoFromCache(m.group(1), m.group(2)).isDefined) {
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/commit/${m.group(3)}">${m.group(1)}/${m.group(2)}@${m.group(3).substring(0, 7)}</a>"""
} else {
s"${m.group(1)}/${m.group(2)}@${m.group(3).substring(0, 7)}"
}
}
.replaceAll("\\[release:([^\\s]+?)/([^\\s]+?)/([^\\s]+?):(.+)\\]") { m =>
if (getRepositoryInfoFromCache(m.group(1), m.group(2)).isDefined) {
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/releases/${encodeRefName(m.group(3))}">${StringUtil.escapeHtml(m.group(4))}</a>"""
} else {
StringUtil.escapeHtml(m.group(4))
}
}
)
// format: off
/**
* Remove html tags from the given Html instance.
@@ -333,9 +346,9 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
content: Html
)(implicit context: Context): Html =
(if (mailAddress.isEmpty) {
getAccountByUserName(userName)
getAccountByUserNameFromCache(userName)
} else {
getAccountByMailAddress(mailAddress)
getAccountByMailAddressFromCache(mailAddress)
}).map { account =>
Html(s"""<a href="${url(account.userName)}" class="${styleClass}">${content}</a>""")
} getOrElse content
@@ -494,4 +507,8 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
case class CommentDiffLine(newLine: Option[String], oldLine: Option[String], `type`: String, text: String)
def appendQueryString(baseUrl: String, queryString: String): String = {
s"$baseUrl${if (baseUrl.contains("?")) "&" else "?"}$queryString"
}
}

View File

@@ -7,12 +7,16 @@
<form action="@context.path/admin/system" method="POST" validate="true" class="form-horizontal" autocomplete="off">
<ul class="nav nav-tabs fill-width" id="pullreq-tab">
<li><a href="#system">System settings</a></li>
<li><a href="#integrations">Integrations</a></li>
<li><a href="#authentication">Authentication</a></li>
</ul>
<div class="tab-content fill-width" style="padding-top: 20px;">
<div class="tab-pane" id="system">
@settings_system(info)
</div>
<div class="tab-pane" id="integrations">
@settings_integrations(info)
</div>
<div class="tab-pane" id="authentication">
@settings_authentication(info)
</div>
@@ -30,6 +34,9 @@ $(function(){
if(location.hash == '#authentication'){
$('li:has(a[href="#authentication"])').addClass('active');
$('div#authentication').addClass('active');
} else if(location.hash == '#integrations'){
$('li:has(a[href="#integrations"])').addClass('active');
$('div#integrations').addClass('active');
} else {
$('li:has(a[href="#system"])').addClass('active');
$('div#system').addClass('active');

View File

@@ -0,0 +1,196 @@
@(info: Option[Any])(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.util.DatabaseConfig
<!--====================================================================-->
<!-- Services -->
<!--====================================================================-->
<label class="strong">Services</label>
<fieldset>
<label class="checkbox">
<input type="checkbox" name="gravatar"@if(context.settings.gravatar){ checked}/>
Use Gravatar for profile images
</label>
</fieldset>
<!--====================================================================-->
<!-- SSH access -->
<!--====================================================================-->
<hr>
<label class="strong">SSH access</label>
<fieldset>
<label class="checkbox">
<input type="checkbox" id="sshEnabled" name="ssh.enabled"@if(context.settings.ssh.enabled){ checked}/>
Enable SSH access to git repository
<span class="muted normal">(Both SSH host and Base URL are required if SSH access is enabled)</span>
</label>
</fieldset>
<div class="ssh">
<div class="form-group">
<label class="control-label col-md-2" for="sshHost">SSH host</label>
<div class="col-md-10">
<input type="text" id="sshHost" name="ssh.host" class="form-control" value="@context.settings.ssh.sshHost"/>
<span id="error-ssh_host" class="error"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="sshPort">SSH port</label>
<div class="col-md-10">
<input type="text" id="sshPort" name="ssh.port" class="form-control" value="@context.settings.ssh.sshPort"/>
<span id="error-ssh_port" class="error"></span>
</div>
</div>
</div>
<!--====================================================================-->
<!-- Communication email -->
<!--====================================================================-->
<hr>
<label class="strong">Communication</label>
<fieldset>
<label class="checkbox">
<input type="checkbox" id="useSMTP" name="useSMTP" @if(context.settings.useSMTP){ checked}/>
SMTP
<span class="muted normal">(Enable notification as well as SMTP configuration if you want to send notification email too)</span>
</label>
</fieldset>
<div class="useSMTP">
<div class="form-group">
<label class="control-label col-md-2" for="smtpHost">SMTP host</label>
<div class="col-md-10">
<input type="text" id="smtpHost" name="smtp.host" class="form-control" value="@context.settings.smtp.map(_.host)"/>
<span id="error-smtp_host" class="error"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="smtpPort">SMTP port</label>
<div class="col-md-10">
<input type="text" id="smtpPort" name="smtp.port" class="form-control input-mini" value="@context.settings.smtp.map(_.port)"/>
<span id="error-smtp_port" class="error"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="smtpUser">SMTP user</label>
<div class="col-md-10">
<input type="text" id="smtpUser" name="smtp.user" class="form-control" value="@context.settings.smtp.map(_.user)"/>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="smtpPassword">SMTP password</label>
<div class="col-md-10">
<input type="password" id="smtpPassword" name="smtp.password" class="form-control" value="@context.settings.smtp.map(_.password)"/>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="smtpSsl">Enable SSL</label>
<div class="col-md-10">
<input type="checkbox" id="smtpSsl" name="smtp.ssl"@if(context.settings.smtp.flatMap(_.ssl).getOrElse(false)){ checked}/>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="smtpStarttls">Enable STARTTLS</label>
<div class="col-md-10">
<input type="checkbox" id="smtpStarttls" name="smtp.starttls"@if(context.settings.smtp.flatMap(_.starttls).getOrElse(false)){ checked}/>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="fromAddress">FROM address</label>
<div class="col-md-10">
<input type="text" id="fromAddress" name="smtp.fromAddress" class="form-control" value="@context.settings.smtp.map(_.fromAddress)"/>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="fromName">FROM name</label>
<div class="col-md-10">
<input type="text" id="fromName" name="smtp.fromName" class="form-control" value="@context.settings.smtp.map(_.fromName)"/>
</div>
</div>
<div class="text-right">
Send test mail to:
<input type="text" id="testAddress" size="30"/>
<input type="button" id="sendTestMail" value="Send"/>
</div>
</div>
<!--====================================================================-->
<!-- Notification email -->
<!--====================================================================-->
<hr>
<label class="strong">Notifications</label>
<fieldset>
<label class="checkbox" for="notification">
<input type="checkbox" id="notification" name="notification"@if(context.settings.notification){ checked}/>
Send notifications
</label>
</fieldset>
<!--====================================================================-->
<!-- Web hook -->
<!--====================================================================-->
<hr>
<label class="strong">Web hook</label>
<fieldset>
<label class="checkbox" for="blockPrivateAddress">
<input type="checkbox" id="blockPrivateAddress" name="webhook.blockPrivateAddress"@if(context.settings.webHook.blockPrivateAddress){ checked}/>
Block sending to private addresses
</label>
</fieldset>
<div class="webhook">
<label><span class="strong">IP whitelist</span></label>
<fieldset>
<textarea name="webhook.whitelist" class="form-control" style="height: 100px;">@context.settings.webHook.whitelist.mkString("\n")</textarea>
</fieldset>
</div>
<script>
$(function(){
$('#sendTestMail').click(function(){
var host = $('#smtpHost' ).val();
var port = $('#smtpPort' ).val();
var user = $('#smtpUser' ).val();
var password = $('#smtpPassword').val();
var ssl = $('#smtpSsl' ).prop('checked');
var starttls = $('#smtpStarttls').prop('checked');
var fromAddress = $('#fromAddress' ).val();
var fromName = $('#fromName' ).val();
var testAddress = $('#testAddress' ).val();
if(host == ''){
alert('SMTP Host is required.');
$('#smtpHost').focus();
} else if(testAddress == ''){
alert('Destination is required.');
$('#testAddress').focus();
} else {
$.post('@context.path/admin/system/sendmail', {
'smtp.host': host,
'smtp.port': port,
'smtp.user': user,
'smtp.password': password,
'smtp.ssl': ssl,
'smtp.starttls': starttls,
'smtp.fromAddress': fromAddress,
'smtp.fromName': fromName,
'testAddress': testAddress
}, function(data, status){
if(data != ''){
alert(data);
}
});
}
});
$('#sshEnabled').change(function(){
$('.ssh input').prop('disabled', !$(this).prop('checked'));
}).change();
$('#useSMTP').change(function(){
$('.useSMTP input').prop('disabled', !$(this).prop('checked'));
// With only SMTP in current version, notification cannot be enabled if no communication channel exists
$('#notification').prop('disabled', !$(this).prop('checked'));
if (!$(this).prop('checked')) {
// With only SMTP in current version, if SMTP is unchecked then we disable notification
$('#notification').prop('checked', false);
}
}).change();
$('#blockPrivateAddress').change(function(){
$('.webhook textarea').prop('disabled', !$(this).prop('checked'));
}).change();
});
</script>

View File

@@ -94,6 +94,14 @@
</div>
</div>
<!--====================================================================-->
<!-- User-defined CSS -->
<!--====================================================================-->
<hr>
<label><span class="strong">User-defined CSS</span></label>
<fieldset>
<textarea name="userDefinedCss" class="form-control" style="height: 100px;">@context.settings.userDefinedCss</textarea>
</fieldset>
<!--====================================================================-->
<!-- Account registration -->
<!--====================================================================-->
<hr>
@@ -108,8 +116,80 @@
<span class="strong">Deny</span> <span class="normal">- Only administrators can create accounts.</span>
</label>
</fieldset>
<!--====================================================================-->
<!-- Repository operations -->
<!--====================================================================-->
<hr>
<label class="strong">Default permissions when creating a new repository</label>
<label class="strong">Repository operation</label>
<fieldset>
<div class="form-group">
<label class="control-label col-md-2">Create</label>
<div class="col-md-10">
<label class="radio">
<input type="radio" name="repositoryOperation.create" value="true"@if(context.settings.repositoryOperation.create){ checked}>
<span class="strong">All users</span> <span class="normal">- All users can create repository.</span>
</label>
<label class="radio">
<input type="radio" name="repositoryOperation.create" value="false"@if(!context.settings.repositoryOperation.create){ checked}>
<span class="strong">Admin only</span> <span class="normal">- Only administrators can create repository.</span>
</label>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2">Delete</label>
<div class="col-md-10">
<label class="radio">
<input type="radio" name="repositoryOperation.delete" value="true"@if(context.settings.repositoryOperation.delete){ checked}>
<span class="strong">All users</span> <span class="normal">- All users can delete repository.</span>
</label>
<label class="radio">
<input type="radio" name="repositoryOperation.delete" value="false"@if(!context.settings.repositoryOperation.delete){ checked}>
<span class="strong">Admin only</span> <span class="normal">- Only administrators can delete repository.</span>
</label>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2">Rename</label>
<div class="col-md-10">
<label class="radio">
<input type="radio" name="repositoryOperation.rename" value="true"@if(context.settings.repositoryOperation.rename){ checked}>
<span class="strong">All users</span> <span class="normal">- All users can rename repository.</span>
</label>
<label class="radio">
<input type="radio" name="repositoryOperation.rename" value="false"@if(!context.settings.repositoryOperation.rename){ checked}>
<span class="strong">Admin only</span> <span class="normal">- Only administrators can rename repository.</span>
</label>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2">Transfer</label>
<div class="col-md-10">
<label class="radio">
<input type="radio" name="repositoryOperation.transfer" value="true"@if(context.settings.repositoryOperation.transfer){ checked}>
<span class="strong">All users</span> <span class="normal">- All users can transfer repository.</span>
</label>
<label class="radio">
<input type="radio" name="repositoryOperation.transfer" value="false"@if(!context.settings.repositoryOperation.transfer){ checked}>
<span class="strong">Admin only</span> <span class="normal">- Only administrators can transfer repository.</span>
</label>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2">Fork</label>
<div class="col-md-10">
<label class="radio">
<input type="radio" name="repositoryOperation.fork" value="true"@if(context.settings.repositoryOperation.fork){ checked}>
<span class="strong">All users</span> <span class="normal">- All users can fork repository.</span>
</label>
<label class="radio">
<input type="radio" name="repositoryOperation.fork" value="false"@if(!context.settings.repositoryOperation.fork){ checked}>
<span class="strong">Admin only</span> <span class="normal">- Only administrators can fork repository.</span>
</label>
</div>
</div>
</fieldset>
<hr>
<label class="strong">Default visibility when creating a new repository</label>
<fieldset>
<label class="radio">
<input type="radio" name="isCreateRepoOptionPublic" value="true"@if(context.settings.isCreateRepoOptionPublic){ checked}>
@@ -151,6 +231,41 @@
</label>
</fieldset>
<!--====================================================================-->
<!-- File upload -->
<!--====================================================================-->
<hr>
<label class="strong">File upload</label>
<fieldset>
<div class="form-group">
<label class="control-label col-md-2" for="uploadMaxFileSize">Max size</label>
<div class="col-md-10">
<input type="text" id="uploadMaxFileSize" name="upload.maxFileSize" class="form-control input-mini" value="@context.settings.upload.maxFileSize"/>
<span id="error-upload_maxFileSize" class="error"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="uploadTimeout">Timeout</label>
<div class="col-md-10">
<input type="text" id="uploadTimeout" name="upload.timeout" class="form-control input-mini" value="@context.settings.upload.timeout"/>
<span id="error-upload_timeout" class="error"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="uploadLargeMaxFileSize">Max size for large files</label>
<div class="col-md-10">
<input type="text" id="uploadLargeMaxFileSize" name="upload.largeMaxFileSize" class="form-control input-mini" value="@context.settings.upload.largeMaxFileSize"/>
<span id="error-upload_largeMaxFileSize" class="error"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="uploadLargeTimeout">Timeout for large files</label>
<div class="col-md-10">
<input type="text" id="uploadLargeTimeout" name="upload.largeTimeout" class="form-control input-mini" value="@context.settings.upload.largeTimeout"/>
<span id="error-upload_largeTimeout" class="error"></span>
</div>
</div>
</fieldset>
<!--====================================================================-->
<!-- Activity -->
<!--====================================================================-->
<hr>
@@ -165,141 +280,20 @@
</div>
</fieldset>
<!--====================================================================-->
<!-- Services -->
<!-- Sidebar -->
<!--====================================================================-->
<hr>
<label class="strong">Services</label>
<label><span class="strong">Show Repositories in Sidebar</span></label>
<fieldset>
<label class="checkbox">
<input type="checkbox" name="gravatar"@if(context.settings.gravatar){ checked}/>
Use Gravatar for profile images
<label class="radio">
<input type="radio" name="limitVisibleRepositories" value="false"@if(!context.settings.limitVisibleRepositories){ checked}>
<span class="strong">All</span> <span class="normal">- Show all repositories in sidebar.</span>
</label>
<label class="radio">
<input type="radio" name="limitVisibleRepositories" value="true"@if(context.settings.limitVisibleRepositories){ checked}>
<span class="strong">Limited</span> <span class="normal">- Limit the visible repositories in sidebar.</span>
</label>
</fieldset>
<!--====================================================================-->
<!-- SSH access -->
<!--====================================================================-->
<hr>
<label class="strong">SSH access</label>
<fieldset>
<label class="checkbox">
<input type="checkbox" id="sshEnabled" name="ssh.enabled"@if(context.settings.ssh.enabled){ checked}/>
Enable SSH access to git repository
<span class="muted normal">(Both SSH host and Base URL are required if SSH access is enabled)</span>
</label>
</fieldset>
<div class="ssh">
<div class="form-group">
<label class="control-label col-md-2" for="sshHost">SSH host</label>
<div class="col-md-10">
<input type="text" id="sshHost" name="ssh.host" class="form-control" value="@context.settings.ssh.sshHost"/>
<span id="error-ssh_host" class="error"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="sshPort">SSH port</label>
<div class="col-md-10">
<input type="text" id="sshPort" name="ssh.port" class="form-control" value="@context.settings.ssh.sshPort"/>
<span id="error-ssh_port" class="error"></span>
</div>
</div>
</div>
<!--====================================================================-->
<!-- Communication email -->
<!--====================================================================-->
<hr>
<label class="strong">Communication</label>
<fieldset>
<label class="checkbox">
<input type="checkbox" id="useSMTP" name="useSMTP" @if(context.settings.useSMTP){ checked}/>
SMTP
<span class="muted normal">(Enable notification as well as SMTP configuration if you want to send notification email too)</span>
</label>
</fieldset>
<div class="useSMTP">
<div class="form-group">
<label class="control-label col-md-2" for="smtpHost">SMTP host</label>
<div class="col-md-10">
<input type="text" id="smtpHost" name="smtp.host" class="form-control" value="@context.settings.smtp.map(_.host)"/>
<span id="error-smtp_host" class="error"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="smtpPort">SMTP port</label>
<div class="col-md-10">
<input type="text" id="smtpPort" name="smtp.port" class="form-control input-mini" value="@context.settings.smtp.map(_.port)"/>
<span id="error-smtp_port" class="error"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="smtpUser">SMTP user</label>
<div class="col-md-10">
<input type="text" id="smtpUser" name="smtp.user" class="form-control" value="@context.settings.smtp.map(_.user)"/>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="smtpPassword">SMTP password</label>
<div class="col-md-10">
<input type="password" id="smtpPassword" name="smtp.password" class="form-control" value="@context.settings.smtp.map(_.password)"/>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="smtpSsl">Enable SSL</label>
<div class="col-md-10">
<input type="checkbox" id="smtpSsl" name="smtp.ssl"@if(context.settings.smtp.flatMap(_.ssl).getOrElse(false)){ checked}/>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="smtpStarttls">Enable STARTTLS</label>
<div class="col-md-10">
<input type="checkbox" id="smtpStarttls" name="smtp.starttls"@if(context.settings.smtp.flatMap(_.starttls).getOrElse(false)){ checked}/>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="fromAddress">FROM address</label>
<div class="col-md-10">
<input type="text" id="fromAddress" name="smtp.fromAddress" class="form-control" value="@context.settings.smtp.map(_.fromAddress)"/>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="fromName">FROM name</label>
<div class="col-md-10">
<input type="text" id="fromName" name="smtp.fromName" class="form-control" value="@context.settings.smtp.map(_.fromName)"/>
</div>
</div>
<div class="text-right">
Send test mail to:
<input type="text" id="testAddress" size="30"/>
<input type="button" id="sendTestMail" value="Send"/>
</div>
</div>
<!--====================================================================-->
<!-- Notification email -->
<!--====================================================================-->
<hr>
<label class="strong">Notifications</label>
<fieldset>
<label class="checkbox" for="notification">
<input type="checkbox" id="notification" name="notification"@if(context.settings.notification){ checked}/>
Send notifications
</label>
</fieldset>
<!--====================================================================-->
<!-- Web hook -->
<!--====================================================================-->
<hr>
<label class="strong">Web hook</label>
<fieldset>
<label class="checkbox" for="blockPrivateAddress">
<input type="checkbox" id="blockPrivateAddress" name="webhook.blockPrivateAddress"@if(context.settings.webHook.blockPrivateAddress){ checked}/>
Block sending to private addresses
</label>
</fieldset>
<div class="webhook">
<label><span class="strong">IP whitelist</span></label>
<fieldset>
<textarea name="webhook.whitelist" class="form-control" style="height: 100px;">@context.settings.webHook.whitelist.mkString("\n")</textarea>
</fieldset>
</div>
<script>
$(function(){
$('#skinName').change(function(evt) {
@@ -309,61 +303,5 @@ $(function(){
themeCss.attr('href', themeCss.attr('href').replace(oldVal, that.val()));
$(document.body).removeClass(oldVal).addClass(that.val());
});
$('#sendTestMail').click(function(){
var host = $('#smtpHost' ).val();
var port = $('#smtpPort' ).val();
var user = $('#smtpUser' ).val();
var password = $('#smtpPassword').val();
var ssl = $('#smtpSsl' ).prop('checked');
var starttls = $('#smtpStarttls').prop('checked');
var fromAddress = $('#fromAddress' ).val();
var fromName = $('#fromName' ).val();
var testAddress = $('#testAddress' ).val();
if(host == ''){
alert('SMTP Host is required.');
$('#smtpHost').focus();
} else if(testAddress == ''){
alert('Destination is required.');
$('#testAddress').focus();
} else {
$.post('@context.path/admin/system/sendmail', {
'smtp.host': host,
'smtp.port': port,
'smtp.user': user,
'smtp.password': password,
'smtp.ssl': ssl,
'smtp.starttls': starttls,
'smtp.fromAddress': fromAddress,
'smtp.fromName': fromName,
'testAddress': testAddress
}, function(data, status){
if(data != ''){
alert(data);
}
});
}
});
$('#sshEnabled').change(function(){
$('.ssh input').prop('disabled', !$(this).prop('checked'));
}).change();
$('#useSMTP').change(function(){
$('.useSMTP input').prop('disabled', !$(this).prop('checked'));
// With only SMTP in current version, notification cannot be enabled if no communication channel exists
$('#notification').prop('disabled', !$(this).prop('checked'));
if (!$(this).prop('checked')) {
// With only SMTP in current version, if SMTP is unchecked then we disable notification
$('#notification').prop('checked', false);
}
}).change();
$('#blockPrivateAddress').change(function(){
$('.webhook textarea').prop('disabled', !$(this).prop('checked'));
}).change();
});
</script>

View File

@@ -7,21 +7,24 @@
@activities.map { activity =>
<div class="block">
@(activity.activityType match {
case "open_issue" => detailActivity(activity, "issue-opened")
case "comment_issue" => detailActivity(activity, "comment-discussion")
case "comment_commit" => detailActivity(activity, "comment-discussion")
case "close_issue" => detailActivity(activity, "issue-closed")
case "reopen_issue" => detailActivity(activity, "issue-reopened")
case "open_pullreq" => detailActivity(activity, "git-pull-request")
case "merge_pullreq" => detailActivity(activity, "git-merge")
case "release" => detailActivity(activity, "package")
case "create_repository" => simpleActivity(activity, "repo")
case "create_branch" => simpleActivity(activity, "git-branch")
case "delete_branch" => simpleActivity(activity, "circle-slash")
case "create_tag" => simpleActivity(activity, "tag")
case "delete_tag" => simpleActivity(activity, "circle-slash")
case "fork" => simpleActivity(activity, "repo-forked")
case "push" => customActivity(activity, "git-commit"){
case "open_issue" => simpleActivity(activity)
case "comment_issue" => simpleActivity(activity)
case "comment_commit" => simpleActivity(activity)
case "close_issue" => simpleActivity(activity)
case "reopen_issue" => simpleActivity(activity)
case "open_pullreq" => simpleActivity(activity)
case "merge_pullreq" => simpleActivity(activity)
case "release" => simpleActivity(activity)
case "create_repository" => simpleActivity(activity)
case "delete_repository" => simpleActivity(activity)
case "rename_repository" => simpleActivity(activity)
case "transfer_repository" => simpleActivity(activity)
case "create_branch" => simpleActivity(activity)
case "delete_branch" => simpleActivity(activity)
case "create_tag" => simpleActivity(activity)
case "delete_tag" => simpleActivity(activity)
case "fork" => simpleActivity(activity)
case "push" => customActivity(activity){
<div class="small activity-message">
{activity.additionalInfo.get.split("\n").reverse.take(4).zipWithIndex.map{ case (commit, i) =>
if(i == 3){
@@ -37,12 +40,12 @@
}}
</div>
}
case "create_wiki" => customActivity(activity, "book"){
case "create_wiki" => customActivity(activity){
<div class="small activity-message">
Created <a href={s"${context.path}/${activity.userName}/${activity.repositoryName}/wiki/${activity.additionalInfo.get}"}>{activity.additionalInfo.get}</a>.
</div>
}
case "edit_wiki" => customActivity(activity, "book"){
case "edit_wiki" => customActivity(activity){
activity.additionalInfo.get.split(":") match {
case Array(pageName, commitId) =>
<div class="small activity-message">
@@ -60,26 +63,7 @@
}
}
@detailActivity(activity: gitbucket.core.model.Activity, image: String) = {
@*
<div class="activity-icon-large"><i class="mega-octicon octicon-@image"></i></div>
*@
<div>
<div class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</div>
<div class="strong">
@helpers.avatarLink(activity.activityUserName, 16)
@helpers.activityMessage(activity.message)
</div>
@activity.additionalInfo.map { additionalInfo =>
<div class=" activity-message">@additionalInfo</div>
}
</div>
}
@customActivity(activity: gitbucket.core.model.Activity, image: String)(additionalInfo: Any) = {
@*
<div class="activity-icon-large"><i class="mega-octicon octicon-@image"></i></div>
*@
@customActivity(activity: gitbucket.core.model.Activity)(additionalInfo: Any) = {
<div>
<div class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</div>
<div class="strong">
@@ -90,10 +74,7 @@
</div>
}
@simpleActivity(activity: gitbucket.core.model.Activity, image: String) = {
@*
<div class="activity-icon-small"><i class="octicon octicon-@image"></i></div>
*@
@simpleActivity(activity: gitbucket.core.model.Activity) = {
<div>
<span class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</span>
<div>

View File

@@ -65,9 +65,8 @@ $(function(){
}
@dropzone(clickable: Boolean, textareaId: Option[String]) = {
url: '@context.path/upload/file/@repository.owner/@repository.name',
maxFilesize: @{FileUtil.MaxFileSize / 1024 / 1024},
//timeout defaults to 30 secs
timeout: @{FileUtil.UploadTimeout},
maxFilesize: @{context.settings.upload.maxFileSize / 1024 / 1024},
timeout: @{context.settings.upload.timeout},
clickable: @clickable,
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your files...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
success: function(file, id) {

View File

@@ -1,10 +1,12 @@
@(latestUpdatedDate: java.util.Date,
recentOnly: Boolean = true)
@import gitbucket.core.view.helpers
<span data-toggle="tooltip" title="@helpers.datetime(latestUpdatedDate)">
@if(recentOnly){
@helpers.datetimeAgoRecentOnly(latestUpdatedDate)
} else {
@helpers.datetimeAgo(latestUpdatedDate)
}
</span>
@if(latestUpdatedDate != null){
<span data-toggle="tooltip" title="@helpers.datetime(latestUpdatedDate)">
@if(recentOnly){
@helpers.datetimeAgoRecentOnly(latestUpdatedDate)
} else {
@helpers.datetimeAgo(latestUpdatedDate)
}
</span>
}

View File

@@ -104,8 +104,8 @@
} else {
@if(diff.newContent != None || diff.oldContent != None){
<div id="diffText-@i" class="diffText"></div>
<input type="hidden" id="newText-@i" data-file-name="@diff.oldPath" value="@diff.newContent">
<input type="hidden" id="oldText-@i" data-file-name="@diff.newPath" value="@diff.oldContent">
<input type="hidden" id="newText-@i" data-file-name="@diff.oldPath" data-val="@diff.newContent">
<input type="hidden" id="oldText-@i" data-file-name="@diff.newPath" data-val="@diff.oldContent">
} else {
@if(diff.newIsImage || diff.oldIsImage){
<div class="diff-image-render diff2up">

View File

@@ -1,4 +1,5 @@
@(page: Int, count: Int, limit: Int, width: Int, baseURL: String)
@import gitbucket.core.view.helpers
@(page: Int, count: Int, limit: Int, width: Int, baseURL: String = "")
@defining(gitbucket.core.view.Pagination(page, count, limit, width)){ p =>
@if(p.count > p.limit){
<div>
@@ -6,7 +7,7 @@
@if(page == 1){
<li class="disabled"><span>&#9664;</span></li>
} else {
<li><a href="@baseURL&page=@(page - 1)">&#9664;</a></li>
<li><a href="@helpers.appendQueryString(baseURL, s"page=${page - 1 }")">&#9664;</a></li>
}
@for(i <- 1 to p.max){
@if(i == p.max && p.omitRight){
@@ -16,7 +17,7 @@
<li class="active"><span>@i</span></li>
} else {
@if(p.visibleFor(i)){
<li><a href="@baseURL&page=@i">@i</a></li>
<li><a href="@helpers.appendQueryString(baseURL, s"page=$i")">@i</a></li>
}
}
@if(i == 1 && p.omitLeft){
@@ -26,7 +27,7 @@
@if(page == p.max){
<li class="disabled"><span>&#9654;</span></li>
} else {
<li><a href="@baseURL&page=@(page + 1)">&#9654;</a></li>
<li><a href="@helpers.appendQueryString(baseURL, s"page=${page + 1}")">&#9654;</a></li>
}
</ul>
</div>

View File

@@ -11,7 +11,9 @@
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
isEditable: Boolean,
isManageable: Boolean)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@gitbucket.core.html.main((if(target == "issues") "Issues" else "Pull requests") + s" - ${repository.owner}/${repository.name}", Some(repository)){
@gitbucket.core.html.menu(target, repository){
<ul class="nav nav-pills pull-left" style="line-height: 14px; margin-bottom: 10px;">

View File

@@ -36,7 +36,7 @@
<link href="@helpers.assets("/vendors/jquery-ui/jquery-ui.structure.min.css")" rel="stylesheet">
<link href="@helpers.assets("/vendors/jquery-ui/jquery-ui.theme.min.css")" rel="stylesheet">
<link href="@helpers.assets("/common/css/gitbucket.css")" rel="stylesheet">
<script src="@helpers.assets("/vendors/jquery/jquery-3.4.1.min.js")"></script>
<script src="@helpers.assets("/vendors/jquery/jquery-3.5.1.min.js")"></script>
<script src="@helpers.assets("/vendors/jquery-ui/jquery-ui.min.js")"></script>
<script src="@helpers.assets("/vendors/dropzone/dropzone.min.js")"></script>
<script src="@helpers.assets("/common/js/validation.js")"></script>
@@ -55,6 +55,9 @@
<meta name="go-import" content="@context.baseUrl.replaceFirst("^https?://", "")/@repository.owner/@repository.name git @repository.httpUrl" />
}
<script src="@helpers.assets("/vendors/AdminLTE-2.4.2/js/adminlte.min.js")" type="text/javascript"></script>
@context.settings.userDefinedCss.map { css =>
<style type="text/css">@css</style>
}
</head>
<body class="@context.settings.skinName page-load @if(body.toString.contains("menu-item-hover")){sidebar-mini} @if(context.sidebarCollapse){sidebar-collapse}">
<div class="wrapper">
@@ -69,7 +72,7 @@
<nav class="navbar navbar-static-top" role="navigation">
<!-- Sidebar toggle button-->
@if(body.toString.contains("main-sidebar")){
<a href="#" class="sidebar-toggle" data-toggle="push-menu" role="button">
<a href="#" class="sidebar-toggle" data-toggle="push-menu" role="button" title="Toggle navigation">
<span class="sr-only">Toggle navigation</span>
</a>
}
@@ -99,7 +102,9 @@
<ul class="dropdown-menu pull-right" style="width: auto;">
<li>
<ul class="menu">
<li><a href="@context.path/new">New repository</a></li>
@if(context.settings.repositoryOperation.create || context.loginAccount.get.isAdmin){
<li><a href="@context.path/new">New repository</a></li>
}
<li><a href="@context.path/groups/new">New group</a></li>
</ul>
</li>

View File

@@ -75,7 +75,7 @@
@gitbucket.core.plugin.PluginRegistry().getRepositoryHeaders.map { repositoryHeaderComponent =>
@repositoryHeaderComponent(repository, context)
}
@if(repository.repository.options.allowFork) {
@if(repository.repository.options.allowFork && (context.settings.repositoryOperation.fork || context.loginAccount.map(_.isAdmin).getOrElse(false))) {
@if(context.loginAccount.isEmpty){
<a class="btn btn-default btn-sm" href="@context.path/signin?redirect=@helpers.urlEncode(s"${context.path}/${repository.owner}/${repository.name}")">
<span class="strong"><i class="octicon octicon-repo-forked"></i>Fork</span><span class="muted">: @repository.forkedCount</span>

View File

@@ -71,10 +71,9 @@ $(function(){
});
$("#drop").dropzone({
maxFilesize: @{gitbucket.core.util.FileUtil.MaxFileSize / 1024 / 1024},
url: '@context.path/upload/release/@repository.owner/@repository.name/@helpers.encodeRefName(tag.name)',
//timeout defaults to 30 secs
timeout: @{gitbucket.core.util.FileUtil.UploadTimeout},
maxFilesize: @{context.settings.upload.largeMaxFileSize / 1024 / 1024},
timeout: @{context.settings.upload.largeTimeout},
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your files...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
success: function(file, id) {
var attach =

View File

@@ -1,6 +1,8 @@
@(repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
releases: Seq[(gitbucket.core.util.JGitUtil.TagInfo, Option[(gitbucket.core.model.ReleaseTag, Seq[gitbucket.core.model.ReleaseAsset])])],
hasWritePermission: Boolean)(implicit context: gitbucket.core.controller.Context)
releases: Seq[(gitbucket.core.util.JGitUtil.TagInfo, Option[(gitbucket.core.model.ReleaseTag, Seq[gitbucket.core.model.ReleaseAsset])])],
hasWritePermission: Boolean,
page: Int, totalReleases: Int)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@gitbucket.core.html.main("Releases" + s" - ${repository.owner}/${repository.name}", Some(repository)){
@gitbucket.core.html.menu("releases", repository){
@@ -61,5 +63,9 @@
}
</tbody>
</table>
<div class="pull-right">
@gitbucket.core.helper.html.paginator(page, totalReleases, gitbucket.core.service.ReleaseService.ReleaseLimit, 10)
</div>
}
}

View File

@@ -138,7 +138,6 @@ $(function(){
$('#btn-code').click(function(){
$('#editor').show();
$('#preview').hide();
$('#btn-code').appendClass('active');
$('#btn-preview').removeClass('active');
});
@@ -146,7 +145,6 @@ $(function(){
$('#editor').hide();
$('#preview').show();
$('#btn-code').removeClass('active');
$('#btn-preview').addClass('active');
@if(fileName.map(helpers.isRenderable _).getOrElse(false)) {
// update preview

View File

@@ -51,11 +51,12 @@
$(function(){
$('#upload-area').dropzone({
url: '@context.path/upload/tmp',
maxFilesize: @{FileUtil.MaxFileSize / 1024 / 1024},
maxFilesize: @{context.settings.upload.maxFileSize / 1024 / 1024},
timeout: @{context.settings.upload.timeout},
clickable: true,
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your files...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
success: function(file, id) {
file.previewElement.remove();
$(file.previewElement).remove();
$('#upload-files').append($('<li class="upload-file">')
.append($('<span>').data('id', id).text(file.name))
.append($('<a class="delete" href="javascript:void(0);" style="margin-left: 4px;">(delete)</a>')));

View File

@@ -7,32 +7,8 @@
<div class="panel panel-default">
<div class="panel-heading strong">Danger Zone</div>
<div class="panel-body">
<form id="transfer-form" method="post" action="@helpers.url(repository)/settings/transfer" validate="true" autocomplete="off">
<fieldset class="form-group">
<label class="strong">Transfer Ownership</label>
<div>
Transfer this repo to another user or to group.
<div class="pull-right">
@gitbucket.core.helper.html.account("newOwner", 200, true, true)
<input type="submit" class="btn btn-danger" value="Transfer"/>
<div>
<span id="error-newOwner" class="error"></span>
</div>
</div>
</div>
</fieldset>
</form>
<form id="delete-form" method="post" action="@helpers.url(repository)/settings/delete">
<fieldset class="border-top form-group">
<label class="strong">Delete repository</label>
<div>
Once you delete a repository, there is no going back.
<input type="submit" class="btn btn-danger pull-right" value="Delete this repository"/>
</div>
</fieldset>
</form>
<form id="gc-form" method="post" action="@helpers.url(repository)/settings/gc">
<fieldset class="border-top form-group">
<fieldset class="form-group">
<label class="strong">Garbage collection</label>
<div>
Run garbage collection for this git repository immediately.
@@ -40,6 +16,51 @@
</div>
</fieldset>
</form>
@if(context.settings.repositoryOperation.rename || context.loginAccount.get.isAdmin){
<form id="rename-form" method="post" action="@helpers.url(repository)/settings/rename" validate="true" autocomplete="off">
<fieldset class="border-top form-group">
<label class="strong">Rename repository</label>
<div>
Rename this repository. The current URL will be unavailable.
<div class="pull-right">
<input type="text" name="repositoryName" id="repositoryName" class="form-control" style="width: 200px; display: inline; vertical-align: middle;" value="@repository.name"/>
<input type="submit" class="btn btn-danger" value="Rename"/>
<div>
<span id="error-repositoryName" class="error"></span>
</div>
</div>
</div>
</fieldset>
</form>
}
@if(context.settings.repositoryOperation.transfer || context.loginAccount.get.isAdmin){
<form id="transfer-form" method="post" action="@helpers.url(repository)/settings/transfer" validate="true" autocomplete="off">
<fieldset class="border-top form-group">
<label class="strong">Transfer Ownership</label>
<div>
Transfer this repo to another user or to group.
<div class="pull-right">
@gitbucket.core.helper.html.account("newOwner", 200, true, true)
<input type="submit" class="btn btn-danger" value="Transfer"/>
<div>
<span id="error-newOwner" class="error"></span>
</div>
</div>
</div>
</fieldset>
</form>
}
@if(context.settings.repositoryOperation.delete || context.loginAccount.get.isAdmin){
<form id="delete-form" method="post" action="@helpers.url(repository)/settings/delete">
<fieldset class="border-top form-group">
<label class="strong">Delete repository</label>
<div>
Once you delete a repository, there is no going back.
<input type="submit" class="btn btn-danger pull-right" value="Delete this repository"/>
</div>
</fieldset>
</form>
}
</div>
</div>
}
@@ -50,6 +71,13 @@ $(function(){
$('#delete-form').submit(function(){
return confirm('Once you delete a repository, there is no going back.\nAre you sure?');
});
$('#rename-form').submit(function(){
if($('#rename-form').data('validated') === true){
return confirm('Rename this repository as you entered. The current URL will be unavailable.\nAre you sure?');
} else {
return true;
}
});
$('#transfer-form').submit(function(){
if($('#transfer-form').data('validated') === true){
return confirm('Transfer to the repository owner you entered.\nAre you sure?');

View File

@@ -9,11 +9,6 @@
<div class="panel-heading strong">Settings</div>
<div class="panel-body">
<fieldset class="form-group">
<label for="repositoryName" class="strong">Repository Name:</label>
<input type="text" name="repositoryName" id="repositoryName" class="form-control" value="@repository.name"/>
<span id="error-repositoryName" class="error"></span>
</fieldset>
<fieldset class="border-top form-group">
<label for="description" class="strong">Description:</label>
<input type="text" name="description" id="description" class="form-control" value="@repository.repository.description"/>
</fieldset>

View File

@@ -5,11 +5,13 @@
<div class="content-wrapper main-center">
<div class="content body">
<div class="signin-form">
@context.settings.information.map { information =>
<div class="alert alert-info" style="background-color: white; color: #555; border-color: #4183c4; font-size: small; line-height: 120%;">
<button type="button" class="close" data-dismiss="alert">&times;</button>
@Html(information)
</div>
@if(context.settings.allowAnonymousAccess){
@context.settings.information.map { information =>
<div class="alert alert-info" style="background-color: white; color: #555; border-color: #4183c4; font-size: small; line-height: 120%;">
<button type="button" class="close" data-dismiss="alert">&times;</button>
@Html(information)
</div>
}
}
@gitbucket.core.helper.html.error(error)
@gitbucket.core.html.signinform(context.settings, userName, password)

View File

@@ -48,7 +48,8 @@ $(function(){
try {
$('#content1').dropzone({
url: '@context.path/upload/wiki/@repository.owner/@repository.name',
maxFilesize: @{FileUtil.MaxFileSize / 1024 / 1024},
maxFilesize: @{context.settings.upload.maxFileSize / 1024 / 1024},
timeout: @{context.settings.upload.timeout},
clickable: false,
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your files...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
success: function(file, id) {
@@ -59,7 +60,8 @@ $(function(){
});
$('.clickable').dropzone({
url: '@context.path/upload/wiki/@repository.owner/@repository.name',
maxFilesize: @{FileUtil.MaxFileSize / 1024 / 1024},
maxFilesize: @{context.settings.upload.maxFileSize / 1024 / 1024},
timeout: @{context.settings.upload.timeout},
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your files...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
success: function(file, id) {
var attachFile = (file.type.match(/image\/.*/) ? '\n![' + file.name.split('.')[0] : '\n[' + file.name) + '](' + file.name + ')';

View File

@@ -80,16 +80,6 @@
</cookie-config>
</session-config>
<!-- ===================================================================== -->
<!-- Optional configurations -->
<!-- ===================================================================== -->
<!--
<context-param>
<param-name>gitbucket.home</param-name>
<param-value>PATH_TO_DATADIR</param-value>
</context-param>
-->
<mime-mapping>
<extension>svg</extension>
<mime-type>image/svg+xml</mime-type>

View File

@@ -76,12 +76,12 @@ function displayErrors(data, elem){
* @param ignoreSpace {Number} 0: include, 1: ignore
*/
function diffUsingJS(oldTextId, newTextId, outputId, viewType, ignoreSpace) {
var old = $('#'+oldTextId), head = $('#' + newTextId);
var old = $('#' + oldTextId), head = $('#' + newTextId);
var render = new JsDiffRender({
oldText: old.val(),
oldTextName: old.attr('data-file-name'),
newText: head.val(),
newTextName: head.attr('data-file-name'),
oldText : old.data('val'),
oldTextName: old.data('file-name'),
newText : head.data('val'),
newTextName: head.data('file-name'),
ignoreSpace: ignoreSpace,
contextSize: 4
});

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -110,7 +110,7 @@ table.diff ins{
background-color: #a6f3a6;
text-decoration: none;
}
table.diff del{
table.diff .body.delete del{
background-color: #f8cbcb;
text-decoration: none;
}

View File

@@ -520,6 +520,7 @@ object ApiSpecModels {
|"user":$jsonUser,
|"labels":[$jsonLabel],
|"assignee":$jsonUser,
|"draft":true,
|"id":0,
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/pull/1347",
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/pulls/1347",
@@ -706,7 +707,7 @@ object ApiSpecModels {
|"size":100,
|"label":"release.zip",
|"file_id":"${assetFileName}",
|"browser_download_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/releases/tag1/assets/${assetFileName}"
|"browser_download_url":"http://gitbucket.exmple.com/octocat/Hello-World/releases/tag1/assets/${assetFileName}"
|}""".stripMargin
val jsonRelease =

View File

@@ -14,7 +14,7 @@ import scala.util.Using
class MergeServiceSpec extends FunSpec {
val service = new MergeService with AccountService with ActivityService with IssuesService with LabelsService
with MilestonesService with RepositoryService with PrioritiesService with PullRequestService with CommitsService
with WebHookPullRequestService with WebHookPullRequestReviewCommentService {}
with WebHookPullRequestService with WebHookPullRequestReviewCommentService with RequestCache {}
val branch = "master"
val issueId = 10
def initRepository(owner: String, name: String): File = {

View File

@@ -18,7 +18,8 @@ class PullRequestServiceSpec
with PrioritiesService
with WebHookService
with WebHookPullRequestService
with WebHookPullRequestReviewCommentService {
with WebHookPullRequestReviewCommentService
with RequestCache {
def swap(r: (Issue, PullRequest)) = (r._2 -> r._1)

View File

@@ -12,7 +12,7 @@ import java.sql.DriverManager
import java.io.File
import gitbucket.core.controller.Context
import gitbucket.core.service.SystemSettingsService.{Ssh, SystemSettings}
import gitbucket.core.service.SystemSettingsService.{RepositoryOperation, Ssh, SystemSettings}
import javax.servlet.http.{HttpServletRequest, HttpSession}
import org.scalatestplus.mockito.MockitoSugar
import org.mockito.Mockito._
@@ -36,9 +36,17 @@ trait ServiceSpecBase extends MockitoSugar {
allowAccountRegistration = false,
allowAnonymousAccess = true,
isCreateRepoOptionPublic = true,
repositoryOperation = RepositoryOperation(
create = true,
delete = true,
rename = true,
transfer = true,
fork = true
),
gravatar = false,
notification = false,
activityLogLimit = None,
limitVisibleRepositories = false,
ssh = Ssh(
enabled = false,
sshHost = None,
@@ -51,10 +59,17 @@ trait ServiceSpecBase extends MockitoSugar {
oidcAuthentication = false,
oidc = None,
skinName = "skin-blue",
userDefinedCss = None,
showMailAddress = false,
webHook = SystemSettingsService.WebHook(
blockPrivateAddress = false,
whitelist = Nil
),
upload = SystemSettingsService.Upload(
maxFileSize = 3 * 1024 * 1024,
timeout = 30 * 10000,
largeMaxFileSize = 3 * 1024 * 1024,
largeTimeout = 30 * 10000
)
)
@@ -84,7 +99,7 @@ trait ServiceSpecBase extends MockitoSugar {
lazy val dummyService = new RepositoryService with AccountService with ActivityService with IssuesService
with MergeService with PullRequestService with CommitsService with CommitStatusService with LabelsService
with MilestonesService with PrioritiesService with WebHookService with WebHookPullRequestService
with WebHookPullRequestReviewCommentService {
with WebHookPullRequestReviewCommentService with RequestCache {
override def fetchAsPullRequest(
userName: String,
repositoryName: String,
@@ -107,7 +122,8 @@ trait ServiceSpecBase extends MockitoSugar {
}
def generateNewIssue(userName: String, repositoryName: String, loginUser: String = "root")(
implicit s: Session
implicit
s: Session
): Int = {
dummyService.insertIssue(
owner = userName,
@@ -123,7 +139,8 @@ trait ServiceSpecBase extends MockitoSugar {
}
def generateNewPullRequest(base: String, request: String, loginUser: String)(
implicit s: Session
implicit
s: Session
): (Issue, PullRequest) = {
implicit val context = Context(createSystemSettings(), None, this.request)
val Array(baseUserName, baseRepositoryName, baesBranch) = base.split("/")

View File

@@ -7,7 +7,7 @@ import gitbucket.core.model.WebHookContentType
class WebHookServiceSpec extends FunSuite with ServiceSpecBase {
lazy val service = new WebHookPullRequestService with AccountService with ActivityService with RepositoryService
with MergeService with PullRequestService with IssuesService with CommitsService with LabelsService
with MilestonesService with PrioritiesService with WebHookPullRequestReviewCommentService
with MilestonesService with PrioritiesService with WebHookPullRequestReviewCommentService with RequestCache
test("WebHookPullRequestService.getPullRequestsByRequestForWebhook") {
withTestDB { implicit session =>

View File

@@ -7,7 +7,7 @@ import javax.servlet.http.{HttpServletRequest, HttpSession}
import gitbucket.core.controller.Context
import gitbucket.core.model.Account
import gitbucket.core.service.RequestCache
import gitbucket.core.service.SystemSettingsService.{Ssh, SystemSettings, WebHook}
import gitbucket.core.service.SystemSettingsService.{RepositoryOperation, Ssh, SystemSettings, Upload, WebHook}
import org.mockito.Mockito._
import org.scalatest.FunSpec
import org.scalatestplus.mockito.MockitoSugar
@@ -122,9 +122,17 @@ class AvatarImageProviderSpec extends FunSpec with MockitoSugar {
allowAccountRegistration = false,
allowAnonymousAccess = true,
isCreateRepoOptionPublic = true,
repositoryOperation = RepositoryOperation(
create = true,
delete = true,
rename = true,
transfer = true,
fork = true
),
gravatar = useGravatar,
notification = false,
activityLogLimit = None,
limitVisibleRepositories = false,
ssh = Ssh(
enabled = false,
sshHost = None,
@@ -137,10 +145,17 @@ class AvatarImageProviderSpec extends FunSpec with MockitoSugar {
oidcAuthentication = false,
oidc = None,
skinName = "skin-blue",
userDefinedCss = None,
showMailAddress = false,
webHook = WebHook(
blockPrivateAddress = false,
whitelist = Nil
),
upload = Upload(
maxFileSize = 3 * 1024 * 1024,
timeout = 30 * 10000,
largeMaxFileSize = 3 * 1024 * 1024,
largeTimeout = 30 * 10000
)
)
@@ -150,11 +165,13 @@ class AvatarImageProviderSpec extends FunSpec with MockitoSugar {
class AvatarImageProviderImpl(account: Option[Account]) extends AvatarImageProvider with RequestCache {
def toHtml(userName: String, size: Int, mailAddress: String = "", tooltip: Boolean = false)(
implicit context: Context
implicit
context: Context
): Html = getAvatarImageHtml(userName, size, mailAddress, tooltip)
override def getAccountByMailAddress(mailAddress: String)(implicit context: Context): Option[Account] = account
override def getAccountByUserName(userName: String)(implicit context: Context): Option[Account] = account
override def getAccountByMailAddressFromCache(mailAddress: String)(implicit context: Context): Option[Account] =
account
override def getAccountByUserNameFromCache(userName: String)(implicit context: Context): Option[Account] = account
}
}