mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-08 03:17:02 +02:00
Compare commits
130 Commits
4.32.0
...
disable-gi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f16f395539 | ||
|
|
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 | ||
|
|
a6e7761141 | ||
|
|
eb053a66d7 | ||
|
|
5257c83563 | ||
|
|
04bc92001f | ||
|
|
d939082e1f | ||
|
|
a943a5985d | ||
|
|
9e19821256 | ||
|
|
455183a13e | ||
|
|
c241c08904 | ||
|
|
08389cb1a0 | ||
|
|
94f9d42fc4 | ||
|
|
f35ecce3c7 | ||
|
|
2837bb40b0 | ||
|
|
54331f976d | ||
|
|
b975e74de3 | ||
|
|
c45ab34f43 | ||
|
|
4ee442f697 | ||
|
|
3c25d322f2 | ||
|
|
265c6b3e0f | ||
|
|
fad4503aec | ||
|
|
d8f70bfde3 | ||
|
|
d7dfb44efc | ||
|
|
409330a9fb | ||
|
|
0fd7e07831 | ||
|
|
91bd26d2a9 | ||
|
|
e5c4cf3298 | ||
|
|
c874d3fd84 | ||
|
|
c06f95256e | ||
|
|
2f1e05833e | ||
|
|
719cad00d6 | ||
|
|
b372c71fbf | ||
|
|
6b252a7018 | ||
|
|
6575258b6c | ||
|
|
d33280f9af | ||
|
|
863bb80ad1 |
29
.github/workflows/build.yml
vendored
Normal file
29
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
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: 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
|
||||
@@ -1,6 +1,13 @@
|
||||
# Changelog
|
||||
All changes to the project will be documented in this file.
|
||||
|
||||
### 4.33.0 - 31 Dec 2019
|
||||
|
||||
- All CLI options are configurable by environment variables
|
||||
- Folding pull request files
|
||||
- WebHook security options
|
||||
- Add assignee and assignees properties to some Web APIs' response
|
||||
|
||||
### 4.32.0 - 7 Aug 2019
|
||||
|
||||
- Bump to Scala 2.13.0 and Scalatra 2.7.0
|
||||
|
||||
29
README.md
29
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.12) [](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,19 +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]`
|
||||
|
||||
`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 for upload files.
|
||||
|
||||
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).
|
||||
@@ -68,15 +55,13 @@ 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.32.0 - 7 Aug 2019
|
||||
### 4.33.0 - 31 Dec 2019
|
||||
|
||||
- Bump to Scala 2.13.0 and Scalatra 2.7.0
|
||||
- Draft pull request
|
||||
- Drop network installation of plugins
|
||||
- Compare view works for commit id
|
||||
- Apply default priority to pull requests
|
||||
- Focus title after clicking issue / pull request edit button
|
||||
- All CLI options are configurable by environment variables
|
||||
- Folding pull request files
|
||||
- WebHook security options
|
||||
- Add assignee and assignees properties to some Web APIs' response
|
||||
|
||||
See the [change log](CHANGELOG.md) for all of the updates.
|
||||
|
||||
61
build.sbt
61
build.sbt
@@ -3,10 +3,10 @@ import com.typesafe.sbt.pgp.PgpKeys._
|
||||
|
||||
val Organization = "io.github.gitbucket"
|
||||
val Name = "gitbucket"
|
||||
val GitBucketVersion = "4.32.0"
|
||||
val GitBucketVersion = "4.33.0"
|
||||
val ScalatraVersion = "2.7.0-RC1"
|
||||
val JettyVersion = "9.4.19.v20190610"
|
||||
val JgitVersion = "5.4.0.201906121030-r"
|
||||
val JettyVersion = "9.4.30.v20200611"
|
||||
val JgitVersion = "5.8.0.202006091008-r"
|
||||
|
||||
lazy val root = (project in file("."))
|
||||
.enablePlugins(SbtTwirl, ScalatraPlugin)
|
||||
@@ -17,7 +17,7 @@ sourcesInBase := false
|
||||
organization := Organization
|
||||
name := Name
|
||||
version := GitBucketVersion
|
||||
scalaVersion := "2.13.0"
|
||||
scalaVersion := "2.13.1"
|
||||
|
||||
scalafmtOnCompile := true
|
||||
|
||||
@@ -27,9 +27,7 @@ coverageExcludedPackages := ".*\\.html\\..*"
|
||||
resolvers ++= Seq(
|
||||
Classpaths.typesafeReleases,
|
||||
Resolver.jcenterRepo,
|
||||
"amateras" at "http://amateras.sourceforge.jp/mvn/",
|
||||
"sonatype-snapshot" at "https://oss.sonatype.org/content/repositories/snapshots/",
|
||||
"amateras-snapshot" at "http://amateras.sourceforge.jp/mvn-snapshot/"
|
||||
"sonatype-snapshot" at "https://oss.sonatype.org/content/repositories/snapshots/"
|
||||
)
|
||||
|
||||
libraryDependencies ++= Seq(
|
||||
@@ -38,41 +36,42 @@ 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.18",
|
||||
"org.apache.commons" % "commons-compress" % "1.20",
|
||||
"org.apache.commons" % "commons-email" % "1.5",
|
||||
"org.apache.httpcomponents" % "httpclient" % "4.5.9",
|
||||
"commons-net" % "commons-net" % "3.6",
|
||||
"org.apache.httpcomponents" % "httpclient" % "4.5.12",
|
||||
"org.apache.sshd" % "apache-sshd" % "2.1.0" exclude ("org.slf4j", "slf4j-jdk14") exclude ("org.apache.sshd", "sshd-mina") exclude ("org.apache.sshd", "sshd-netty"),
|
||||
"org.apache.tika" % "tika-core" % "1.22",
|
||||
"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.4.3",
|
||||
"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.3.1",
|
||||
"com.typesafe" % "config" % "1.3.4",
|
||||
"com.typesafe.akka" %% "akka-actor" % "2.5.23",
|
||||
"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.2.Final",
|
||||
"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.0.0" % "test",
|
||||
"com.dimafeng" %% "testcontainers-scala" % "0.29.0" % "test",
|
||||
"org.testcontainers" % "mysql" % "1.12.0" % "test",
|
||||
"org.testcontainers" % "postgresql" % "1.12.0" % "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"
|
||||
)
|
||||
|
||||
@@ -258,3 +257,17 @@ licenseOverrides := {
|
||||
case DepModuleInfo("com.github.bkromhout", "java-diff-utils", _) =>
|
||||
LicenseInfo(LicenseCategory.Apache, "Apache-2.0", "http://www.apache.org/licenses/LICENSE-2.0")
|
||||
}
|
||||
|
||||
testOptions in Test ++= {
|
||||
if (scala.util.Properties.isWin) {
|
||||
Seq(
|
||||
Tests.Exclude(
|
||||
Set(
|
||||
"gitbucket.core.GitBucketCoreModuleSpec"
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
Nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
sbt.version=1.2.8
|
||||
sbt.version=1.3.12
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
||||
|
||||
addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.1")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.4.2")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.10")
|
||||
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.3")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.0")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0")
|
||||
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.4")
|
||||
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0")
|
||||
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.2")
|
||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.0")
|
||||
|
||||
addSbtCoursier
|
||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1")
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.3")
|
||||
@@ -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;
|
||||
@@ -16,13 +20,22 @@ public class JettyLauncher {
|
||||
System.setProperty("java.awt.headless", "true");
|
||||
|
||||
String host = null;
|
||||
int port = 8080;
|
||||
String port = null;
|
||||
InetSocketAddress address = null;
|
||||
String contextPath = "/";
|
||||
String tmpDirPath="";
|
||||
boolean forceHttps = false;
|
||||
boolean saveSessions = false;
|
||||
|
||||
host = getEnvironmentVariable("gitbucket.host");
|
||||
port = getEnvironmentVariable("gitbucket.port");
|
||||
contextPath = getEnvironmentVariable("gitbucket.prefix");
|
||||
tmpDirPath = getEnvironmentVariable("gitbucket.tempDir");
|
||||
|
||||
for(String arg: args) {
|
||||
if(arg.equals("--save_sessions")) {
|
||||
saveSessions = true;
|
||||
}
|
||||
if(arg.startsWith("--") && arg.contains("=")) {
|
||||
String[] dim = arg.split("=");
|
||||
if(dim.length >= 2) {
|
||||
@@ -31,16 +44,10 @@ public class JettyLauncher {
|
||||
host = dim[1];
|
||||
break;
|
||||
case "--port":
|
||||
port = Integer.parseInt(dim[1]);
|
||||
port = dim[1];
|
||||
break;
|
||||
case "--prefix":
|
||||
contextPath = dim[1];
|
||||
if (!contextPath.startsWith("/")) {
|
||||
contextPath = "/" + contextPath;
|
||||
}
|
||||
break;
|
||||
case "--max_file_size":
|
||||
System.setProperty("gitbucket.maxFileSize", dim[1]);
|
||||
break;
|
||||
case "--gitbucket.home":
|
||||
System.setProperty("gitbucket.home", dim[1]);
|
||||
@@ -51,18 +58,19 @@ public class JettyLauncher {
|
||||
case "--plugin_dir":
|
||||
System.setProperty("gitbucket.pluginDir", dim[1]);
|
||||
break;
|
||||
case "--validate_password":
|
||||
System.setProperty("gitbucket.validate.password", dim[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (contextPath != null && !contextPath.startsWith("/")) {
|
||||
contextPath = "/" + contextPath;
|
||||
}
|
||||
|
||||
if(host != null) {
|
||||
address = new InetSocketAddress(host, port);
|
||||
address = new InetSocketAddress(host, getPort(port));
|
||||
} else {
|
||||
address = new InetSocketAddress(port);
|
||||
address = new InetSocketAddress(getPort(port));
|
||||
}
|
||||
|
||||
Server server = new Server(address);
|
||||
@@ -87,8 +95,21 @@ 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.equals("")){
|
||||
if(tmpDirPath == null || tmpDirPath.equals("")){
|
||||
tmpDir = new File(getGitBucketHome(), "tmp");
|
||||
if(!tmpDir.exists()){
|
||||
tmpDir.mkdirs();
|
||||
@@ -111,7 +132,7 @@ public class JettyLauncher {
|
||||
ProtectionDomain domain = JettyLauncher.class.getProtectionDomain();
|
||||
URL location = domain.getCodeSource().getLocation();
|
||||
|
||||
context.setContextPath(contextPath);
|
||||
context.setContextPath(contextPath == null ? "" : contextPath);
|
||||
context.setDescriptor(location.toExternalForm() + "/WEB-INF/web.xml");
|
||||
context.setServer(server);
|
||||
context.setWar(location.toExternalForm());
|
||||
@@ -140,6 +161,23 @@ public class JettyLauncher {
|
||||
return new File(System.getProperty("user.home"), ".gitbucket");
|
||||
}
|
||||
|
||||
private static String getEnvironmentVariable(String key){
|
||||
String value = System.getenv(key.toUpperCase().replace('.', '_'));
|
||||
if (value != null && value.length() == 0){
|
||||
return null;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private static int getPort(String port){
|
||||
if(port == null) {
|
||||
return 8080;
|
||||
} else {
|
||||
return Integer.parseInt(port);
|
||||
}
|
||||
}
|
||||
|
||||
private static Handler addStatisticsHandler(Handler handler) {
|
||||
// The graceful shutdown is implemented via the statistics handler.
|
||||
// See the following: https://bugs.eclipse.org/bugs/show_bug.cgi?id=420142
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
<!--================================================================================================-->
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -64,5 +64,6 @@ object GitBucketCoreModule
|
||||
new Version("4.31.0", new LiquibaseMigration("update/gitbucket-core_4.31.xml")),
|
||||
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.32.0", new LiquibaseMigration("update/gitbucket-core_4.32.xml")),
|
||||
new Version("4.33.0")
|
||||
)
|
||||
|
||||
@@ -12,6 +12,7 @@ case class ApiIssue(
|
||||
number: Int,
|
||||
title: String,
|
||||
user: ApiUser,
|
||||
assignee: Option[ApiUser],
|
||||
labels: List[ApiLabel],
|
||||
state: String,
|
||||
created_at: Date,
|
||||
@@ -19,6 +20,7 @@ case class ApiIssue(
|
||||
body: String
|
||||
)(repositoryName: RepositoryName, isPullRequest: Boolean) {
|
||||
val id = 0 // dummy id
|
||||
val assignees = List(assignee).flatten
|
||||
val comments_url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/issues/${number}/comments")
|
||||
val html_url = ApiPath(s"/${repositoryName.fullName}/${if (isPullRequest) { "pull" } else { "issues" }}/${number}")
|
||||
val pull_request = if (isPullRequest) {
|
||||
@@ -36,11 +38,18 @@ case class ApiIssue(
|
||||
}
|
||||
|
||||
object ApiIssue {
|
||||
def apply(issue: Issue, repositoryName: RepositoryName, user: ApiUser, labels: List[ApiLabel]): ApiIssue =
|
||||
def apply(
|
||||
issue: Issue,
|
||||
repositoryName: RepositoryName,
|
||||
user: ApiUser,
|
||||
assignee: Option[ApiUser],
|
||||
labels: List[ApiLabel]
|
||||
): ApiIssue =
|
||||
ApiIssue(
|
||||
number = issue.issueId,
|
||||
title = issue.title,
|
||||
user = user,
|
||||
assignee = assignee,
|
||||
labels = labels,
|
||||
state = if (issue.closed) { "closed" } else { "open" },
|
||||
body = issue.content.getOrElse(""),
|
||||
|
||||
@@ -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
|
||||
@@ -82,7 +83,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 +95,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(
|
||||
@@ -255,12 +256,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
account,
|
||||
members,
|
||||
extraMailAddresses,
|
||||
context.loginAccount.exists(
|
||||
x =>
|
||||
members.exists { member =>
|
||||
member.userName == x.userName && member.isManager
|
||||
}
|
||||
)
|
||||
isGroupManager(context.loginAccount, members)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -272,12 +268,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
if (account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
||||
getVisibleRepositories(context.loginAccount, Some(userName)),
|
||||
extraMailAddresses,
|
||||
context.loginAccount.exists(
|
||||
x =>
|
||||
members.exists { member =>
|
||||
member.userName == x.userName && member.isManager
|
||||
}
|
||||
)
|
||||
isGroupManager(context.loginAccount, members)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -536,7 +527,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
WebHookPushPayload.createDummyPayload(ownerAccount)
|
||||
}
|
||||
|
||||
val (webHook, json, reqFuture, resFuture) = callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload).head
|
||||
val (webHook, json, reqFuture, resFuture) =
|
||||
callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload, context.settings).head
|
||||
|
||||
val toErrorMap: PartialFunction[Throwable, Map[String, String]] = {
|
||||
case e: java.net.UnknownHostException => Map("error" -> ("Unknown host " + e.getMessage))
|
||||
@@ -700,27 +692,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 {
|
||||
@@ -744,8 +737,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
|
||||
|
||||
@@ -759,7 +752,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
// redirect to the repository
|
||||
redirect(s"/${accountName}/${repository.name}")
|
||||
}
|
||||
} else BadRequest()
|
||||
} else Forbidden()
|
||||
})
|
||||
|
||||
private def existsAccount: Constraint = new Constraint() {
|
||||
@@ -822,4 +815,13 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def isGroupManager(account: Option[Account], members: Seq[GroupMember]): Boolean = {
|
||||
account.exists { account =>
|
||||
account.isAdmin || members.exists { member =>
|
||||
member.userName == account.userName && member.isManager
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,7 +27,12 @@ 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 +98,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 +128,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 {
|
||||
|
||||
@@ -65,7 +65,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
|
||||
)
|
||||
@@ -279,11 +284,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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -98,7 +98,16 @@ trait MilestonesControllerBase extends ControllerBase {
|
||||
for {
|
||||
owner <- params.optionValue("owner")
|
||||
repository <- params.optionValue("repository")
|
||||
_ <- getMilestones(owner, repository).find(_.title.equalsIgnoreCase(value))
|
||||
_ <- params.optionValue("milestoneId") match {
|
||||
// existing milestone
|
||||
case Some(id) =>
|
||||
getMilestones(owner, repository)
|
||||
.find(m => m.title.equalsIgnoreCase(value) && m.milestoneId.toString != id)
|
||||
// new milestone
|
||||
case None =>
|
||||
getMilestones(owner, repository)
|
||||
.find(m => m.title.equalsIgnoreCase(value))
|
||||
}
|
||||
} yield {
|
||||
"Milestone already exists."
|
||||
}
|
||||
|
||||
@@ -324,13 +324,14 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
pullreq.branch,
|
||||
loginAccount,
|
||||
s"Merge branch '${alias}' into ${pullreq.requestBranch}",
|
||||
Some(pullreq)
|
||||
Some(pullreq),
|
||||
context.settings
|
||||
) match {
|
||||
case None => // conflict
|
||||
flash.update("error", s"Can't automatic merging branch '${alias}' into ${pullreq.requestBranch}.")
|
||||
case Some(oldId) =>
|
||||
// update pull request
|
||||
updatePullRequests(owner, name, pullreq.requestBranch, loginAccount, "synchronize")
|
||||
updatePullRequests(owner, name, pullreq.requestBranch, loginAccount, "synchronize", context.settings)
|
||||
flash.update("info", s"Merge branch '${alias}' into ${pullreq.requestBranch}")
|
||||
}
|
||||
}
|
||||
@@ -357,7 +358,15 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
|
||||
mergePullRequest(repository, issueId, context.loginAccount.get, form.message, form.strategy, form.isDraft) match {
|
||||
mergePullRequest(
|
||||
repository,
|
||||
issueId,
|
||||
context.loginAccount.get,
|
||||
form.message,
|
||||
form.strategy,
|
||||
form.isDraft,
|
||||
context.settings
|
||||
) match {
|
||||
case Right(objectId) => redirect(s"/${owner}/${name}/pull/${issueId}")
|
||||
case Left(message) => Some(BadRequest(message))
|
||||
}
|
||||
@@ -558,7 +567,8 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
commitIdFrom = form.commitIdFrom,
|
||||
commitIdTo = form.commitIdTo,
|
||||
isDraft = form.isDraft,
|
||||
loginAccount = context.loginAccount.get
|
||||
loginAccount = context.loginAccount.get,
|
||||
settings = context.settings
|
||||
)
|
||||
|
||||
// insert labels
|
||||
|
||||
@@ -2,7 +2,7 @@ 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}
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
@@ -10,6 +10,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
|
||||
@@ -42,17 +43,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 +213,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
|
||||
@@ -44,7 +45,6 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
|
||||
// for repository options
|
||||
case class OptionsForm(
|
||||
repositoryName: String,
|
||||
description: Option[String],
|
||||
isPrivate: Boolean,
|
||||
issuesOption: String,
|
||||
@@ -57,9 +57,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))),
|
||||
@@ -104,6 +101,15 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
(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 +150,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 */
|
||||
@@ -294,7 +295,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
)
|
||||
}
|
||||
|
||||
val (webHook, json, reqFuture, resFuture) = callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload).head
|
||||
val (webHook, json, reqFuture, resFuture) =
|
||||
callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload, context.settings).head
|
||||
|
||||
val toErrorMap: PartialFunction[Throwable, Map[String, String]] = {
|
||||
case e: java.net.UnknownHostException => Map("error" -> ("Unknown host " + e.getMessage))
|
||||
@@ -363,24 +365,40 @@ 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) {
|
||||
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
|
||||
}
|
||||
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) {
|
||||
renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
|
||||
}
|
||||
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()
|
||||
})
|
||||
|
||||
/**
|
||||
|
||||
@@ -354,7 +354,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
path = form.path,
|
||||
files = files.toIndexedSeq,
|
||||
message = form.message.getOrElse("Add files via upload"),
|
||||
loginAccount = context.loginAccount.get
|
||||
loginAccount = context.loginAccount.get,
|
||||
settings = context.settings
|
||||
) {
|
||||
case (git, headTip, builder, inserter) =>
|
||||
JGitUtil.processTree(git, headTip) { (path, tree) =>
|
||||
@@ -441,7 +442,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
charset = form.charset,
|
||||
message = form.message.getOrElse(s"Create ${form.newFileName}"),
|
||||
commit = form.commit,
|
||||
loginAccount = context.loginAccount.get
|
||||
loginAccount = context.loginAccount.get,
|
||||
settings = context.settings
|
||||
)
|
||||
|
||||
redirect(
|
||||
@@ -465,7 +467,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
form.message.getOrElse(s"Rename ${form.oldFileName.get} to ${form.newFileName}")
|
||||
},
|
||||
commit = form.commit,
|
||||
loginAccount = context.loginAccount.get
|
||||
loginAccount = context.loginAccount.get,
|
||||
settings = context.settings
|
||||
)
|
||||
|
||||
redirect(
|
||||
@@ -485,7 +488,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
charset = "",
|
||||
message = form.message.getOrElse(s"Delete ${form.fileName}"),
|
||||
commit = form.commit,
|
||||
loginAccount = context.loginAccount.get
|
||||
loginAccount = context.loginAccount.get,
|
||||
settings = context.settings
|
||||
)
|
||||
|
||||
redirect(
|
||||
@@ -924,8 +928,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
|
||||
@@ -946,7 +951,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,17 +98,18 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
)(OIDC.apply)
|
||||
),
|
||||
"skinName" -> trim(label("AdminLTE skin name", text(required))),
|
||||
"showMailAddress" -> trim(label("Show mail address", boolean())) //,
|
||||
// "pluginNetworkInstall" -> trim(label("Network plugin installation", boolean())),
|
||||
// "proxy" -> optionalIfNotChecked(
|
||||
// "useProxy",
|
||||
// mapping(
|
||||
// "host" -> trim(label("Proxy host", text(required))),
|
||||
// "port" -> trim(label("Proxy port", number())),
|
||||
// "user" -> trim(label("Keystore", optional(text()))),
|
||||
// "password" -> trim(label("Keystore", optional(text())))
|
||||
// )(Proxy.apply)
|
||||
// )
|
||||
"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),
|
||||
"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) {
|
||||
@@ -176,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(
|
||||
@@ -190,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(
|
||||
@@ -524,24 +533,37 @@ 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 multiLineText(constraints: Constraint*): SingleValueType[Seq[String]] =
|
||||
new SingleValueType[Seq[String]](constraints: _*) {
|
||||
def convert(value: String, messages: Messages): Seq[String] = {
|
||||
if (value == null) {
|
||||
Nil
|
||||
} else {
|
||||
value.split("\n").toIndexedSeq.map(_.trim)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
form.pageName,
|
||||
commitId
|
||||
)
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Gollum) {
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Gollum, context.settings) {
|
||||
getAccountByUserName(repository.owner).map { repositoryUser =>
|
||||
WebHookGollumPayload("edited", form.pageName, commitId, repository, repositoryUser, loginAccount)
|
||||
}
|
||||
@@ -229,7 +229,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
commitId =>
|
||||
updateLastActivityDate(repository.owner, repository.name)
|
||||
recordCreateWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName)
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Gollum) {
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Gollum, context.settings) {
|
||||
getAccountByUserName(repository.owner).map { repositoryUser =>
|
||||
WebHookGollumPayload("created", form.pageName, commitId, repository, repositoryUser, loginAccount)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -31,7 +31,7 @@ trait ApiIssueControllerBase extends ControllerBase {
|
||||
val condition = IssueSearchCondition(request)
|
||||
val baseOwner = getAccountByUserName(repository.owner).get
|
||||
|
||||
val issues: List[(Issue, Account)] =
|
||||
val issues: List[(Issue, Account, Option[Account])] =
|
||||
searchIssueByApi(
|
||||
condition = condition,
|
||||
offset = (page - 1) * PullRequestLimit,
|
||||
@@ -40,11 +40,12 @@ trait ApiIssueControllerBase extends ControllerBase {
|
||||
)
|
||||
|
||||
JsonFormat(issues.map {
|
||||
case (issue, issueUser) =>
|
||||
case (issue, issueUser, assignedUser) =>
|
||||
ApiIssue(
|
||||
issue = issue,
|
||||
repositoryName = RepositoryName(repository),
|
||||
user = ApiUser(issueUser),
|
||||
assignee = assignedUser.map(ApiUser(_)),
|
||||
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||
.map(ApiLabel(_, RepositoryName(repository)))
|
||||
)
|
||||
@@ -59,13 +60,15 @@ trait ApiIssueControllerBase extends ControllerBase {
|
||||
(for {
|
||||
issueId <- params("id").toIntOpt
|
||||
issue <- getIssue(repository.owner, repository.name, issueId.toString)
|
||||
openedUser <- getAccountByUserName(issue.openedUserName)
|
||||
users = getAccountsByUserNames(Set(issue.openedUserName) ++ issue.assignedUserName, Set())
|
||||
openedUser <- users.get(issue.openedUserName)
|
||||
} yield {
|
||||
JsonFormat(
|
||||
ApiIssue(
|
||||
issue,
|
||||
RepositoryName(repository),
|
||||
ApiUser(openedUser),
|
||||
issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)),
|
||||
getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository)))
|
||||
)
|
||||
)
|
||||
@@ -98,6 +101,7 @@ trait ApiIssueControllerBase extends ControllerBase {
|
||||
issue,
|
||||
RepositoryName(repository),
|
||||
ApiUser(loginAccount),
|
||||
issue.assignedUserName.flatMap(getAccountByUserName(_)).map(ApiUser(_)),
|
||||
getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||
.map(ApiLabel(_, RepositoryName(repository)))
|
||||
)
|
||||
|
||||
@@ -114,8 +114,9 @@ trait ApiPullRequestControllerBase extends ControllerBase {
|
||||
requestBranch = reqBranch,
|
||||
commitIdFrom = commitIdFrom.getName,
|
||||
commitIdTo = commitIdTo.getName,
|
||||
isDraft = false,
|
||||
loginAccount = context.loginAccount.get
|
||||
isDraft = createPullReq.draft.getOrElse(false),
|
||||
loginAccount = context.loginAccount.get,
|
||||
settings = context.settings
|
||||
)
|
||||
getApiPullRequest(repository, issueId).map(JsonFormat(_))
|
||||
case _ =>
|
||||
@@ -143,7 +144,8 @@ trait ApiPullRequestControllerBase extends ControllerBase {
|
||||
commitIdFrom = commitIdFrom.getName,
|
||||
commitIdTo = commitIdTo.getName,
|
||||
isDraft = false,
|
||||
loginAccount = context.loginAccount.get
|
||||
loginAccount = context.loginAccount.get,
|
||||
settings = context.settings
|
||||
)
|
||||
getApiPullRequest(repository, createPullReqAlt.issue).map(JsonFormat(_))
|
||||
case _ =>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -127,17 +127,14 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
||||
branch,
|
||||
path,
|
||||
Some(paths.last),
|
||||
if (data.sha.isDefined) {
|
||||
Some(paths.last)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
data.sha.map(_ => paths.last),
|
||||
StringUtil.base64Decode(data.content),
|
||||
data.message,
|
||||
commit,
|
||||
context.loginAccount.get,
|
||||
data.committer.map(_.name).getOrElse(context.loginAccount.get.fullName),
|
||||
data.committer.map(_.email).getOrElse(context.loginAccount.get.mailAddress)
|
||||
data.committer.map(_.email).getOrElse(context.loginAccount.get.mailAddress),
|
||||
context.settings
|
||||
)
|
||||
ApiContents("file", paths.last, path, objectId.name, None, None)(RepositoryName(repository))
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.service.{AccountService, RepositoryService}
|
||||
import gitbucket.core.util.{AdminAuthenticator, UsersAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.StringUtil._
|
||||
import org.scalatra.NoContent
|
||||
|
||||
trait ApiUserControllerBase extends ControllerBase {
|
||||
@@ -70,7 +71,7 @@ trait ApiUserControllerBase extends ControllerBase {
|
||||
} yield {
|
||||
val user = createAccount(
|
||||
data.login,
|
||||
data.password,
|
||||
pbkdf2_sha256(data.password),
|
||||
data.fullName.getOrElse(data.login),
|
||||
data.email,
|
||||
data.isAdmin.getOrElse(false),
|
||||
|
||||
@@ -15,7 +15,7 @@ import gitbucket.core.service.ProtectedBranchService.ProtectedBranchReceiveHook
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.service.SystemSettingsService
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import gitbucket.core.util.DatabaseConfig
|
||||
import gitbucket.core.util.{ConfigUtil, DatabaseConfig}
|
||||
import gitbucket.core.util.Directory._
|
||||
import io.github.gitbucket.solidbase.Solidbase
|
||||
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
|
||||
@@ -235,7 +235,7 @@ object PluginRegistry {
|
||||
.reverse
|
||||
}
|
||||
|
||||
lazy val extraPluginDir: Option[String] = Option(System.getProperty("gitbucket.pluginDir"))
|
||||
lazy val extraPluginDir: Option[String] = ConfigUtil.getConfigValue[String]("gitbucket.pluginDir")
|
||||
|
||||
def getGitBucketVersion(pluginJarFileName: String): Option[String] = {
|
||||
val regex = ".+-gitbucket\\_(\\d+\\.\\d+\\.\\d+(-SNAPSHOT)?)-.+".r
|
||||
|
||||
@@ -131,6 +131,23 @@ trait ActivityService {
|
||||
currentDate
|
||||
)
|
||||
|
||||
def recordReopenPullRequestActivity(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
title: String
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"reopen_issue",
|
||||
s"[user:${activityUserName}] reopened pull request [issue:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(title),
|
||||
currentDate
|
||||
)
|
||||
|
||||
def recordCommentIssueActivity(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
|
||||
@@ -94,7 +94,8 @@ trait CommitsService {
|
||||
repository,
|
||||
issue,
|
||||
pullRequest,
|
||||
loginAccount
|
||||
loginAccount,
|
||||
context.settings
|
||||
)
|
||||
}
|
||||
case None =>
|
||||
|
||||
@@ -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) =>
|
||||
@@ -80,16 +83,17 @@ trait HandleCommentService {
|
||||
|
||||
// call web hooks
|
||||
action match {
|
||||
case None => commentId foreach (callIssueCommentWebHook(repository, issue, _, loginAccount))
|
||||
case None =>
|
||||
commentId foreach (callIssueCommentWebHook(repository, issue, _, loginAccount, context.settings))
|
||||
case Some(act) =>
|
||||
val webHookAction = act match {
|
||||
case "close" => "closed"
|
||||
case "reopen" => "reopened"
|
||||
}
|
||||
if (issue.isPullRequest)
|
||||
callPullRequestWebHook(webHookAction, repository, issue.issueId, loginAccount)
|
||||
callPullRequestWebHook(webHookAction, repository, issue.issueId, loginAccount, context.settings)
|
||||
else
|
||||
callIssuesWebHook(webHookAction, repository, issue, loginAccount)
|
||||
callIssuesWebHook(webHookAction, repository, issue, loginAccount, context.settings)
|
||||
}
|
||||
|
||||
// call hooks
|
||||
|
||||
@@ -57,7 +57,7 @@ trait IssueCreationService {
|
||||
createReferComment(owner, name, issue, title + " " + body.getOrElse(""), loginAccount)
|
||||
|
||||
// call web hooks
|
||||
callIssuesWebHook("opened", repository, issue, loginAccount)
|
||||
callIssuesWebHook("opened", repository, issue, loginAccount, context.settings)
|
||||
|
||||
// call hooks
|
||||
PluginRegistry().getIssueHooks.foreach(_.created(issue, repository))
|
||||
|
||||
@@ -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.
|
||||
@@ -265,17 +265,19 @@ trait IssuesService {
|
||||
}
|
||||
|
||||
/** for api
|
||||
* @return (issue, issueUser, commentCount)
|
||||
* @return (issue, issueUser, assignedUser)
|
||||
*/
|
||||
def searchIssueByApi(condition: IssueSearchCondition, offset: Int, limit: Int, repos: (String, String)*)(
|
||||
implicit s: Session
|
||||
): List[(Issue, Account)] = {
|
||||
): List[(Issue, Account, Option[Account])] = {
|
||||
// get issues and comment count and labels
|
||||
searchIssueQueryBase(condition, false, offset, limit, repos)
|
||||
.join(Accounts)
|
||||
.on { case t1 ~ t2 ~ i ~ t3 => t3.userName === t1.openedUserName }
|
||||
.sortBy { case t1 ~ t2 ~ i ~ t3 => i asc }
|
||||
.map { case t1 ~ t2 ~ i ~ t3 => (t1, t3) }
|
||||
.joinLeft(Accounts)
|
||||
.on { case t1 ~ t2 ~ i ~ t3 ~ t4 => t4.userName === t1.assignedUserName }
|
||||
.sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 => i asc }
|
||||
.map { case t1 ~ t2 ~ i ~ t3 ~ t4 => (t1, t3, t4) }
|
||||
.list
|
||||
}
|
||||
|
||||
@@ -908,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(
|
||||
|
||||
@@ -8,6 +8,7 @@ import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.{JGitUtil, LockUtil}
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import org.eclipse.jgit.merge.{MergeStrategy, Merger, RecursiveMerger}
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.transport.RefSpec
|
||||
@@ -167,7 +168,8 @@ trait MergeService {
|
||||
remoteBranch: String,
|
||||
loginAccount: Account,
|
||||
message: String,
|
||||
pullreq: Option[PullRequest]
|
||||
pullreq: Option[PullRequest],
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): Option[ObjectId] = {
|
||||
val localUserName = localRepository.owner
|
||||
val localRepositoryName = localRepository.name
|
||||
@@ -212,7 +214,7 @@ trait MergeService {
|
||||
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, localUserName, localRepositoryName)
|
||||
.foreach { issueId =>
|
||||
getIssue(localRepository.owner, localRepository.name, issueId.toString).foreach { issue =>
|
||||
callIssuesWebHook("closed", localRepository, issue, loginAccount)
|
||||
callIssuesWebHook("closed", localRepository, issue, loginAccount, settings)
|
||||
PluginRegistry().getIssueHooks
|
||||
.foreach(
|
||||
_.closedByCommitComment(issue, localRepository, commit.fullMessage, loginAccount)
|
||||
@@ -223,7 +225,7 @@ trait MergeService {
|
||||
}
|
||||
|
||||
pullreq.foreach { pullreq =>
|
||||
callWebHookOf(localRepository.owner, localRepository.name, WebHook.Push) {
|
||||
callWebHookOf(localRepository.owner, localRepository.name, WebHook.Push, settings) {
|
||||
for {
|
||||
ownerAccount <- getAccountByUserName(localRepository.owner)
|
||||
} yield {
|
||||
@@ -251,7 +253,8 @@ trait MergeService {
|
||||
loginAccount: Account,
|
||||
message: String,
|
||||
strategy: String,
|
||||
isDraft: Boolean
|
||||
isDraft: Boolean,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context, context: Context): Either[String, ObjectId] = {
|
||||
if (!isDraft) {
|
||||
if (repository.repository.options.mergeOptions.split(",").contains(strategy)) {
|
||||
@@ -333,7 +336,7 @@ trait MergeService {
|
||||
repository.name
|
||||
).foreach { issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
callIssuesWebHook("closed", repository, issue, loginAccount)
|
||||
callIssuesWebHook("closed", repository, issue, loginAccount, context.settings)
|
||||
PluginRegistry().getIssueHooks
|
||||
.foreach(_.closedByCommitComment(issue, repository, commit.fullMessage, loginAccount))
|
||||
}
|
||||
@@ -347,7 +350,7 @@ trait MergeService {
|
||||
repository.name
|
||||
).foreach { issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
callIssuesWebHook("closed", repository, issue, loginAccount)
|
||||
callIssuesWebHook("closed", repository, issue, loginAccount, context.settings)
|
||||
PluginRegistry().getIssueHooks
|
||||
.foreach(_.closedByCommitComment(issue, repository, issueContent, loginAccount))
|
||||
}
|
||||
@@ -355,16 +358,23 @@ trait MergeService {
|
||||
closeIssuesFromMessage(message, loginAccount.userName, repository.owner, repository.name)
|
||||
.foreach { issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
callIssuesWebHook("closed", repository, issue, loginAccount)
|
||||
callIssuesWebHook("closed", repository, issue, loginAccount, context.settings)
|
||||
PluginRegistry().getIssueHooks
|
||||
.foreach(_.closedByCommitComment(issue, repository, issueContent, loginAccount))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callPullRequestWebHook("closed", repository, issueId, context.loginAccount.get)
|
||||
callPullRequestWebHook("closed", repository, issueId, context.loginAccount.get, context.settings)
|
||||
|
||||
updatePullRequests(repository.owner, repository.name, pullreq.branch, loginAccount, "closed")
|
||||
updatePullRequests(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
pullreq.branch,
|
||||
loginAccount,
|
||||
"closed",
|
||||
settings
|
||||
)
|
||||
|
||||
// call hooks
|
||||
PluginRegistry().getPullRequestHooks.foreach { h =>
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.api.JsonFormat
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.JGitUtil
|
||||
@@ -106,7 +107,8 @@ trait PullRequestService {
|
||||
commitIdFrom: String,
|
||||
commitIdTo: String,
|
||||
isDraft: Boolean,
|
||||
loginAccount: Account
|
||||
loginAccount: Account,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, context: Context): Unit = {
|
||||
getIssue(originRepository.owner, originRepository.name, issueId.toString).foreach { baseIssue =>
|
||||
PullRequests insert PullRequest(
|
||||
@@ -142,7 +144,7 @@ trait PullRequestService {
|
||||
)
|
||||
|
||||
// call web hook
|
||||
callPullRequestWebHook("opened", originRepository, issueId, loginAccount)
|
||||
callPullRequestWebHook("opened", originRepository, issueId, loginAccount, settings)
|
||||
|
||||
getIssue(originRepository.owner, originRepository.name, issueId.toString) foreach { issue =>
|
||||
// extract references and create refer comment
|
||||
@@ -226,7 +228,14 @@ trait PullRequestService {
|
||||
/**
|
||||
* Fetch pull request contents into refs/pull/${issueId}/head and update pull request table.
|
||||
*/
|
||||
def updatePullRequests(owner: String, repository: String, branch: String, loginAccount: Account, action: String)(
|
||||
def updatePullRequests(
|
||||
owner: String,
|
||||
repository: String,
|
||||
branch: String,
|
||||
loginAccount: Account,
|
||||
action: String,
|
||||
settings: SystemSettings
|
||||
)(
|
||||
implicit s: Session,
|
||||
c: JsonFormat.Context
|
||||
): Unit = {
|
||||
@@ -275,7 +284,8 @@ trait PullRequestService {
|
||||
action,
|
||||
getRepository(owner, repository).get,
|
||||
pullreq.requestBranch,
|
||||
loginAccount
|
||||
loginAccount,
|
||||
settings
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import gitbucket.core.model.{Account, WebHook}
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import gitbucket.core.service.WebHookService.WebHookPushPayload
|
||||
import gitbucket.core.util.Directory.getRepositoryDir
|
||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||
@@ -12,6 +13,7 @@ import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder}
|
||||
import org.eclipse.jgit.lib._
|
||||
import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack}
|
||||
|
||||
import scala.util.Using
|
||||
|
||||
trait RepositoryCommitFileService {
|
||||
@@ -24,12 +26,13 @@ trait RepositoryCommitFileService {
|
||||
branch: String,
|
||||
path: String,
|
||||
message: String,
|
||||
loginAccount: Account
|
||||
loginAccount: Account,
|
||||
settings: SystemSettings
|
||||
)(
|
||||
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit
|
||||
)(implicit s: Session, c: JsonFormat.Context) = {
|
||||
// prepend path to the filename
|
||||
_commitFile(repository, branch, message, loginAccount, loginAccount.fullName, loginAccount.mailAddress)(f)
|
||||
_commitFile(repository, branch, message, loginAccount, loginAccount.fullName, loginAccount.mailAddress, settings)(f)
|
||||
}
|
||||
|
||||
def commitFile(
|
||||
@@ -42,7 +45,8 @@ trait RepositoryCommitFileService {
|
||||
charset: String,
|
||||
message: String,
|
||||
commit: String,
|
||||
loginAccount: Account
|
||||
loginAccount: Account,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
||||
commitFile(
|
||||
repository,
|
||||
@@ -55,7 +59,8 @@ trait RepositoryCommitFileService {
|
||||
commit,
|
||||
loginAccount,
|
||||
loginAccount.fullName,
|
||||
loginAccount.mailAddress
|
||||
loginAccount.mailAddress,
|
||||
settings
|
||||
)
|
||||
}
|
||||
|
||||
@@ -70,7 +75,8 @@ trait RepositoryCommitFileService {
|
||||
commit: String,
|
||||
loginAccount: Account,
|
||||
fullName: String,
|
||||
mailAddress: String
|
||||
mailAddress: String,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
||||
|
||||
val newPath = newFileName.map { newFileName =>
|
||||
@@ -80,7 +86,7 @@ trait RepositoryCommitFileService {
|
||||
if (path.length == 0) oldFileName else s"${path}/${oldFileName}"
|
||||
}
|
||||
|
||||
_commitFile(repository, branch, message, loginAccount, fullName, mailAddress) {
|
||||
_commitFile(repository, branch, message, loginAccount, fullName, mailAddress, settings) {
|
||||
case (git, headTip, builder, inserter) =>
|
||||
if (headTip.getName == commit) {
|
||||
val permission = JGitUtil
|
||||
@@ -111,7 +117,8 @@ trait RepositoryCommitFileService {
|
||||
message: String,
|
||||
loginAccount: Account,
|
||||
committerName: String,
|
||||
committerMailAddress: String
|
||||
committerMailAddress: String,
|
||||
settings: SystemSettings
|
||||
)(
|
||||
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit
|
||||
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
||||
@@ -165,7 +172,7 @@ trait RepositoryCommitFileService {
|
||||
refUpdate.update()
|
||||
|
||||
// update pull request
|
||||
updatePullRequests(repository.owner, repository.name, branch, loginAccount, "synchronize")
|
||||
updatePullRequests(repository.owner, repository.name, branch, loginAccount, "synchronize", settings)
|
||||
|
||||
// record activity
|
||||
val commitInfo = new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
||||
@@ -178,7 +185,7 @@ trait RepositoryCommitFileService {
|
||||
if (branch == repository.repository.defaultBranch) {
|
||||
closeIssuesFromMessage(message, committerName, repository.owner, repository.name).foreach { issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
callIssuesWebHook("closed", repository, issue, loginAccount)
|
||||
callIssuesWebHook("closed", repository, issue, loginAccount, settings)
|
||||
PluginRegistry().getIssueHooks
|
||||
.foreach(_.closedByCommitComment(issue, repository, message, loginAccount))
|
||||
}
|
||||
@@ -191,7 +198,7 @@ trait RepositoryCommitFileService {
|
||||
}
|
||||
|
||||
val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Push) {
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Push, settings) {
|
||||
getAccountByUserName(repository.owner).map { ownerAccount =>
|
||||
WebHookPushPayload(
|
||||
git,
|
||||
|
||||
@@ -459,6 +459,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 +471,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) &&
|
||||
|
||||
@@ -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,7 +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)
|
||||
@@ -90,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(
|
||||
@@ -146,7 +171,15 @@ trait SystemSettingsService {
|
||||
None
|
||||
},
|
||||
getValue(props, SkinName, "skin-blue"),
|
||||
getValue(props, ShowMailAddress, false)
|
||||
getOptionValue(props, UserDefinedCss, None),
|
||||
getValue(props, ShowMailAddress, false),
|
||||
WebHook(getValue(props, WebHookBlockPrivateAddress, false), getSeqValue(props, WebHookWhitelist, "")),
|
||||
Upload(
|
||||
getValue(props, UploadMaxFileSize, 3 * 1024 * 1024),
|
||||
getValue(props, UploadTimeout, 3 * 10000),
|
||||
getValue(props, UploadLargeMaxFileSize, 3 * 1024 * 1024),
|
||||
getValue(props, UploadLargeTimeout, 3 * 10000)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -164,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],
|
||||
@@ -175,7 +210,10 @@ object SystemSettingsService {
|
||||
oidcAuthentication: Boolean,
|
||||
oidc: Option[OIDC],
|
||||
skinName: String,
|
||||
showMailAddress: Boolean
|
||||
userDefinedCss: Option[String],
|
||||
showMailAddress: Boolean,
|
||||
webHook: WebHook,
|
||||
upload: Upload
|
||||
) {
|
||||
|
||||
def baseUrl(request: HttpServletRequest): String =
|
||||
@@ -194,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],
|
||||
@@ -247,12 +294,14 @@ 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 Lfs(serverUrl: Option[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
|
||||
@@ -263,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"
|
||||
@@ -297,15 +352,17 @@ 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 = {
|
||||
getSystemProperty(key).getOrElse(getEnvironmentVariable(key).getOrElse {
|
||||
getConfigValue(key).getOrElse {
|
||||
defining(props.getProperty(key)) { value =>
|
||||
if (value == null || value.isEmpty) {
|
||||
default
|
||||
@@ -313,11 +370,21 @@ object SystemSettingsService {
|
||||
convertType(value).asInstanceOf[A]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private def getSeqValue[A: ClassTag](props: java.util.Properties, key: String, default: A): Seq[A] = {
|
||||
getValue[String](props, key, "").split("\n").toIndexedSeq.map { value =>
|
||||
if (value == null || value.isEmpty) {
|
||||
default
|
||||
} else {
|
||||
convertType(value).asInstanceOf[A]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def getOptionValue[A: ClassTag](props: java.util.Properties, key: String, default: Option[A]): Option[A] = {
|
||||
getSystemProperty(key).orElse(getEnvironmentVariable(key).orElse {
|
||||
getConfigValue(key).orElse {
|
||||
defining(props.getProperty(key)) { value =>
|
||||
if (value == null || value.isEmpty) {
|
||||
default
|
||||
@@ -325,7 +392,7 @@ object SystemSettingsService {
|
||||
Some(convertType(value)).asInstanceOf[Option[A]]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,24 +3,26 @@ package gitbucket.core.service
|
||||
import fr.brouillard.oss.security.xhub.XHub
|
||||
import fr.brouillard.oss.security.xhub.XHub.{XHubConverter, XHubDigest}
|
||||
import gitbucket.core.api._
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.model.{
|
||||
Account,
|
||||
AccountWebHook,
|
||||
AccountWebHookEvent,
|
||||
CommitComment,
|
||||
Issue,
|
||||
IssueComment,
|
||||
Label,
|
||||
PullRequest,
|
||||
WebHook,
|
||||
RepositoryWebHook,
|
||||
RepositoryWebHookEvent,
|
||||
AccountWebHook,
|
||||
AccountWebHookEvent
|
||||
WebHook
|
||||
}
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import org.apache.http.client.utils.URLEncodedUtils
|
||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||
import gitbucket.core.util.{RepositoryName, StringUtil}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.{HttpClientUtil, RepositoryName, StringUtil}
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import org.apache.http.NameValuePair
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity
|
||||
@@ -34,6 +36,7 @@ import scala.util.{Failure, Success}
|
||||
import org.apache.http.HttpRequest
|
||||
import org.apache.http.HttpResponse
|
||||
import gitbucket.core.model.WebHookContentType
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import org.apache.http.client.entity.EntityBuilder
|
||||
import org.apache.http.entity.ContentType
|
||||
|
||||
@@ -201,20 +204,28 @@ trait WebHookService {
|
||||
def deleteAccountWebHook(owner: String, url: String)(implicit s: Session): Unit =
|
||||
AccountWebHooks.filter(_.byPrimaryKey(owner, url)).delete
|
||||
|
||||
def callWebHookOf(owner: String, repository: String, event: WebHook.Event)(
|
||||
def callWebHookOf(owner: String, repository: String, event: WebHook.Event, settings: SystemSettings)(
|
||||
makePayload: => Option[WebHookPayload]
|
||||
)(implicit s: Session, c: JsonFormat.Context): Unit = {
|
||||
val webHooks = getWebHooksByEvent(owner, repository, event)
|
||||
if (webHooks.nonEmpty) {
|
||||
makePayload.map(callWebHook(event, webHooks, _))
|
||||
makePayload.map(callWebHook(event, webHooks, _, settings))
|
||||
}
|
||||
val accountWebHooks = getAccountWebHooksByEvent(owner, event)
|
||||
if (accountWebHooks.nonEmpty) {
|
||||
makePayload.map(callWebHook(event, accountWebHooks, _))
|
||||
makePayload.map(callWebHook(event, accountWebHooks, _, settings))
|
||||
}
|
||||
}
|
||||
|
||||
def callWebHook(event: WebHook.Event, webHooks: List[WebHook], payload: WebHookPayload)(
|
||||
private def validateTargetAddress(settings: SystemSettings, url: String): Boolean = {
|
||||
val host = new java.net.URL(url).getHost
|
||||
|
||||
!settings.webHook.blockPrivateAddress ||
|
||||
!HttpClientUtil.isPrivateAddress(host) ||
|
||||
settings.webHook.whitelist.exists(range => HttpClientUtil.inIpRange(range, host))
|
||||
}
|
||||
|
||||
def callWebHook(event: WebHook.Event, webHooks: List[WebHook], payload: WebHookPayload, settings: SystemSettings)(
|
||||
implicit c: JsonFormat.Context
|
||||
): List[(WebHook, String, Future[HttpRequest], Future[HttpResponse])] = {
|
||||
import org.apache.http.impl.client.HttpClientBuilder
|
||||
@@ -234,6 +245,9 @@ trait WebHookService {
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (!validateTargetAddress(settings, webHook.url)) {
|
||||
throw new IllegalArgumentException(s"Illegal address: ${webHook.url}")
|
||||
}
|
||||
val httpClient = HttpClientBuilder.create.useSystemProperties.addInterceptorLast(itcp).build
|
||||
logger.debug(s"start web hook invocation for ${webHook.url}")
|
||||
val httpPost = new HttpPost(webHook.url)
|
||||
@@ -302,7 +316,6 @@ trait WebHookService {
|
||||
} else {
|
||||
Nil
|
||||
}
|
||||
// logger.debug("end callWebHook")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,10 +328,12 @@ trait WebHookPullRequestService extends WebHookService {
|
||||
action: String,
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
issue: Issue,
|
||||
sender: Account
|
||||
sender: Account,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, context: JsonFormat.Context): Unit = {
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Issues) {
|
||||
val users = getAccountsByUserNames(Set(repository.owner, issue.openedUserName), Set(sender))
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Issues, settings) {
|
||||
val users =
|
||||
getAccountsByUserNames(Set(repository.owner, issue.openedUserName) ++ issue.assignedUserName, Set(sender))
|
||||
for {
|
||||
repoOwner <- users.get(repository.owner)
|
||||
issueUser <- users.get(issue.openedUserName)
|
||||
@@ -331,6 +346,7 @@ trait WebHookPullRequestService extends WebHookService {
|
||||
issue,
|
||||
RepositoryName(repository),
|
||||
ApiUser(issueUser),
|
||||
issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)),
|
||||
getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||
.map(ApiLabel(_, RepositoryName(repository)))
|
||||
),
|
||||
@@ -344,10 +360,11 @@ trait WebHookPullRequestService extends WebHookService {
|
||||
action: String,
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
issueId: Int,
|
||||
sender: Account
|
||||
sender: Account,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): Unit = {
|
||||
import WebHookService._
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.PullRequest) {
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.PullRequest, settings) {
|
||||
for {
|
||||
(issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId)
|
||||
users = getAccountsByUserNames(
|
||||
@@ -406,7 +423,8 @@ trait WebHookPullRequestService extends WebHookService {
|
||||
action: String,
|
||||
requestRepository: RepositoryService.RepositoryInfo,
|
||||
requestBranch: String,
|
||||
sender: Account
|
||||
sender: Account,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): Unit = {
|
||||
import WebHookService._
|
||||
for {
|
||||
@@ -437,7 +455,7 @@ trait WebHookPullRequestService extends WebHookService {
|
||||
mergedComment = getMergedComment(baseRepo.owner, baseRepo.name, issue.issueId)
|
||||
)
|
||||
|
||||
callWebHook(WebHook.PullRequest, webHooks, payload)
|
||||
callWebHook(WebHook.PullRequest, webHooks, payload, settings)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,10 +469,11 @@ trait WebHookPullRequestReviewCommentService extends WebHookService {
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
issue: Issue,
|
||||
pullRequest: PullRequest,
|
||||
sender: Account
|
||||
sender: Account,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): Unit = {
|
||||
import WebHookService._
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.PullRequestReviewComment) {
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.PullRequestReviewComment, settings) {
|
||||
val users =
|
||||
getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set(sender))
|
||||
for {
|
||||
@@ -496,18 +515,20 @@ trait WebHookIssueCommentService extends WebHookPullRequestService {
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
issue: Issue,
|
||||
issueCommentId: Int,
|
||||
sender: Account
|
||||
sender: Account,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): Unit = {
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.IssueComment) {
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.IssueComment, settings) {
|
||||
for {
|
||||
issueComment <- getComment(repository.owner, repository.name, issueCommentId.toString())
|
||||
users = getAccountsByUserNames(
|
||||
Set(issue.openedUserName, repository.owner, issueComment.commentedUserName),
|
||||
Set(issue.openedUserName, repository.owner, issueComment.commentedUserName) ++ issue.assignedUserName,
|
||||
Set(sender)
|
||||
)
|
||||
issueUser <- users.get(issue.openedUserName)
|
||||
repoOwner <- users.get(repository.owner)
|
||||
commenter <- users.get(issueComment.commentedUserName)
|
||||
assignedUser = issue.assignedUserName.flatMap(users.get(_))
|
||||
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||
} yield {
|
||||
WebHookIssueCommentPayload(
|
||||
@@ -517,6 +538,7 @@ trait WebHookIssueCommentService extends WebHookPullRequestService {
|
||||
commentUser = commenter,
|
||||
repository = repository,
|
||||
repositoryUser = repoOwner,
|
||||
assignedUser = assignedUser,
|
||||
sender = sender,
|
||||
labels = labels
|
||||
)
|
||||
@@ -690,6 +712,7 @@ object WebHookService {
|
||||
commentUser: Account,
|
||||
repository: RepositoryInfo,
|
||||
repositoryUser: Account,
|
||||
assignedUser: Option[Account],
|
||||
sender: Account,
|
||||
labels: List[Label]
|
||||
): WebHookIssueCommentPayload =
|
||||
@@ -700,6 +723,7 @@ object WebHookService {
|
||||
issue,
|
||||
RepositoryName(repository),
|
||||
ApiUser(issueUser),
|
||||
assignedUser.map(ApiUser(_)),
|
||||
labels.map(ApiLabel(_, RepositoryName(repository)))
|
||||
),
|
||||
comment =
|
||||
|
||||
@@ -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(())
|
||||
@@ -53,8 +53,9 @@ class ApiAuthenticationFilter extends Filter with AccessTokenService with Accoun
|
||||
}
|
||||
|
||||
def doBasicAuth(auth: String, settings: SystemSettings, request: HttpServletRequest): Option[Account] = {
|
||||
implicit val session = request.getAttribute(Keys.Request.DBSession).asInstanceOf[slick.jdbc.JdbcBackend#Session]
|
||||
val Array(username, password) = AuthUtil.decodeAuthHeader(auth).split(":", 2)
|
||||
authenticate(settings, username, password)
|
||||
Database() withTransaction { implicit session =>
|
||||
authenticate(settings, username, password)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,7 +239,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
with MilestonesService
|
||||
with WebHookPullRequestService
|
||||
with WebHookPullRequestReviewCommentService
|
||||
with CommitsService {
|
||||
with CommitsService
|
||||
with SystemSettingsService {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
|
||||
private var existIds: Seq[String] = Nil
|
||||
@@ -269,6 +270,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
}
|
||||
|
||||
def onPostReceive(receivePack: ReceivePack, commands: java.util.Collection[ReceiveCommand]): Unit = {
|
||||
val settings = loadSystemSettings()
|
||||
|
||||
Database() withTransaction { implicit session =>
|
||||
try {
|
||||
Using.resource(Git.open(Directory.getRepositoryDir(owner, repository))) { git =>
|
||||
@@ -317,7 +320,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
getAccountByUserName(pusher).foreach { pusherAccount =>
|
||||
closeIssuesFromMessage(commit.fullMessage, pusher, owner, repository).foreach { issueId =>
|
||||
getIssue(owner, repository, issueId.toString).foreach { issue =>
|
||||
callIssuesWebHook("closed", repositoryInfo, issue, pusherAccount)
|
||||
callIssuesWebHook("closed", repositoryInfo, issue, pusherAccount, settings)
|
||||
PluginRegistry().getIssueHooks
|
||||
.foreach(_.closedByCommitComment(issue, repositoryInfo, commit.fullMessage, pusherAccount))
|
||||
}
|
||||
@@ -337,7 +340,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
}.isDefined) {
|
||||
markMergeAndClosePullRequest(pusher, owner, repository, pull)
|
||||
getAccountByUserName(pusher).foreach { pusherAccount =>
|
||||
callPullRequestWebHook("closed", repositoryInfo, pull.issueId, pusherAccount)
|
||||
callPullRequestWebHook("closed", repositoryInfo, pull.issueId, pusherAccount, settings)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -365,14 +368,14 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
case ReceiveCommand.Type.CREATE | ReceiveCommand.Type.UPDATE |
|
||||
ReceiveCommand.Type.UPDATE_NONFASTFORWARD =>
|
||||
getAccountByUserName(pusher).foreach { pusherAccount =>
|
||||
updatePullRequests(owner, repository, branchName, pusherAccount, "synchronize")
|
||||
updatePullRequests(owner, repository, branchName, pusherAccount, "synchronize", settings)
|
||||
}
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
|
||||
// call web hook
|
||||
callWebHookOf(owner, repository, WebHook.Push) {
|
||||
callWebHookOf(owner, repository, WebHook.Push, settings) {
|
||||
for {
|
||||
pusherAccount <- getAccountByUserName(pusher)
|
||||
ownerAccount <- getAccountByUserName(owner)
|
||||
@@ -390,7 +393,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
}
|
||||
}
|
||||
if (command.getType == ReceiveCommand.Type.CREATE) {
|
||||
callWebHookOf(owner, repository, WebHook.Create) {
|
||||
callWebHookOf(owner, repository, WebHook.Create, settings) {
|
||||
for {
|
||||
pusherAccount <- getAccountByUserName(pusher)
|
||||
ownerAccount <- getAccountByUserName(owner)
|
||||
@@ -428,11 +431,14 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
extends PostReceiveHook
|
||||
with WebHookService
|
||||
with AccountService
|
||||
with RepositoryService {
|
||||
with RepositoryService
|
||||
with SystemSettingsService {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[WikiCommitHook])
|
||||
|
||||
override def onPostReceive(receivePack: ReceivePack, commands: util.Collection[ReceiveCommand]): Unit = {
|
||||
val settings = loadSystemSettings()
|
||||
|
||||
Database() withTransaction { implicit session =>
|
||||
try {
|
||||
commands.asScala.headOption.foreach { command =>
|
||||
@@ -468,7 +474,7 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
(commits.head._1, fileName, commits.last._3)
|
||||
}
|
||||
|
||||
callWebHookOf(owner, repository, WebHook.Gollum) {
|
||||
callWebHookOf(owner, repository, WebHook.Gollum, settings) {
|
||||
for {
|
||||
pusherAccount <- getAccountByUserName(pusher)
|
||||
repositoryUser <- getAccountByUserName(owner)
|
||||
|
||||
41
src/main/scala/gitbucket/core/util/ConfigUtil.scala
Normal file
41
src/main/scala/gitbucket/core/util/ConfigUtil.scala
Normal file
@@ -0,0 +1,41 @@
|
||||
package gitbucket.core.util
|
||||
|
||||
import gitbucket.core.util.SyntaxSugars.defining
|
||||
|
||||
import scala.reflect.ClassTag
|
||||
|
||||
object ConfigUtil {
|
||||
|
||||
def getConfigValue[A: ClassTag](key: String): Option[A] = {
|
||||
getSystemProperty(key).orElse(getEnvironmentVariable(key))
|
||||
}
|
||||
|
||||
def getEnvironmentVariable[A: ClassTag](key: String): Option[A] = {
|
||||
val name = (if (key.startsWith("gitbucket.")) "" else "GITBUCKET_") + key.toUpperCase.replace('.', '_')
|
||||
val value = System.getenv(name)
|
||||
if (value != null && value.nonEmpty) {
|
||||
Some(convertType(value))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def getSystemProperty[A: ClassTag](key: String): Option[A] = {
|
||||
val name = if (key.startsWith("gitbucket.")) key else "gitbucket." + key
|
||||
val value = System.getProperty(name)
|
||||
if (value != null && value.nonEmpty) {
|
||||
Some(convertType(value))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def convertType[A: ClassTag](value: String): A =
|
||||
defining(implicitly[ClassTag[A]].runtimeClass) { c =>
|
||||
if (c == classOf[Boolean]) value.toBoolean
|
||||
else if (c == classOf[Long]) value.toLong
|
||||
else if (c == classOf[Int]) value.toInt
|
||||
else value
|
||||
}.asInstanceOf[A]
|
||||
|
||||
}
|
||||
@@ -6,9 +6,8 @@ import java.io.File
|
||||
import Directory._
|
||||
import ConfigUtil._
|
||||
import com.github.takezoe.slick.blocking.{BlockingH2Driver, BlockingJdbcProfile, BlockingMySQLDriver}
|
||||
import gitbucket.core.util.SyntaxSugars.defining
|
||||
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
|
||||
@@ -54,16 +53,14 @@ object DatabaseConfig {
|
||||
lazy val minimumIdle: Option[Int] = getOptionValue("db.minimumIdle", config.getInt)
|
||||
lazy val maximumPoolSize: Option[Int] = getOptionValue("db.maximumPoolSize", config.getInt)
|
||||
|
||||
private def getValue[T](path: String, f: String => T): T = {
|
||||
getSystemProperty(path).getOrElse(getEnvironmentVariable(path).getOrElse {
|
||||
f(path)
|
||||
})
|
||||
private def getValue[T: ClassTag](path: String, f: String => T): T = {
|
||||
getConfigValue(path).getOrElse(f(path))
|
||||
}
|
||||
|
||||
private def getOptionValue[T](path: String, f: String => T): Option[T] = {
|
||||
getSystemProperty(path).orElse(getEnvironmentVariable(path).orElse {
|
||||
private def getOptionValue[T: ClassTag](path: String, f: String => T): Option[T] = {
|
||||
getConfigValue(path).orElse {
|
||||
if (config.hasPath(path)) Some(f(path)) else None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -81,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 {
|
||||
@@ -100,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
|
||||
@@ -114,33 +119,3 @@ object DatabaseType {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object ConfigUtil {
|
||||
|
||||
def getEnvironmentVariable[A](key: String): Option[A] = {
|
||||
val value = System.getenv("GITBUCKET_" + key.toUpperCase.replace('.', '_'))
|
||||
if (value != null && value.nonEmpty) {
|
||||
Some(convertType(value)).asInstanceOf[Option[A]]
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def getSystemProperty[A](key: String): Option[A] = {
|
||||
val value = System.getProperty("gitbucket." + key)
|
||||
if (value != null && value.nonEmpty) {
|
||||
Some(convertType(value)).asInstanceOf[Option[A]]
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def convertType[A: ClassTag](value: String) =
|
||||
defining(implicitly[ClassTag[A]].runtimeClass) { c =>
|
||||
if (c == classOf[Boolean]) value.toBoolean
|
||||
else if (c == classOf[Long]) value.toLong
|
||||
else if (c == classOf[Int]) value.toInt
|
||||
else value
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -62,7 +62,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")
|
||||
|
||||
@@ -27,10 +27,12 @@ object FileUtil {
|
||||
}
|
||||
|
||||
def getSafeMimeType(name: String): String = {
|
||||
getMimeType(name).replace("text/html", "text/plain")
|
||||
getMimeType(name)
|
||||
.replace("text/html", "text/plain")
|
||||
.replace("image/svg+xml", "text/plain; charset=UTF-8")
|
||||
}
|
||||
|
||||
def isImage(name: String): Boolean = getMimeType(name).startsWith("image/")
|
||||
def isImage(name: String): Boolean = getSafeMimeType(name).startsWith("image/")
|
||||
|
||||
def isLarge(size: Long): Boolean = (size > 1024 * 1000)
|
||||
|
||||
@@ -90,10 +92,4 @@ object FileUtil {
|
||||
name
|
||||
}
|
||||
|
||||
lazy val MaxFileSize =
|
||||
if (System.getProperty("gitbucket.maxFileSize") != null)
|
||||
System.getProperty("gitbucket.maxFileSize").toLong
|
||||
else
|
||||
3 * 1024 * 1024
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package gitbucket.core.util
|
||||
|
||||
import java.net.{InetAddress, URL}
|
||||
|
||||
import gitbucket.core.service.SystemSettingsService
|
||||
import org.apache.commons.net.util.SubnetUtils
|
||||
import org.apache.http.HttpHost
|
||||
import org.apache.http.auth.{AuthScope, UsernamePasswordCredentials}
|
||||
import org.apache.http.impl.client.{BasicCredentialsProvider, CloseableHttpClient, HttpClientBuilder}
|
||||
@@ -32,4 +35,19 @@ object HttpClientUtil {
|
||||
}
|
||||
}
|
||||
|
||||
def isPrivateAddress(address: String): Boolean = {
|
||||
val ipAddress = InetAddress.getByName(address)
|
||||
ipAddress.isSiteLocalAddress || ipAddress.isLinkLocalAddress || ipAddress.isLoopbackAddress
|
||||
}
|
||||
|
||||
def inIpRange(ipRange: String, ipAddress: String): Boolean = {
|
||||
if (ipRange.contains('/')) {
|
||||
val utils = new SubnetUtils(ipRange)
|
||||
utils.setInclusiveHostCount(true)
|
||||
utils.getInfo.isInRange(ipAddress)
|
||||
} else {
|
||||
ipRange == ipAddress
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ trait Validations {
|
||||
/**
|
||||
* Constraint for the identifier such as user name or page name.
|
||||
*/
|
||||
def identifier: Constraint = new Constraint() {
|
||||
val identifier: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
if (!value.matches("[a-zA-Z0-9\\-_.]+")) {
|
||||
Some(s"${name} contains invalid character.")
|
||||
@@ -19,22 +19,10 @@ trait Validations {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constraint for the password.
|
||||
*/
|
||||
def password: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
if (System.getProperty("gitbucket.validate.password") != "false" && !value.matches("[a-zA-Z0-9\\-_.]+")) {
|
||||
Some(s"${name} contains invalid character.")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constraint for the repository identifier.
|
||||
*/
|
||||
def repository: Constraint = new Constraint() {
|
||||
val repository: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
if (!value.matches("[a-zA-Z0-9\\-\\+_.]+")) {
|
||||
Some(s"${name} contains invalid character.")
|
||||
@@ -48,7 +36,7 @@ trait Validations {
|
||||
/**
|
||||
* Constraint for the color pattern.
|
||||
*/
|
||||
def color = pattern("#[0-9a-fA-F]{6}")
|
||||
val color = pattern("#[0-9a-fA-F]{6}")
|
||||
|
||||
/**
|
||||
* ValueType for the java.util.Date property.
|
||||
|
||||
@@ -6,6 +6,7 @@ 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
|
||||
import gitbucket.core.plugin.{PluginRegistry, RenderRequest}
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.service.{RepositoryService, RequestCache}
|
||||
@@ -273,6 +274,18 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
*/
|
||||
def url(userName: String)(implicit context: Context): String = s"${context.path}/${encodeRefName(userName)}"
|
||||
|
||||
/**
|
||||
* Generates the url to the pull request base branch.
|
||||
*/
|
||||
def basePRBranchUrl(pullreq: PullRequest)(implicit context: Context): String =
|
||||
s"${context.path}/${encodeRefName(pullreq.userName)}/${encodeRefName(pullreq.repositoryName)}/tree/${encodeRefName(pullreq.branch)}"
|
||||
|
||||
/**
|
||||
* Generates the url to the pull request branch.
|
||||
*/
|
||||
def requestPRBranchUrl(pullreq: PullRequest)(implicit context: Context): String =
|
||||
s"${context.path}/${encodeRefName(pullreq.requestUserName)}/${encodeRefName(pullreq.repositoryName)}/tree/${encodeRefName(pullreq.requestBranch)}"
|
||||
|
||||
/**
|
||||
* Returns the url to the root of assets.
|
||||
*/
|
||||
@@ -481,4 +494,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,13 +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>
|
||||
<li><a href="#plugins">Plugins</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>
|
||||
@@ -31,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,122 +280,18 @@
|
||||
</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>
|
||||
</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 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>
|
||||
<script>
|
||||
@@ -292,57 +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();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -65,7 +65,8 @@ $(function(){
|
||||
}
|
||||
@dropzone(clickable: Boolean, textareaId: Option[String]) = {
|
||||
url: '@context.path/upload/file/@repository.owner/@repository.name',
|
||||
maxFilesize: @{FileUtil.MaxFileSize / 1024 / 1024},
|
||||
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>
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
<table class="table table-bordered diff-outside" commitId="@newCommitId" fileName="@diff.newPath" data-diff-id="@i">
|
||||
<tr>
|
||||
<th style="font-weight: normal;" class="box-header">
|
||||
<i class="fa fa-chevron-down rotate" data-toggle="collapse" data-target=".diff-collapse-@i" aria-hidden="true" style="cursor: pointer; font-size: 12px"></i>
|
||||
@if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){
|
||||
@if(newCommitId.isDefined){
|
||||
<div class="pull-right align-right">
|
||||
@@ -92,7 +93,7 @@
|
||||
}
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr class="diff-collapse-@i collapse in">
|
||||
<td style="padding: 0;">
|
||||
@if(diff.oldObjectId == diff.newObjectId){
|
||||
@if(diff.oldPath != diff.newPath){
|
||||
@@ -103,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">
|
||||
@@ -139,6 +140,17 @@
|
||||
<script type="text/javascript" src="@helpers.assets("/vendors/jsdifflib/difflib.js")"></script>
|
||||
<link href="@helpers.assets("/vendors/jsdifflib/diffview.css")" type="text/css" rel="stylesheet" />
|
||||
<script>
|
||||
|
||||
$(".rotate").click(function(){
|
||||
if($(this).hasClass('fa-chevron-right')) {
|
||||
$(this).removeClass('fa-chevron-right');
|
||||
$(this).addClass('fa-chevron-down');
|
||||
} else {
|
||||
$(this).removeClass('fa-chevron-down');
|
||||
$(this).addClass('fa-chevron-right');
|
||||
}
|
||||
});
|
||||
|
||||
$(function(){
|
||||
@if(showIndex){
|
||||
$('#toggle-file-list').click(function(){
|
||||
|
||||
@@ -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;">
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
<input type="submit" class="btn btn-success" value="Create milestone"/>
|
||||
} else {
|
||||
@if(milestone.get.closedDate.isDefined){
|
||||
<input type="button" class="btn btn-default" value="Open" id="open" onclick="location.href='@helpers.url(repository)/issues/milestones/@milestone.get.milestoneId/close';"/>
|
||||
<input type="button" class="btn btn-default" value="Open" id="open" onclick="location.href='@helpers.url(repository)/issues/milestones/@milestone.get.milestoneId/open';"/>
|
||||
} else {
|
||||
<input type="button" class="btn btn-default" value="Close" id="close" onclick="location.href='@helpers.url(repository)/issues/milestones/@milestone.get.milestoneId/open';"/>
|
||||
<input type="button" class="btn btn-default" value="Close" id="close" onclick="location.href='@helpers.url(repository)/issues/milestones/@milestone.get.milestoneId/close';"/>
|
||||
}
|
||||
<input type="submit" class="btn btn-success" value="Update milestone"/>
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<link rel="icon" href="@helpers.assets("/common/images/gitbucket.png")" type="image/vnd.microsoft.icon" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="@helpers.assets("/vendors/google-fonts/css/source-sans-pro.css")" rel="stylesheet">
|
||||
<link href="@helpers.assets("/vendors/bootstrap-3.3.7/css/bootstrap.min.css")" rel="stylesheet">
|
||||
<link href="@helpers.assets("/vendors/bootstrap-3.4.1/css/bootstrap.min.css")" rel="stylesheet">
|
||||
<link href="@helpers.assets("/vendors/octicons-4.4.0/octicons.min.css")" rel="stylesheet">
|
||||
<link href="@helpers.assets("/vendors/bootstrap-datetimepicker-4.17.44/css/bootstrap-datetimepicker.min.css")" rel="stylesheet">
|
||||
<link href="@helpers.assets("/vendors/colorpicker/css/bootstrap-colorpicker.min.css")" rel="stylesheet">
|
||||
@@ -36,12 +36,12 @@
|
||||
<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.2.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>
|
||||
<script src="@helpers.assets("/common/js/gitbucket.js")"></script>
|
||||
<script src="@helpers.assets("/vendors/bootstrap-3.3.7/js/bootstrap.min.js")"></script>
|
||||
<script src="@helpers.assets("/vendors/bootstrap-3.4.1/js/bootstrap.min.js")"></script>
|
||||
<script src="@helpers.assets("/vendors/bootstrap3-typeahead/bootstrap3-typeahead.min.js")"></script>
|
||||
<script src="@helpers.assets("/vendors/bootstrap-datetimepicker-4.17.44/js/moment.min.js")"></script>
|
||||
<script src="@helpers.assets("/vendors/bootstrap-datetimepicker-4.17.44/js/bootstrap-datetimepicker.min.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>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.pulls.html.menu("commits", issue, pullreq, commits.flatten, comments, changedFileSize, isManageable, repository){
|
||||
<table class="table table-bordered">
|
||||
<table class="table table-bordered table-hover">
|
||||
@commits.map { day =>
|
||||
<tr>
|
||||
<th rowspan="@day.size" width="100">@helpers.date(day.head.commitTime)</th>
|
||||
|
||||
@@ -47,8 +47,8 @@
|
||||
<span class="muted">
|
||||
@helpers.user(comment.commentedUserName, styleClass = "username strong")
|
||||
merged @commits.size @helpers.plural(commits.size, "commit")
|
||||
into <code>@pullreq.userName:@pullreq.branch</code>
|
||||
from <code>@pullreq.requestUserName:@pullreq.requestBranch</code>
|
||||
into <code><a href="@helpers.basePRBranchUrl(pullreq)">@pullreq.userName:@pullreq.branch</a></code>
|
||||
from <code><a href="@helpers.requestPRBranchUrl(pullreq)">@pullreq.requestUserName:@pullreq.requestBranch</a></code>
|
||||
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
|
||||
</span>
|
||||
}.getOrElse {
|
||||
@@ -56,8 +56,8 @@
|
||||
<span class="muted">
|
||||
@helpers.user(issue.openedUserName, styleClass = "username strong")
|
||||
wants to merge @commits.size @helpers.plural(commits.size, "commit")
|
||||
into <code>@pullreq.userName:@pullreq.branch</code>
|
||||
from <code>@pullreq.requestUserName:@pullreq.requestBranch</code>
|
||||
into <code><a href="@helpers.basePRBranchUrl(pullreq)">@pullreq.userName:@pullreq.branch</a></code>
|
||||
from <code><a href="@helpers.requestPRBranchUrl(pullreq)">@pullreq.requestUserName:@pullreq.requestBranch</a></code>
|
||||
</span>
|
||||
}
|
||||
} else {
|
||||
@@ -65,8 +65,8 @@
|
||||
<span class="muted">
|
||||
@helpers.user(issue.openedUserName, styleClass = "username strong")
|
||||
wants to merge @commits.size @helpers.plural(commits.size, "commit")
|
||||
into <code>@pullreq.userName:@pullreq.branch</code>
|
||||
from <code>@pullreq.requestUserName:@pullreq.requestBranch</code>
|
||||
into <code><a href="@helpers.basePRBranchUrl(pullreq)">@pullreq.userName:@pullreq.branch</a></code>
|
||||
from <code><a href="@helpers.requestPRBranchUrl(pullreq)">@pullreq.requestUserName:@pullreq.requestBranch</a></code>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -71,8 +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)',
|
||||
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>
|
||||
|
||||
@@ -981,6 +981,12 @@ div.pullreq-info {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.collapsing {
|
||||
-webkit-transition: none;
|
||||
transition: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
/* Wiki */
|
||||
/****************************************************************************/
|
||||
|
||||
@@ -76,12 +76,12 @@ function displayErrors(data, elem){
|
||||
* @param ignoreSpace {Number} 0: include, 1: ignore
|
||||
*/
|
||||
function diffUsingJS(oldTextId, newTextId, outputId, viewType, ignoreSpace) {
|
||||
var old = $('#'+oldTextId), head = $('#' + newTextId);
|
||||
var old = $('#' + oldTextId), head = $('#' + newTextId);
|
||||
var render = new JsDiffRender({
|
||||
oldText: old.val(),
|
||||
oldTextName: old.attr('data-file-name'),
|
||||
newText: head.val(),
|
||||
newTextName: head.attr('data-file-name'),
|
||||
oldText : old.data('val'),
|
||||
oldTextName: old.data('file-name'),
|
||||
newText : head.data('val'),
|
||||
newTextName: head.data('file-name'),
|
||||
ignoreSpace: ignoreSpace,
|
||||
contextSize: 4
|
||||
});
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
1
src/main/webapp/assets/vendors/bootstrap-3.4.1/css/bootstrap.css.map
vendored
Normal file
1
src/main/webapp/assets/vendors/bootstrap-3.4.1/css/bootstrap.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
6
src/main/webapp/assets/vendors/bootstrap-3.4.1/css/bootstrap.min.css
vendored
Normal file
6
src/main/webapp/assets/vendors/bootstrap-3.4.1/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/main/webapp/assets/vendors/bootstrap-3.4.1/css/bootstrap.min.css.map
vendored
Normal file
1
src/main/webapp/assets/vendors/bootstrap-3.4.1/css/bootstrap.min.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
@@ -1,6 +1,6 @@
|
||||
/*!
|
||||
* Bootstrap v3.3.7 (http://getbootstrap.com)
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Bootstrap v3.4.1 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2019 Twitter, Inc.
|
||||
* Licensed under the MIT license
|
||||
*/
|
||||
|
||||
@@ -17,10 +17,10 @@ if (typeof jQuery === 'undefined') {
|
||||
}(jQuery);
|
||||
|
||||
/* ========================================================================
|
||||
* Bootstrap: transition.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#transitions
|
||||
* Bootstrap: transition.js v3.4.1
|
||||
* https://getbootstrap.com/docs/3.4/javascript/#transitions
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Copyright 2011-2019 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
@@ -28,7 +28,7 @@ if (typeof jQuery === 'undefined') {
|
||||
+function ($) {
|
||||
'use strict';
|
||||
|
||||
// CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
|
||||
// CSS TRANSITION SUPPORT (Shoutout: https://modernizr.com/)
|
||||
// ============================================================
|
||||
|
||||
function transitionEnd() {
|
||||
@@ -50,7 +50,7 @@ if (typeof jQuery === 'undefined') {
|
||||
return false // explicit for ie8 ( ._.)
|
||||
}
|
||||
|
||||
// http://blog.alexmaccaw.com/css-transitions
|
||||
// https://blog.alexmaccaw.com/css-transitions
|
||||
$.fn.emulateTransitionEnd = function (duration) {
|
||||
var called = false
|
||||
var $el = this
|
||||
@@ -77,10 +77,10 @@ if (typeof jQuery === 'undefined') {
|
||||
}(jQuery);
|
||||
|
||||
/* ========================================================================
|
||||
* Bootstrap: alert.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#alerts
|
||||
* Bootstrap: alert.js v3.4.1
|
||||
* https://getbootstrap.com/docs/3.4/javascript/#alerts
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Copyright 2011-2019 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
@@ -96,7 +96,7 @@ if (typeof jQuery === 'undefined') {
|
||||
$(el).on('click', dismiss, this.close)
|
||||
}
|
||||
|
||||
Alert.VERSION = '3.3.7'
|
||||
Alert.VERSION = '3.4.1'
|
||||
|
||||
Alert.TRANSITION_DURATION = 150
|
||||
|
||||
@@ -109,7 +109,8 @@ if (typeof jQuery === 'undefined') {
|
||||
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
|
||||
}
|
||||
|
||||
var $parent = $(selector === '#' ? [] : selector)
|
||||
selector = selector === '#' ? [] : selector
|
||||
var $parent = $(document).find(selector)
|
||||
|
||||
if (e) e.preventDefault()
|
||||
|
||||
@@ -172,10 +173,10 @@ if (typeof jQuery === 'undefined') {
|
||||
}(jQuery);
|
||||
|
||||
/* ========================================================================
|
||||
* Bootstrap: button.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#buttons
|
||||
* Bootstrap: button.js v3.4.1
|
||||
* https://getbootstrap.com/docs/3.4/javascript/#buttons
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Copyright 2011-2019 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
@@ -192,7 +193,7 @@ if (typeof jQuery === 'undefined') {
|
||||
this.isLoading = false
|
||||
}
|
||||
|
||||
Button.VERSION = '3.3.7'
|
||||
Button.VERSION = '3.4.1'
|
||||
|
||||
Button.DEFAULTS = {
|
||||
loadingText: 'loading...'
|
||||
@@ -298,10 +299,10 @@ if (typeof jQuery === 'undefined') {
|
||||
}(jQuery);
|
||||
|
||||
/* ========================================================================
|
||||
* Bootstrap: carousel.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#carousel
|
||||
* Bootstrap: carousel.js v3.4.1
|
||||
* https://getbootstrap.com/docs/3.4/javascript/#carousel
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Copyright 2011-2019 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
@@ -329,7 +330,7 @@ if (typeof jQuery === 'undefined') {
|
||||
.on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
|
||||
}
|
||||
|
||||
Carousel.VERSION = '3.3.7'
|
||||
Carousel.VERSION = '3.4.1'
|
||||
|
||||
Carousel.TRANSITION_DURATION = 600
|
||||
|
||||
@@ -443,7 +444,9 @@ if (typeof jQuery === 'undefined') {
|
||||
var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid"
|
||||
if ($.support.transition && this.$element.hasClass('slide')) {
|
||||
$next.addClass(type)
|
||||
$next[0].offsetWidth // force reflow
|
||||
if (typeof $next === 'object' && $next.length) {
|
||||
$next[0].offsetWidth // force reflow
|
||||
}
|
||||
$active.addClass(direction)
|
||||
$next.addClass(direction)
|
||||
$active
|
||||
@@ -505,10 +508,17 @@ if (typeof jQuery === 'undefined') {
|
||||
// =================
|
||||
|
||||
var clickHandler = function (e) {
|
||||
var href
|
||||
var $this = $(this)
|
||||
var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7
|
||||
var href = $this.attr('href')
|
||||
if (href) {
|
||||
href = href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
|
||||
}
|
||||
|
||||
var target = $this.attr('data-target') || href
|
||||
var $target = $(document).find(target)
|
||||
|
||||
if (!$target.hasClass('carousel')) return
|
||||
|
||||
var options = $.extend({}, $target.data(), $this.data())
|
||||
var slideIndex = $this.attr('data-slide-to')
|
||||
if (slideIndex) options.interval = false
|
||||
@@ -536,10 +546,10 @@ if (typeof jQuery === 'undefined') {
|
||||
}(jQuery);
|
||||
|
||||
/* ========================================================================
|
||||
* Bootstrap: collapse.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#collapse
|
||||
* Bootstrap: collapse.js v3.4.1
|
||||
* https://getbootstrap.com/docs/3.4/javascript/#collapse
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Copyright 2011-2019 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
@@ -567,7 +577,7 @@ if (typeof jQuery === 'undefined') {
|
||||
if (this.options.toggle) this.toggle()
|
||||
}
|
||||
|
||||
Collapse.VERSION = '3.3.7'
|
||||
Collapse.VERSION = '3.4.1'
|
||||
|
||||
Collapse.TRANSITION_DURATION = 350
|
||||
|
||||
@@ -674,7 +684,7 @@ if (typeof jQuery === 'undefined') {
|
||||
}
|
||||
|
||||
Collapse.prototype.getParent = function () {
|
||||
return $(this.options.parent)
|
||||
return $(document).find(this.options.parent)
|
||||
.find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
|
||||
.each($.proxy(function (i, element) {
|
||||
var $element = $(element)
|
||||
@@ -697,7 +707,7 @@ if (typeof jQuery === 'undefined') {
|
||||
var target = $trigger.attr('data-target')
|
||||
|| (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
|
||||
|
||||
return $(target)
|
||||
return $(document).find(target)
|
||||
}
|
||||
|
||||
|
||||
@@ -749,10 +759,10 @@ if (typeof jQuery === 'undefined') {
|
||||
}(jQuery);
|
||||
|
||||
/* ========================================================================
|
||||
* Bootstrap: dropdown.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#dropdowns
|
||||
* Bootstrap: dropdown.js v3.4.1
|
||||
* https://getbootstrap.com/docs/3.4/javascript/#dropdowns
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Copyright 2011-2019 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
@@ -769,7 +779,7 @@ if (typeof jQuery === 'undefined') {
|
||||
$(element).on('click.bs.dropdown', this.toggle)
|
||||
}
|
||||
|
||||
Dropdown.VERSION = '3.3.7'
|
||||
Dropdown.VERSION = '3.4.1'
|
||||
|
||||
function getParent($this) {
|
||||
var selector = $this.attr('data-target')
|
||||
@@ -779,7 +789,7 @@ if (typeof jQuery === 'undefined') {
|
||||
selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
|
||||
}
|
||||
|
||||
var $parent = selector && $(selector)
|
||||
var $parent = selector !== '#' ? $(document).find(selector) : null
|
||||
|
||||
return $parent && $parent.length ? $parent : $this.parent()
|
||||
}
|
||||
@@ -915,10 +925,10 @@ if (typeof jQuery === 'undefined') {
|
||||
}(jQuery);
|
||||
|
||||
/* ========================================================================
|
||||
* Bootstrap: modal.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#modals
|
||||
* Bootstrap: modal.js v3.4.1
|
||||
* https://getbootstrap.com/docs/3.4/javascript/#modals
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Copyright 2011-2019 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
@@ -930,15 +940,16 @@ if (typeof jQuery === 'undefined') {
|
||||
// ======================
|
||||
|
||||
var Modal = function (element, options) {
|
||||
this.options = options
|
||||
this.$body = $(document.body)
|
||||
this.$element = $(element)
|
||||
this.$dialog = this.$element.find('.modal-dialog')
|
||||
this.$backdrop = null
|
||||
this.isShown = null
|
||||
this.originalBodyPad = null
|
||||
this.scrollbarWidth = 0
|
||||
this.options = options
|
||||
this.$body = $(document.body)
|
||||
this.$element = $(element)
|
||||
this.$dialog = this.$element.find('.modal-dialog')
|
||||
this.$backdrop = null
|
||||
this.isShown = null
|
||||
this.originalBodyPad = null
|
||||
this.scrollbarWidth = 0
|
||||
this.ignoreBackdropClick = false
|
||||
this.fixedContent = '.navbar-fixed-top, .navbar-fixed-bottom'
|
||||
|
||||
if (this.options.remote) {
|
||||
this.$element
|
||||
@@ -949,7 +960,7 @@ if (typeof jQuery === 'undefined') {
|
||||
}
|
||||
}
|
||||
|
||||
Modal.VERSION = '3.3.7'
|
||||
Modal.VERSION = '3.4.1'
|
||||
|
||||
Modal.TRANSITION_DURATION = 300
|
||||
Modal.BACKDROP_TRANSITION_DURATION = 150
|
||||
@@ -966,7 +977,7 @@ if (typeof jQuery === 'undefined') {
|
||||
|
||||
Modal.prototype.show = function (_relatedTarget) {
|
||||
var that = this
|
||||
var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
|
||||
var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
|
||||
|
||||
this.$element.trigger(e)
|
||||
|
||||
@@ -1057,8 +1068,8 @@ if (typeof jQuery === 'undefined') {
|
||||
.off('focusin.bs.modal') // guard against infinite focus loop
|
||||
.on('focusin.bs.modal', $.proxy(function (e) {
|
||||
if (document !== e.target &&
|
||||
this.$element[0] !== e.target &&
|
||||
!this.$element.has(e.target).length) {
|
||||
this.$element[0] !== e.target &&
|
||||
!this.$element.has(e.target).length) {
|
||||
this.$element.trigger('focus')
|
||||
}
|
||||
}, this))
|
||||
@@ -1160,7 +1171,7 @@ if (typeof jQuery === 'undefined') {
|
||||
var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight
|
||||
|
||||
this.$element.css({
|
||||
paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
|
||||
paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
|
||||
paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
|
||||
})
|
||||
}
|
||||
@@ -1185,11 +1196,26 @@ if (typeof jQuery === 'undefined') {
|
||||
Modal.prototype.setScrollbar = function () {
|
||||
var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
|
||||
this.originalBodyPad = document.body.style.paddingRight || ''
|
||||
if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
|
||||
var scrollbarWidth = this.scrollbarWidth
|
||||
if (this.bodyIsOverflowing) {
|
||||
this.$body.css('padding-right', bodyPad + scrollbarWidth)
|
||||
$(this.fixedContent).each(function (index, element) {
|
||||
var actualPadding = element.style.paddingRight
|
||||
var calculatedPadding = $(element).css('padding-right')
|
||||
$(element)
|
||||
.data('padding-right', actualPadding)
|
||||
.css('padding-right', parseFloat(calculatedPadding) + scrollbarWidth + 'px')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Modal.prototype.resetScrollbar = function () {
|
||||
this.$body.css('padding-right', this.originalBodyPad)
|
||||
$(this.fixedContent).each(function (index, element) {
|
||||
var padding = $(element).data('padding-right')
|
||||
$(element).removeData('padding-right')
|
||||
element.style.paddingRight = padding ? padding : ''
|
||||
})
|
||||
}
|
||||
|
||||
Modal.prototype.measureScrollbar = function () { // thx walsh
|
||||
@@ -1207,8 +1233,8 @@ if (typeof jQuery === 'undefined') {
|
||||
|
||||
function Plugin(option, _relatedTarget) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
var data = $this.data('bs.modal')
|
||||
var $this = $(this)
|
||||
var data = $this.data('bs.modal')
|
||||
var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
|
||||
|
||||
if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
|
||||
@@ -1219,7 +1245,7 @@ if (typeof jQuery === 'undefined') {
|
||||
|
||||
var old = $.fn.modal
|
||||
|
||||
$.fn.modal = Plugin
|
||||
$.fn.modal = Plugin
|
||||
$.fn.modal.Constructor = Modal
|
||||
|
||||
|
||||
@@ -1236,10 +1262,13 @@ if (typeof jQuery === 'undefined') {
|
||||
// ==============
|
||||
|
||||
$(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
|
||||
var $this = $(this)
|
||||
var href = $this.attr('href')
|
||||
var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7
|
||||
var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
|
||||
var $this = $(this)
|
||||
var href = $this.attr('href')
|
||||
var target = $this.attr('data-target') ||
|
||||
(href && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7
|
||||
|
||||
var $target = $(document).find(target)
|
||||
var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
|
||||
|
||||
if ($this.is('a')) e.preventDefault()
|
||||
|
||||
@@ -1255,18 +1284,148 @@ if (typeof jQuery === 'undefined') {
|
||||
}(jQuery);
|
||||
|
||||
/* ========================================================================
|
||||
* Bootstrap: tooltip.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#tooltip
|
||||
* Bootstrap: tooltip.js v3.4.1
|
||||
* https://getbootstrap.com/docs/3.4/javascript/#tooltip
|
||||
* Inspired by the original jQuery.tipsy by Jason Frame
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Copyright 2011-2019 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
|
||||
+function ($) {
|
||||
'use strict';
|
||||
|
||||
var DISALLOWED_ATTRIBUTES = ['sanitize', 'whiteList', 'sanitizeFn']
|
||||
|
||||
var uriAttrs = [
|
||||
'background',
|
||||
'cite',
|
||||
'href',
|
||||
'itemtype',
|
||||
'longdesc',
|
||||
'poster',
|
||||
'src',
|
||||
'xlink:href'
|
||||
]
|
||||
|
||||
var ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i
|
||||
|
||||
var DefaultWhitelist = {
|
||||
// Global attributes allowed on any supplied element below.
|
||||
'*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],
|
||||
a: ['target', 'href', 'title', 'rel'],
|
||||
area: [],
|
||||
b: [],
|
||||
br: [],
|
||||
col: [],
|
||||
code: [],
|
||||
div: [],
|
||||
em: [],
|
||||
hr: [],
|
||||
h1: [],
|
||||
h2: [],
|
||||
h3: [],
|
||||
h4: [],
|
||||
h5: [],
|
||||
h6: [],
|
||||
i: [],
|
||||
img: ['src', 'alt', 'title', 'width', 'height'],
|
||||
li: [],
|
||||
ol: [],
|
||||
p: [],
|
||||
pre: [],
|
||||
s: [],
|
||||
small: [],
|
||||
span: [],
|
||||
sub: [],
|
||||
sup: [],
|
||||
strong: [],
|
||||
u: [],
|
||||
ul: []
|
||||
}
|
||||
|
||||
/**
|
||||
* A pattern that recognizes a commonly useful subset of URLs that are safe.
|
||||
*
|
||||
* Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts
|
||||
*/
|
||||
var SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi
|
||||
|
||||
/**
|
||||
* A pattern that matches safe data URLs. Only matches image, video and audio types.
|
||||
*
|
||||
* Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts
|
||||
*/
|
||||
var DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i
|
||||
|
||||
function allowedAttribute(attr, allowedAttributeList) {
|
||||
var attrName = attr.nodeName.toLowerCase()
|
||||
|
||||
if ($.inArray(attrName, allowedAttributeList) !== -1) {
|
||||
if ($.inArray(attrName, uriAttrs) !== -1) {
|
||||
return Boolean(attr.nodeValue.match(SAFE_URL_PATTERN) || attr.nodeValue.match(DATA_URL_PATTERN))
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
var regExp = $(allowedAttributeList).filter(function (index, value) {
|
||||
return value instanceof RegExp
|
||||
})
|
||||
|
||||
// Check if a regular expression validates the attribute.
|
||||
for (var i = 0, l = regExp.length; i < l; i++) {
|
||||
if (attrName.match(regExp[i])) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function sanitizeHtml(unsafeHtml, whiteList, sanitizeFn) {
|
||||
if (unsafeHtml.length === 0) {
|
||||
return unsafeHtml
|
||||
}
|
||||
|
||||
if (sanitizeFn && typeof sanitizeFn === 'function') {
|
||||
return sanitizeFn(unsafeHtml)
|
||||
}
|
||||
|
||||
// IE 8 and below don't support createHTMLDocument
|
||||
if (!document.implementation || !document.implementation.createHTMLDocument) {
|
||||
return unsafeHtml
|
||||
}
|
||||
|
||||
var createdDocument = document.implementation.createHTMLDocument('sanitization')
|
||||
createdDocument.body.innerHTML = unsafeHtml
|
||||
|
||||
var whitelistKeys = $.map(whiteList, function (el, i) { return i })
|
||||
var elements = $(createdDocument.body).find('*')
|
||||
|
||||
for (var i = 0, len = elements.length; i < len; i++) {
|
||||
var el = elements[i]
|
||||
var elName = el.nodeName.toLowerCase()
|
||||
|
||||
if ($.inArray(elName, whitelistKeys) === -1) {
|
||||
el.parentNode.removeChild(el)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
var attributeList = $.map(el.attributes, function (el) { return el })
|
||||
var whitelistedAttributes = [].concat(whiteList['*'] || [], whiteList[elName] || [])
|
||||
|
||||
for (var j = 0, len2 = attributeList.length; j < len2; j++) {
|
||||
if (!allowedAttribute(attributeList[j], whitelistedAttributes)) {
|
||||
el.removeAttribute(attributeList[j].nodeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return createdDocument.body.innerHTML
|
||||
}
|
||||
|
||||
// TOOLTIP PUBLIC CLASS DEFINITION
|
||||
// ===============================
|
||||
|
||||
@@ -1282,7 +1441,7 @@ if (typeof jQuery === 'undefined') {
|
||||
this.init('tooltip', element, options)
|
||||
}
|
||||
|
||||
Tooltip.VERSION = '3.3.7'
|
||||
Tooltip.VERSION = '3.4.1'
|
||||
|
||||
Tooltip.TRANSITION_DURATION = 150
|
||||
|
||||
@@ -1299,7 +1458,10 @@ if (typeof jQuery === 'undefined') {
|
||||
viewport: {
|
||||
selector: 'body',
|
||||
padding: 0
|
||||
}
|
||||
},
|
||||
sanitize : true,
|
||||
sanitizeFn : null,
|
||||
whiteList : DefaultWhitelist
|
||||
}
|
||||
|
||||
Tooltip.prototype.init = function (type, element, options) {
|
||||
@@ -1307,7 +1469,7 @@ if (typeof jQuery === 'undefined') {
|
||||
this.type = type
|
||||
this.$element = $(element)
|
||||
this.options = this.getOptions(options)
|
||||
this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))
|
||||
this.$viewport = this.options.viewport && $(document).find($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))
|
||||
this.inState = { click: false, hover: false, focus: false }
|
||||
|
||||
if (this.$element[0] instanceof document.constructor && !this.options.selector) {
|
||||
@@ -1340,7 +1502,15 @@ if (typeof jQuery === 'undefined') {
|
||||
}
|
||||
|
||||
Tooltip.prototype.getOptions = function (options) {
|
||||
options = $.extend({}, this.getDefaults(), this.$element.data(), options)
|
||||
var dataAttributes = this.$element.data()
|
||||
|
||||
for (var dataAttr in dataAttributes) {
|
||||
if (dataAttributes.hasOwnProperty(dataAttr) && $.inArray(dataAttr, DISALLOWED_ATTRIBUTES) !== -1) {
|
||||
delete dataAttributes[dataAttr]
|
||||
}
|
||||
}
|
||||
|
||||
options = $.extend({}, this.getDefaults(), dataAttributes, options)
|
||||
|
||||
if (options.delay && typeof options.delay == 'number') {
|
||||
options.delay = {
|
||||
@@ -1349,6 +1519,10 @@ if (typeof jQuery === 'undefined') {
|
||||
}
|
||||
}
|
||||
|
||||
if (options.sanitize) {
|
||||
options.template = sanitizeHtml(options.template, options.whiteList, options.sanitizeFn)
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
@@ -1460,7 +1634,7 @@ if (typeof jQuery === 'undefined') {
|
||||
.addClass(placement)
|
||||
.data('bs.' + this.type, this)
|
||||
|
||||
this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
|
||||
this.options.container ? $tip.appendTo($(document).find(this.options.container)) : $tip.insertAfter(this.$element)
|
||||
this.$element.trigger('inserted.bs.' + this.type)
|
||||
|
||||
var pos = this.getPosition()
|
||||
@@ -1562,7 +1736,16 @@ if (typeof jQuery === 'undefined') {
|
||||
var $tip = this.tip()
|
||||
var title = this.getTitle()
|
||||
|
||||
$tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
|
||||
if (this.options.html) {
|
||||
if (this.options.sanitize) {
|
||||
title = sanitizeHtml(title, this.options.whiteList, this.options.sanitizeFn)
|
||||
}
|
||||
|
||||
$tip.find('.tooltip-inner').html(title)
|
||||
} else {
|
||||
$tip.find('.tooltip-inner').text(title)
|
||||
}
|
||||
|
||||
$tip.removeClass('fade in top bottom left right')
|
||||
}
|
||||
|
||||
@@ -1743,6 +1926,9 @@ if (typeof jQuery === 'undefined') {
|
||||
})
|
||||
}
|
||||
|
||||
Tooltip.prototype.sanitizeHtml = function (unsafeHtml) {
|
||||
return sanitizeHtml(unsafeHtml, this.options.whiteList, this.options.sanitizeFn)
|
||||
}
|
||||
|
||||
// TOOLTIP PLUGIN DEFINITION
|
||||
// =========================
|
||||
@@ -1776,10 +1962,10 @@ if (typeof jQuery === 'undefined') {
|
||||
}(jQuery);
|
||||
|
||||
/* ========================================================================
|
||||
* Bootstrap: popover.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#popovers
|
||||
* Bootstrap: popover.js v3.4.1
|
||||
* https://getbootstrap.com/docs/3.4/javascript/#popovers
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Copyright 2011-2019 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
@@ -1796,7 +1982,7 @@ if (typeof jQuery === 'undefined') {
|
||||
|
||||
if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
|
||||
|
||||
Popover.VERSION = '3.3.7'
|
||||
Popover.VERSION = '3.4.1'
|
||||
|
||||
Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
|
||||
placement: 'right',
|
||||
@@ -1822,10 +2008,25 @@ if (typeof jQuery === 'undefined') {
|
||||
var title = this.getTitle()
|
||||
var content = this.getContent()
|
||||
|
||||
$tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
|
||||
$tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events
|
||||
this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
|
||||
](content)
|
||||
if (this.options.html) {
|
||||
var typeContent = typeof content
|
||||
|
||||
if (this.options.sanitize) {
|
||||
title = this.sanitizeHtml(title)
|
||||
|
||||
if (typeContent === 'string') {
|
||||
content = this.sanitizeHtml(content)
|
||||
}
|
||||
}
|
||||
|
||||
$tip.find('.popover-title').html(title)
|
||||
$tip.find('.popover-content').children().detach().end()[
|
||||
typeContent === 'string' ? 'html' : 'append'
|
||||
](content)
|
||||
} else {
|
||||
$tip.find('.popover-title').text(title)
|
||||
$tip.find('.popover-content').children().detach().end().text(content)
|
||||
}
|
||||
|
||||
$tip.removeClass('fade top bottom left right in')
|
||||
|
||||
@@ -1844,8 +2045,8 @@ if (typeof jQuery === 'undefined') {
|
||||
|
||||
return $e.attr('data-content')
|
||||
|| (typeof o.content == 'function' ?
|
||||
o.content.call($e[0]) :
|
||||
o.content)
|
||||
o.content.call($e[0]) :
|
||||
o.content)
|
||||
}
|
||||
|
||||
Popover.prototype.arrow = function () {
|
||||
@@ -1885,10 +2086,10 @@ if (typeof jQuery === 'undefined') {
|
||||
}(jQuery);
|
||||
|
||||
/* ========================================================================
|
||||
* Bootstrap: scrollspy.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#scrollspy
|
||||
* Bootstrap: scrollspy.js v3.4.1
|
||||
* https://getbootstrap.com/docs/3.4/javascript/#scrollspy
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Copyright 2011-2019 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
@@ -1914,7 +2115,7 @@ if (typeof jQuery === 'undefined') {
|
||||
this.process()
|
||||
}
|
||||
|
||||
ScrollSpy.VERSION = '3.3.7'
|
||||
ScrollSpy.VERSION = '3.4.1'
|
||||
|
||||
ScrollSpy.DEFAULTS = {
|
||||
offset: 10
|
||||
@@ -2058,10 +2259,10 @@ if (typeof jQuery === 'undefined') {
|
||||
}(jQuery);
|
||||
|
||||
/* ========================================================================
|
||||
* Bootstrap: tab.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#tabs
|
||||
* Bootstrap: tab.js v3.4.1
|
||||
* https://getbootstrap.com/docs/3.4/javascript/#tabs
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Copyright 2011-2019 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
@@ -2078,7 +2279,7 @@ if (typeof jQuery === 'undefined') {
|
||||
// jscs:enable requireDollarBeforejQueryAssignment
|
||||
}
|
||||
|
||||
Tab.VERSION = '3.3.7'
|
||||
Tab.VERSION = '3.4.1'
|
||||
|
||||
Tab.TRANSITION_DURATION = 150
|
||||
|
||||
@@ -2107,7 +2308,7 @@ if (typeof jQuery === 'undefined') {
|
||||
|
||||
if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return
|
||||
|
||||
var $target = $(selector)
|
||||
var $target = $(document).find(selector)
|
||||
|
||||
this.activate($this.closest('li'), $ul)
|
||||
this.activate($target, $target.parent(), function () {
|
||||
@@ -2132,15 +2333,15 @@ if (typeof jQuery === 'undefined') {
|
||||
$active
|
||||
.removeClass('active')
|
||||
.find('> .dropdown-menu > .active')
|
||||
.removeClass('active')
|
||||
.removeClass('active')
|
||||
.end()
|
||||
.find('[data-toggle="tab"]')
|
||||
.attr('aria-expanded', false)
|
||||
.attr('aria-expanded', false)
|
||||
|
||||
element
|
||||
.addClass('active')
|
||||
.find('[data-toggle="tab"]')
|
||||
.attr('aria-expanded', true)
|
||||
.attr('aria-expanded', true)
|
||||
|
||||
if (transition) {
|
||||
element[0].offsetWidth // reflow for transition
|
||||
@@ -2152,10 +2353,10 @@ if (typeof jQuery === 'undefined') {
|
||||
if (element.parent('.dropdown-menu').length) {
|
||||
element
|
||||
.closest('li.dropdown')
|
||||
.addClass('active')
|
||||
.addClass('active')
|
||||
.end()
|
||||
.find('[data-toggle="tab"]')
|
||||
.attr('aria-expanded', true)
|
||||
.attr('aria-expanded', true)
|
||||
}
|
||||
|
||||
callback && callback()
|
||||
@@ -2214,10 +2415,10 @@ if (typeof jQuery === 'undefined') {
|
||||
}(jQuery);
|
||||
|
||||
/* ========================================================================
|
||||
* Bootstrap: affix.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#affix
|
||||
* Bootstrap: affix.js v3.4.1
|
||||
* https://getbootstrap.com/docs/3.4/javascript/#affix
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Copyright 2011-2019 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
@@ -2231,7 +2432,9 @@ if (typeof jQuery === 'undefined') {
|
||||
var Affix = function (element, options) {
|
||||
this.options = $.extend({}, Affix.DEFAULTS, options)
|
||||
|
||||
this.$target = $(this.options.target)
|
||||
var target = this.options.target === Affix.DEFAULTS.target ? $(this.options.target) : $(document).find(this.options.target)
|
||||
|
||||
this.$target = target
|
||||
.on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
|
||||
.on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this))
|
||||
|
||||
@@ -2243,7 +2446,7 @@ if (typeof jQuery === 'undefined') {
|
||||
this.checkPosition()
|
||||
}
|
||||
|
||||
Affix.VERSION = '3.3.7'
|
||||
Affix.VERSION = '3.4.1'
|
||||
|
||||
Affix.RESET = 'affix affix-top affix-bottom'
|
||||
|
||||
6
src/main/webapp/assets/vendors/bootstrap-3.4.1/js/bootstrap.min.js
vendored
Normal file
6
src/main/webapp/assets/vendors/bootstrap-3.4.1/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
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
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user