mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-08 12:27:33 +02:00
Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a4961c3e1 | ||
|
|
561220237f | ||
|
|
f45b85aa71 | ||
|
|
83b3a7983e | ||
|
|
63d4c5054e | ||
|
|
c8f6017be9 | ||
|
|
f9fcb54861 | ||
|
|
3534b7172d | ||
|
|
42a7f974e9 | ||
|
|
b8e02d995a | ||
|
|
e0d038aa92 | ||
|
|
3d01df2bdc | ||
|
|
6f08f1fd23 | ||
|
|
3dd366b394 | ||
|
|
8cf4528959 | ||
|
|
5d77bc5d98 | ||
|
|
572c9ef558 | ||
|
|
7c736c526e | ||
|
|
696d354f3c | ||
|
|
501cbf54ab | ||
|
|
c15d69d566 | ||
|
|
83eb933230 | ||
|
|
43bec9b0df | ||
|
|
279305c202 | ||
|
|
ecbb86c006 | ||
|
|
921fb17ef0 | ||
|
|
0362de7d35 | ||
|
|
cf0d8ea2d0 | ||
|
|
0e9026447d | ||
|
|
cf4d9cb03c | ||
|
|
2a1edeaca3 | ||
|
|
eebabf9b08 | ||
|
|
d882fcad12 | ||
|
|
f976290282 | ||
|
|
f3f9d5dae2 | ||
|
|
71dffd1089 | ||
|
|
f411e98c9a | ||
|
|
1baa489bc7 | ||
|
|
f996b0fc4a | ||
|
|
eb6ba1c800 | ||
|
|
8fac1baa3c | ||
|
|
2d327543b9 | ||
|
|
8a080efe9d | ||
|
|
aaaf61e29e | ||
|
|
1294323df5 | ||
|
|
9ef4e75746 | ||
|
|
ded4ab702d | ||
|
|
f893d045c7 | ||
|
|
ef2e3adcfb | ||
|
|
a1908c5398 | ||
|
|
ec4f0d6531 | ||
|
|
9ef366237c | ||
|
|
9197ad2600 | ||
|
|
2cb7ecd851 | ||
|
|
7d1ad4ce66 | ||
|
|
c274acc8f4 | ||
|
|
7a0d48dd7a | ||
|
|
9c6f9048e1 | ||
|
|
4ff4acfd7e | ||
|
|
d8e9f07721 | ||
|
|
662c5638dd | ||
|
|
02b830d034 | ||
|
|
3734529e5c | ||
|
|
715ec24389 | ||
|
|
5615b23548 | ||
|
|
ca2eeb48cf | ||
|
|
b063c0a80c | ||
|
|
ee8b5692bd | ||
|
|
7a749dca67 | ||
|
|
0378f26ee6 | ||
|
|
4d281273c1 | ||
|
|
e23e11e1a8 | ||
|
|
772835dd22 | ||
|
|
bbf2e57548 | ||
|
|
aecda130b6 | ||
|
|
1d48030e7c | ||
|
|
929ba2fa19 | ||
|
|
6fefa947ca | ||
|
|
072cd2e846 | ||
|
|
5d20cd0365 | ||
|
|
03ec055f66 | ||
|
|
4b6a5e5d49 | ||
|
|
6d21e38159 | ||
|
|
a47c8249bf | ||
|
|
59d7a672b3 | ||
|
|
564e95d36e | ||
|
|
9aee99be74 | ||
|
|
aecd8b503d | ||
|
|
fa65eeea35 | ||
|
|
fe43584c3d | ||
|
|
c255b13dfc | ||
|
|
917b204e5b | ||
|
|
6225fd79fc | ||
|
|
cbec567ef4 | ||
|
|
84b62242d3 | ||
|
|
f17f74c30b | ||
|
|
63b04d5a27 | ||
|
|
f02073a24c | ||
|
|
cb87d126de | ||
|
|
941f8002e5 | ||
|
|
90f4f5cd89 |
39
.github/workflows/build.yml
vendored
Normal file
39
.github/workflows/build.yml
vendored
Normal 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
3
.gitignore
vendored
@@ -27,4 +27,5 @@ project/plugins/project/
|
||||
|
||||
# Metals specific
|
||||
.metals
|
||||
.bloop
|
||||
.bloop
|
||||
project/metals.sbt
|
||||
@@ -1,3 +1,4 @@
|
||||
version = "1.5.1"
|
||||
project.git = true
|
||||
|
||||
maxColumn = 120
|
||||
|
||||
17
.travis.yml
17
.travis.yml
@@ -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
|
||||
20
CHANGELOG.md
20
CHANGELOG.md
@@ -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
|
||||
|
||||
|
||||
37
README.md
37
README.md
@@ -1,4 +1,4 @@
|
||||
GitBucket [](https://gitter.im/gitbucket/gitbucket) [](https://travis-ci.org/gitbucket/gitbucket) [](https://maven-badges.herokuapp.com/maven-central/io.github.gitbucket/gitbucket_2.13) [](https://github.com/gitbucket/gitbucket/blob/master/LICENSE)
|
||||
GitBucket [](https://gitter.im/gitbucket/gitbucket) [](https://github.com/gitbucket/gitbucket/actions?query=workflow%3Abuild+branch%3Amaster) [](https://maven-badges.herokuapp.com/maven-central/io.github.gitbucket/gitbucket_2.13) [](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.
|
||||
|
||||
36
build.sbt
36
build.sbt
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
sbt.version=1.3.5
|
||||
sbt.version=1.3.12
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
<!--================================================================================================-->
|
||||
|
||||
4
src/main/resources/update/gitbucket-core_4.34.xml
Normal file
4
src/main/resources/update/gitbucket-core_4.34.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<changeSet>
|
||||
<dropTable tableName="ACTIVITY" />
|
||||
</changeSet>
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -52,6 +52,7 @@ class ApiController
|
||||
with ReferrerAuthenticator
|
||||
with ReadableUsersAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
with RequestCache
|
||||
|
||||
trait ApiControllerBase extends ControllerBase {
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ class IssuesController
|
||||
with WebHookPullRequestReviewCommentService
|
||||
with CommitsService
|
||||
with PrioritiesService
|
||||
with RequestCache
|
||||
|
||||
trait IssuesControllerBase extends ControllerBase {
|
||||
self: IssuesService
|
||||
|
||||
@@ -36,6 +36,7 @@ class PullRequestsController
|
||||
with MergeService
|
||||
with ProtectedBranchService
|
||||
with PrioritiesService
|
||||
with RequestCache
|
||||
|
||||
trait PullRequestsControllerBase extends ControllerBase {
|
||||
self: RepositoryService
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ class WikiController
|
||||
with WebHookService
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with RequestCache
|
||||
|
||||
trait WikiControllerBase extends ControllerBase {
|
||||
self: WikiService
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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(
|
||||
|
||||
15
src/main/scala/gitbucket/core/service/PaginationHelper.scala
Normal file
15
src/main/scala/gitbucket/core/service/PaginationHelper.scala
Normal 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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
@@ -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) &&
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>"""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>◀</span></li>
|
||||
} else {
|
||||
<li><a href="@baseURL&page=@(page - 1)">◀</a></li>
|
||||
<li><a href="@helpers.appendQueryString(baseURL, s"page=${page - 1 }")">◀</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>▶</span></li>
|
||||
} else {
|
||||
<li><a href="@baseURL&page=@(page + 1)">▶</a></li>
|
||||
<li><a href="@helpers.appendQueryString(baseURL, s"page=${page + 1}")">▶</a></li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -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;">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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>
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>')));
|
||||
|
||||
@@ -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?');
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">×</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">×</button>
|
||||
@Html(information)
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@gitbucket.core.helper.html.error(error)
|
||||
@gitbucket.core.html.signinform(context.settings, userName, password)
|
||||
|
||||
@@ -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 + ')';
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
598
src/main/webapp/assets/vendors/dropzone/dropzone.js
vendored
598
src/main/webapp/assets/vendors/dropzone/dropzone.js
vendored
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
2
src/main/webapp/assets/vendors/jquery/jquery-3.5.1.min.js
vendored
Normal file
2
src/main/webapp/assets/vendors/jquery/jquery-3.5.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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("/")
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user