mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-08 13:16:22 +02:00
Compare commits
105 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7192655f7 | ||
|
|
19ba09740c | ||
|
|
d169777722 | ||
|
|
ff8a5f6b77 | ||
|
|
ec953df156 | ||
|
|
d6a191d95b | ||
|
|
aba428bba1 | ||
|
|
6ab37fd596 | ||
|
|
73fc70f55b | ||
|
|
aad18b7a50 | ||
|
|
cc278be5cd | ||
|
|
d0f4f82a0f | ||
|
|
1dcbf386b1 | ||
|
|
414afd285c | ||
|
|
35b645d8b5 | ||
|
|
b3cba53866 | ||
|
|
a4773bb3ca | ||
|
|
863d8a4af5 | ||
|
|
3fccd7b53c | ||
|
|
dd2760eaf7 | ||
|
|
824bafa739 | ||
|
|
60cdaec05f | ||
|
|
c204a435b3 | ||
|
|
37accd92d6 | ||
|
|
01fd0ee1f0 | ||
|
|
fab1c74473 | ||
|
|
0d8fcfd28d | ||
|
|
b91a7c32a6 | ||
|
|
7f665c649b | ||
|
|
dcbadb4071 | ||
|
|
e3096d15ff | ||
|
|
a83c24e7b3 | ||
|
|
73457c9658 | ||
|
|
cfc8d9f3f1 | ||
|
|
8f423b83ea | ||
|
|
1e7ac532b6 | ||
|
|
0fd1db4596 | ||
|
|
0755b7ab7f | ||
|
|
052382e5c4 | ||
|
|
f44a63cec1 | ||
|
|
603d67354a | ||
|
|
aafa423b9f | ||
|
|
2da9d0a801 | ||
|
|
b60c112a74 | ||
|
|
843ed6df37 | ||
|
|
0f0a849677 | ||
|
|
682901ccbb | ||
|
|
048fdb8837 | ||
|
|
3353616789 | ||
|
|
b6cb4c865f | ||
|
|
1fcfd093f7 | ||
|
|
3f27c6e733 | ||
|
|
b6bd9bfc3b | ||
|
|
6c392f0056 | ||
|
|
9a38de9a23 | ||
|
|
8883600090 | ||
|
|
ab822a3c27 | ||
|
|
0e4d64de23 | ||
|
|
fbc6bd36bd | ||
|
|
ed90ca2dce | ||
|
|
537ef92149 | ||
|
|
d51afa7d40 | ||
|
|
975cffff48 | ||
|
|
d92e9c00e8 | ||
|
|
12d72cbb19 | ||
|
|
e7a6f0930b | ||
|
|
d8e03bed1f | ||
|
|
f48c087cd8 | ||
|
|
917663e0df | ||
|
|
556ddbc926 | ||
|
|
1c6f37b8e8 | ||
|
|
720a329a50 | ||
|
|
220a8f076a | ||
|
|
43be8333c7 | ||
|
|
08706ab4df | ||
|
|
b1196657e0 | ||
|
|
334bd0c919 | ||
|
|
cf0f896972 | ||
|
|
d21ca3ff8a | ||
|
|
83f1f16de7 | ||
|
|
0fa2ccf107 | ||
|
|
18e3dd431b | ||
|
|
f25dee2249 | ||
|
|
575ffa9580 | ||
|
|
f17af5aeb0 | ||
|
|
639f153589 | ||
|
|
fb07098c13 | ||
|
|
74fc08b039 | ||
|
|
2776e00004 | ||
|
|
5932fce303 | ||
|
|
39c9fc4261 | ||
|
|
89ea4509a3 | ||
|
|
d19d838ead | ||
|
|
633a2699a8 | ||
|
|
e79bca4a3c | ||
|
|
f8ab480d20 | ||
|
|
a14129e340 | ||
|
|
5ec39df6e0 | ||
|
|
956e0c6321 | ||
|
|
0660a9203a | ||
|
|
1b660272a1 | ||
|
|
d9ef9b874d | ||
|
|
7824f796ee | ||
|
|
838a8d4c7b | ||
|
|
8db9f77f91 |
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -8,11 +8,11 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
java: [8, 11]
|
||||
java: [8, 11, 17]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Cache
|
||||
uses: actions/cache@v2.1.6
|
||||
uses: actions/cache@v2.1.7
|
||||
env:
|
||||
cache-name: cache-sbt-libs
|
||||
with:
|
||||
|
||||
@@ -4,4 +4,5 @@ updates.includeScala = true
|
||||
|
||||
updates.pin = [
|
||||
{ groupId = "org.eclipse.jetty", version = "9." }
|
||||
{ groupId = "org.eclipse.jgit", version = "5." }
|
||||
]
|
||||
|
||||
16
CHANGELOG.md
16
CHANGELOG.md
@@ -1,6 +1,22 @@
|
||||
# Changelog
|
||||
All changes to the project will be documented in this file.
|
||||
|
||||
### 4.37.1 - 14 Dec 2021
|
||||
- Update gist-plugin and notification-plugin
|
||||
- Fix SSHCommand extension point for apache-sshd 2.x
|
||||
|
||||
### 4.37.0 - 11 Dec 2021
|
||||
- Enhance Git Reference APIs
|
||||
- Add milestone data to issue list API
|
||||
- Support "all" in issue list API
|
||||
- Support EDDSA in signed commit verification
|
||||
- Support custom SSH url
|
||||
- Relax max passward length limitation
|
||||
- Relax max webhook url length limitation
|
||||
|
||||
### 4.36.2 - 16 Aug 2021
|
||||
- Escape user name in avatar image tag
|
||||
|
||||
### 4.36.1 - 22 Jul 2021
|
||||
- Bump gitbucket-gist-plugin to 4.21.0
|
||||
|
||||
|
||||
20
README.md
20
README.md
@@ -61,15 +61,19 @@ Support
|
||||
- If you can't find same question and report, send it to our [Gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
|
||||
- The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles.
|
||||
|
||||
What's New in 4.36.x
|
||||
What's New in 4.37.x
|
||||
-------------
|
||||
### 4.36.1 - 22 Jul 2021
|
||||
- Bump gitbucket-gist-plugin to 4.21.0
|
||||
### 4.37.1 - 14 Dec 2021
|
||||
- Update gist-plugin and notification-plugin
|
||||
- Fix SSHCommand extension point for apache-sshd 2.x
|
||||
|
||||
### 4.36.0 - 17 Jul 2021
|
||||
- Tag selector in the repository viewer
|
||||
- Link issues/pull requests of other repositories
|
||||
- Files and lines can be linked in the diff view
|
||||
- Option to disable XSS protection
|
||||
### 4.37.0 - 11 Dec 2021
|
||||
- Enhance Git Reference APIs
|
||||
- Add milestone data to issue list API
|
||||
- Support "all" in issue list API
|
||||
- Support EDDSA in signed commit verification
|
||||
- Support custom SSH url
|
||||
- Relax max passward length limitation
|
||||
- Relax max webhook url length limitation
|
||||
|
||||
See the [change log](CHANGELOG.md) for all of the updates.
|
||||
|
||||
40
build.sbt
40
build.sbt
@@ -3,10 +3,10 @@ import com.jsuereth.sbtpgp.PgpKeys._
|
||||
|
||||
val Organization = "io.github.gitbucket"
|
||||
val Name = "gitbucket"
|
||||
val GitBucketVersion = "4.36.1"
|
||||
val ScalatraVersion = "2.7.1"
|
||||
val JettyVersion = "9.4.43.v20210629"
|
||||
val JgitVersion = "5.12.0.202106070339-r"
|
||||
val GitBucketVersion = "4.37.1"
|
||||
val ScalatraVersion = "2.8.2"
|
||||
val JettyVersion = "9.4.44.v20210927"
|
||||
val JgitVersion = "5.13.0.202109080827-r"
|
||||
|
||||
lazy val root = (project in file("."))
|
||||
.enablePlugins(SbtTwirl, ScalatraPlugin)
|
||||
@@ -15,7 +15,7 @@ sourcesInBase := false
|
||||
organization := Organization
|
||||
name := Name
|
||||
version := GitBucketVersion
|
||||
scalaVersion := "2.13.6"
|
||||
scalaVersion := "2.13.7"
|
||||
|
||||
scalafmtOnCompile := true
|
||||
|
||||
@@ -34,7 +34,7 @@ libraryDependencies ++= Seq(
|
||||
"org.scalatra" %% "scalatra" % ScalatraVersion cross CrossVersion.for3Use2_13,
|
||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion cross CrossVersion.for3Use2_13,
|
||||
"org.scalatra" %% "scalatra-forms" % ScalatraVersion cross CrossVersion.for3Use2_13,
|
||||
"org.json4s" %% "json4s-jackson" % "3.6.11" cross CrossVersion.for3Use2_13,
|
||||
"org.json4s" %% "json4s-jackson" % "4.0.3" cross CrossVersion.for3Use2_13,
|
||||
"commons-io" % "commons-io" % "2.11.0",
|
||||
"io.github.gitbucket" % "solidbase" % "1.0.3",
|
||||
"io.github.gitbucket" % "markedj" % "1.0.16",
|
||||
@@ -42,34 +42,34 @@ libraryDependencies ++= Seq(
|
||||
"org.apache.commons" % "commons-email" % "1.5",
|
||||
"commons-net" % "commons-net" % "3.8.0",
|
||||
"org.apache.httpcomponents" % "httpclient" % "4.5.13",
|
||||
"org.apache.sshd" % "apache-sshd" % "2.1.0" exclude ("org.slf4j", "slf4j-jdk14") exclude ("org.apache.sshd", "sshd-mina") exclude ("org.apache.sshd", "sshd-netty"),
|
||||
"org.apache.tika" % "tika-core" % "2.0.0",
|
||||
"org.apache.sshd" % "apache-sshd" % "2.8.0" exclude ("org.slf4j", "slf4j-jdk14") exclude ("org.apache.sshd", "sshd-mina") exclude ("org.apache.sshd", "sshd-netty"),
|
||||
"org.apache.tika" % "tika-core" % "2.1.0",
|
||||
"com.github.takezoe" %% "blocking-slick-32" % "0.0.12" cross CrossVersion.for3Use2_13,
|
||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||
"com.h2database" % "h2" % "1.4.199",
|
||||
"org.mariadb.jdbc" % "mariadb-java-client" % "2.7.3",
|
||||
"org.postgresql" % "postgresql" % "42.2.23",
|
||||
"ch.qos.logback" % "logback-classic" % "1.2.4",
|
||||
"org.mariadb.jdbc" % "mariadb-java-client" % "2.7.4",
|
||||
"org.postgresql" % "postgresql" % "42.3.1",
|
||||
"ch.qos.logback" % "logback-classic" % "1.2.7",
|
||||
"com.zaxxer" % "HikariCP" % "4.0.3" exclude ("org.slf4j", "slf4j-api"),
|
||||
"com.typesafe" % "config" % "1.4.1",
|
||||
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.1.0",
|
||||
"io.github.java-diff-utils" % "java-diff-utils" % "4.10",
|
||||
"io.github.java-diff-utils" % "java-diff-utils" % "4.11",
|
||||
"org.cache2k" % "cache2k-all" % "1.6.0.Final",
|
||||
"net.coobird" % "thumbnailator" % "0.4.14",
|
||||
"net.coobird" % "thumbnailator" % "0.4.15",
|
||||
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
|
||||
"com.nimbusds" % "oauth2-oidc-sdk" % "9.10.1",
|
||||
"com.nimbusds" % "oauth2-oidc-sdk" % "9.20",
|
||||
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
||||
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
||||
"junit" % "junit" % "4.13.2" % "test",
|
||||
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test" cross CrossVersion.for3Use2_13,
|
||||
"org.mockito" % "mockito-core" % "3.11.2" % "test",
|
||||
"com.dimafeng" %% "testcontainers-scala" % "0.39.5" % "test",
|
||||
"org.testcontainers" % "mysql" % "1.16.0" % "test",
|
||||
"org.testcontainers" % "postgresql" % "1.16.0" % "test",
|
||||
"org.mockito" % "mockito-core" % "4.1.0" % "test",
|
||||
"com.dimafeng" %% "testcontainers-scala" % "0.39.12" % "test",
|
||||
"org.testcontainers" % "mysql" % "1.16.2" % "test",
|
||||
"org.testcontainers" % "postgresql" % "1.16.2" % "test",
|
||||
"net.i2p.crypto" % "eddsa" % "0.3.0",
|
||||
"is.tagomor.woothee" % "woothee-java" % "1.11.0",
|
||||
"org.ec4j.core" % "ec4j-core" % "0.3.0",
|
||||
"org.kohsuke" % "github-api" % "1.131" % "test"
|
||||
"org.kohsuke" % "github-api" % "1.301" % "test"
|
||||
)
|
||||
|
||||
libraryDependencies ~= {
|
||||
@@ -200,7 +200,7 @@ executableKey := {
|
||||
manifest.getMainAttributes put (AttrName.MANIFEST_VERSION, "1.0")
|
||||
manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher")
|
||||
val outputFile = workDir / warName
|
||||
IO jar (contentMappings.map { case (file, path) => (file, path.toString) }, outputFile, manifest)
|
||||
IO jar (contentMappings.map { case (file, path) => (file, path.toString) }, outputFile, manifest, None)
|
||||
|
||||
// generate checksums
|
||||
Seq(
|
||||
|
||||
@@ -1 +1 @@
|
||||
sbt.version=1.5.5
|
||||
sbt.version=1.5.6
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
||||
|
||||
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3")
|
||||
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.5")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.1")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "1.0.0")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "1.1.0")
|
||||
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.4")
|
||||
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0")
|
||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.8.2")
|
||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.2")
|
||||
|
||||
addDependencyTreePlugin
|
||||
|
||||
@@ -1,51 +1,100 @@
|
||||
import org.eclipse.jetty.server.ConnectionFactory;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.SecureRequestCustomizer;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.server.SslConnectionFactory;
|
||||
import org.eclipse.jetty.server.handler.HandlerList;
|
||||
import org.eclipse.jetty.server.handler.SecuredRedirectHandler;
|
||||
import org.eclipse.jetty.server.handler.StatisticsHandler;
|
||||
import org.eclipse.jetty.server.session.DefaultSessionCache;
|
||||
import org.eclipse.jetty.server.session.FileSessionDataStore;
|
||||
import org.eclipse.jetty.server.session.SessionCache;
|
||||
import org.eclipse.jetty.server.session.SessionHandler;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.eclipse.jetty.webapp.WebAppContext;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URL;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.function.Function.identity;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
|
||||
public class JettyLauncher {
|
||||
|
||||
private interface Defaults {
|
||||
|
||||
String CONNECTORS = "http";
|
||||
String HOST = "0.0.0.0";
|
||||
|
||||
int HTTP_PORT = 8080;
|
||||
int HTTPS_PORT = 8443;
|
||||
|
||||
boolean REDIRECT_HTTPS = false;
|
||||
}
|
||||
|
||||
private interface Connectors {
|
||||
|
||||
String HTTP = "http";
|
||||
String HTTPS = "https";
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
System.setProperty("java.awt.headless", "true");
|
||||
|
||||
String host = null;
|
||||
String port = null;
|
||||
InetSocketAddress address = null;
|
||||
String contextPath = "/";
|
||||
String tmpDirPath="";
|
||||
boolean forceHttps = false;
|
||||
String connectors = getEnvironmentVariable("gitbucket.connectors");
|
||||
String host = getEnvironmentVariable("gitbucket.host");
|
||||
String port = getEnvironmentVariable("gitbucket.port");
|
||||
String securePort = getEnvironmentVariable("gitbucket.securePort");
|
||||
String keyStorePath = getEnvironmentVariable("gitbucket.keyStorePath");
|
||||
String keyStorePassword = getEnvironmentVariable("gitbucket.keyStorePassword");
|
||||
String keyManagerPassword = getEnvironmentVariable("gitbucket.keyManagerPassword");
|
||||
String redirectHttps = getEnvironmentVariable("gitbucket.redirectHttps");
|
||||
String contextPath = getEnvironmentVariable("gitbucket.prefix");
|
||||
String tmpDirPath = getEnvironmentVariable("gitbucket.tempDir");
|
||||
boolean saveSessions = false;
|
||||
|
||||
host = getEnvironmentVariable("gitbucket.host");
|
||||
port = getEnvironmentVariable("gitbucket.port");
|
||||
contextPath = getEnvironmentVariable("gitbucket.prefix");
|
||||
tmpDirPath = getEnvironmentVariable("gitbucket.tempDir");
|
||||
|
||||
for(String arg: args) {
|
||||
if(arg.equals("--save_sessions")) {
|
||||
saveSessions = true;
|
||||
}
|
||||
if(arg.startsWith("--") && arg.contains("=")) {
|
||||
String[] dim = arg.split("=");
|
||||
if(dim.length >= 2) {
|
||||
String[] dim = arg.split("=", 2);
|
||||
if(dim.length == 2) {
|
||||
switch (dim[0]) {
|
||||
case "--connectors":
|
||||
connectors = dim[1];
|
||||
break;
|
||||
case "--host":
|
||||
host = dim[1];
|
||||
break;
|
||||
case "--port":
|
||||
port = dim[1];
|
||||
break;
|
||||
case "--secure_port":
|
||||
securePort = dim[1];
|
||||
break;
|
||||
case "--key_store_path":
|
||||
keyStorePath = dim[1];
|
||||
break;
|
||||
case "--key_store_password":
|
||||
keyStorePassword = dim[1];
|
||||
break;
|
||||
case "--key_manager_password":
|
||||
keyManagerPassword = dim[1];
|
||||
break;
|
||||
case "--redirect_https":
|
||||
redirectHttps = dim[1];
|
||||
break;
|
||||
case "--prefix":
|
||||
contextPath = dim[1];
|
||||
break;
|
||||
@@ -67,38 +116,69 @@ public class JettyLauncher {
|
||||
contextPath = "/" + contextPath;
|
||||
}
|
||||
|
||||
if(host != null) {
|
||||
address = new InetSocketAddress(host, getPort(port));
|
||||
} else {
|
||||
address = new InetSocketAddress(getPort(port));
|
||||
final String hostName = InetAddress.getByName(fallback(host, Defaults.HOST)).getHostName();
|
||||
|
||||
final Server server = new Server();
|
||||
|
||||
final Set<String> connectorsSet = Stream.of(fallback(connectors, Defaults.CONNECTORS)
|
||||
.toLowerCase().split(",")).map(String::trim).collect(toSet());
|
||||
|
||||
final List<ServerConnector> connectorInstances = new ArrayList<>();
|
||||
|
||||
final HttpConfiguration httpConfig = new HttpConfiguration();
|
||||
httpConfig.setSendServerVersion(false);
|
||||
if (connectorsSet.contains(Connectors.HTTPS)) {
|
||||
httpConfig.setSecurePort(fallback(securePort, Defaults.HTTPS_PORT, Integer::parseInt));
|
||||
}
|
||||
|
||||
Server server = new Server(address);
|
||||
if (connectorsSet.contains(Connectors.HTTP)) {
|
||||
final ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
|
||||
connector.setHost(hostName);
|
||||
connector.setPort(fallback(port, Defaults.HTTP_PORT, Integer::parseInt));
|
||||
|
||||
// SelectChannelConnector connector = new SelectChannelConnector();
|
||||
// if(host != null) {
|
||||
// connector.setHost(host);
|
||||
// }
|
||||
// connector.setMaxIdleTime(1000 * 60 * 60);
|
||||
// connector.setSoLingerTime(-1);
|
||||
// connector.setPort(port);
|
||||
// server.addConnector(connector);
|
||||
|
||||
// Disabling Server header
|
||||
for (Connector connector : server.getConnectors()) {
|
||||
for (ConnectionFactory factory : connector.getConnectionFactories()) {
|
||||
if (factory instanceof HttpConnectionFactory) {
|
||||
((HttpConnectionFactory) factory).getHttpConfiguration().setSendServerVersion(false);
|
||||
}
|
||||
}
|
||||
connectorInstances.add(connector);
|
||||
}
|
||||
|
||||
if (connectorsSet.contains(Connectors.HTTPS)) {
|
||||
final SslContextFactory sslContextFactory = new SslContextFactory.Server();
|
||||
|
||||
sslContextFactory.setKeyStorePath(requireNonNull(keyStorePath,
|
||||
"You must specify a path to an SSL keystore via the --key_store_path command line argument" +
|
||||
" or GITBUCKET_KEYSTOREPATH environment variable."));
|
||||
|
||||
sslContextFactory.setKeyStorePassword(requireNonNull(keyStorePassword,
|
||||
"You must specify a an SSL keystore password via the --key_store_password argument" +
|
||||
" or GITBUCKET_KEYSTOREPASSWORD environment variable."));
|
||||
|
||||
sslContextFactory.setKeyManagerPassword(requireNonNull(keyManagerPassword,
|
||||
"You must specify a key manager password via the --key_manager_password' argument" +
|
||||
" or GITBUCKET_KEYMANAGERPASSWORD environment variable."));
|
||||
|
||||
final HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
|
||||
httpsConfig.addCustomizer(new SecureRequestCustomizer());
|
||||
|
||||
final ServerConnector connector = new ServerConnector(server,
|
||||
new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
|
||||
new HttpConnectionFactory(httpsConfig));
|
||||
|
||||
connector.setHost(hostName);
|
||||
connector.setPort(fallback(securePort, Defaults.HTTPS_PORT, Integer::parseInt));
|
||||
|
||||
connectorInstances.add(connector);
|
||||
}
|
||||
|
||||
require(!connectorInstances.isEmpty(),
|
||||
"No server connectors could be configured, please check your --connectors command line argument" +
|
||||
" or GITBUCKET_CONNECTORS environment variable.");
|
||||
|
||||
server.setConnectors(connectorInstances.toArray(new ServerConnector[0]));
|
||||
|
||||
WebAppContext context = new WebAppContext();
|
||||
|
||||
if(saveSessions) {
|
||||
File sessDir = new File(getGitBucketHome(), "sessions");
|
||||
if(!sessDir.exists()){
|
||||
sessDir.mkdirs();
|
||||
mkdir(sessDir);
|
||||
}
|
||||
SessionHandler sessions = context.getSessionHandler();
|
||||
SessionCache cache = new DefaultSessionCache(sessions);
|
||||
@@ -112,7 +192,7 @@ public class JettyLauncher {
|
||||
if(tmpDirPath == null || tmpDirPath.equals("")){
|
||||
tmpDir = new File(getGitBucketHome(), "tmp");
|
||||
if(!tmpDir.exists()){
|
||||
tmpDir.mkdirs();
|
||||
mkdir(tmpDir);
|
||||
}
|
||||
} else {
|
||||
tmpDir = new File(tmpDirPath);
|
||||
@@ -136,13 +216,16 @@ public class JettyLauncher {
|
||||
context.setDescriptor(location.toExternalForm() + "/WEB-INF/web.xml");
|
||||
context.setServer(server);
|
||||
context.setWar(location.toExternalForm());
|
||||
if (forceHttps) {
|
||||
context.setInitParameter("org.scalatra.ForceHttps", "true");
|
||||
|
||||
final HandlerList handlers = new HandlerList();
|
||||
|
||||
if (fallback(redirectHttps, Defaults.REDIRECT_HTTPS, Boolean::parseBoolean)) {
|
||||
handlers.addHandler(new SecuredRedirectHandler());
|
||||
}
|
||||
|
||||
Handler handler = addStatisticsHandler(context);
|
||||
handlers.addHandler(addStatisticsHandler(context));
|
||||
|
||||
server.setHandler(handler);
|
||||
server.setHandler(handlers);
|
||||
server.setStopAtShutdown(true);
|
||||
server.setStopTimeout(7_000);
|
||||
server.start();
|
||||
@@ -170,11 +253,28 @@ public class JettyLauncher {
|
||||
}
|
||||
}
|
||||
|
||||
private static int getPort(String port){
|
||||
if(port == null) {
|
||||
return 8080;
|
||||
} else {
|
||||
return Integer.parseInt(port);
|
||||
private static <T, R> T fallback(R value, T defaultValue, Function<R, T> converter) {
|
||||
return value == null ? defaultValue : converter.apply(value);
|
||||
}
|
||||
|
||||
private static <T> T fallback(T value, T defaultValue) {
|
||||
return fallback(value, defaultValue, identity());
|
||||
}
|
||||
|
||||
private static void require(boolean condition, String message) {
|
||||
if (!condition) {
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> T requireNonNull(T value, String message) {
|
||||
require(value != null, message);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void mkdir(File dir) {
|
||||
if (!dir.mkdirs()) {
|
||||
throw new RuntimeException("Unable to create directory: " + dir);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,15 +20,15 @@ public class PatchUtil {
|
||||
public static String apply(String source, String patch, FileHeader fh)
|
||||
throws IOException, PatchApplyException {
|
||||
RawText rt = new RawText(source.getBytes("UTF-8"));
|
||||
List<String> oldLines = new ArrayList<String>(rt.size());
|
||||
List<String> oldLines = new ArrayList<>(rt.size());
|
||||
for (int i = 0; i < rt.size(); i++)
|
||||
oldLines.add(rt.getString(i));
|
||||
List<String> newLines = new ArrayList<String>(oldLines);
|
||||
List<String> newLines = new ArrayList<>(oldLines);
|
||||
for (HunkHeader hh : fh.getHunks()) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(patch.getBytes("UTF-8"), hh.getStartOffset(), hh.getEndOffset() - hh.getStartOffset());
|
||||
RawText hrt = new RawText(out.toByteArray());
|
||||
List<String> hunkLines = new ArrayList<String>(hrt.size());
|
||||
List<String> hunkLines = new ArrayList<>(hrt.size());
|
||||
for (int i = 0; i < hrt.size(); i++)
|
||||
hunkLines.add(hrt.getString(i));
|
||||
int pos = 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
notifications:1.10.0
|
||||
gist:4.21.0
|
||||
notifications:1.11.0
|
||||
gist:4.22.0
|
||||
emoji:4.6.0
|
||||
pages:1.9.0
|
||||
pages:1.10.0
|
||||
|
||||
7
src/main/resources/update/gitbucket-core_4.37.xml
Normal file
7
src/main/resources/update/gitbucket-core_4.37.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<changeSet>
|
||||
<dropForeignKeyConstraint constraintName="IDX_WEB_HOOK_EVENT_FK0" baseTableName="WEB_HOOK_EVENT"/>
|
||||
<modifyDataType columnName="URL" newDataType="varchar(400)" tableName="WEB_HOOK_EVENT"/>
|
||||
<modifyDataType columnName="URL" newDataType="varchar(400)" tableName="WEB_HOOK"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_WEB_HOOK_EVENT_FK0" baseTableName="WEB_HOOK_EVENT" baseColumnNames="USER_NAME, REPOSITORY_NAME, URL" referencedTableName="WEB_HOOK" referencedColumnNames="USER_NAME, REPOSITORY_NAME, URL" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||
</changeSet>
|
||||
@@ -3,7 +3,6 @@ package gitbucket.core
|
||||
import java.io.FileOutputStream
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.sql.Connection
|
||||
import java.util
|
||||
import java.util.UUID
|
||||
|
||||
import gitbucket.core.model.Activity
|
||||
@@ -84,7 +83,7 @@ object GitBucketCoreModule
|
||||
new Version(
|
||||
"4.34.0",
|
||||
new Migration() {
|
||||
override def migrate(moduleId: String, version: String, context: util.Map[String, AnyRef]): Unit = {
|
||||
override def migrate(moduleId: String, version: String, context: java.util.Map[String, AnyRef]): Unit = {
|
||||
implicit val formats: Formats = Serialization.formats(NoTypeHints)
|
||||
import JDBCUtil._
|
||||
|
||||
@@ -119,5 +118,8 @@ object GitBucketCoreModule
|
||||
new Version("4.35.2"),
|
||||
new Version("4.35.3"),
|
||||
new Version("4.36.0", new LiquibaseMigration("update/gitbucket-core_4.36.xml")),
|
||||
new Version("4.36.1")
|
||||
new Version("4.36.1"),
|
||||
new Version("4.36.2"),
|
||||
new Version("4.37.0", new LiquibaseMigration("update/gitbucket-core_4.37.xml")),
|
||||
new Version("4.37.1")
|
||||
)
|
||||
|
||||
@@ -17,7 +17,8 @@ case class ApiIssue(
|
||||
state: String,
|
||||
created_at: Date,
|
||||
updated_at: Date,
|
||||
body: String
|
||||
body: String,
|
||||
milestone: Option[ApiMilestone]
|
||||
)(repositoryName: RepositoryName, isPullRequest: Boolean) {
|
||||
val id = 0 // dummy id
|
||||
val assignees = List(assignee).flatten
|
||||
@@ -43,7 +44,8 @@ object ApiIssue {
|
||||
repositoryName: RepositoryName,
|
||||
user: ApiUser,
|
||||
assignee: Option[ApiUser],
|
||||
labels: List[ApiLabel]
|
||||
labels: List[ApiLabel],
|
||||
milestone: Option[ApiMilestone]
|
||||
): ApiIssue =
|
||||
ApiIssue(
|
||||
number = issue.issueId,
|
||||
@@ -51,6 +53,7 @@ object ApiIssue {
|
||||
user = user,
|
||||
assignee = assignee,
|
||||
labels = labels,
|
||||
milestone = milestone,
|
||||
state = if (issue.closed) { "closed" } else { "open" },
|
||||
body = issue.content.getOrElse(""),
|
||||
created_at = issue.registeredDate,
|
||||
|
||||
@@ -1,5 +1,49 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
case class ApiObject(sha: String)
|
||||
import gitbucket.core.util.JGitUtil.TagInfo
|
||||
import gitbucket.core.util.RepositoryName
|
||||
import org.eclipse.jgit.lib.Ref
|
||||
|
||||
case class ApiRef(ref: String, `object`: ApiObject)
|
||||
case class ApiRefCommit(
|
||||
sha: String,
|
||||
`type`: String,
|
||||
url: ApiPath
|
||||
)
|
||||
|
||||
case class ApiRef(
|
||||
ref: String,
|
||||
node_id: String = "",
|
||||
url: ApiPath,
|
||||
`object`: ApiRefCommit,
|
||||
)
|
||||
|
||||
object ApiRef {
|
||||
|
||||
def fromRef(
|
||||
repositoryName: RepositoryName,
|
||||
ref: Ref
|
||||
): ApiRef =
|
||||
ApiRef(
|
||||
ref = ref.getName,
|
||||
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/${ref.getName}"),
|
||||
`object` = ApiRefCommit(
|
||||
sha = ref.getObjectId.getName,
|
||||
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/commits/${ref.getObjectId.getName}"),
|
||||
`type` = "commit"
|
||||
)
|
||||
)
|
||||
|
||||
def fromTag(
|
||||
repositoryName: RepositoryName,
|
||||
tagInfo: TagInfo
|
||||
): ApiRef =
|
||||
ApiRef(
|
||||
ref = s"refs/tags/${tagInfo.name}",
|
||||
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/refs/tags/${tagInfo.name}"),
|
||||
`object` = ApiRefCommit(
|
||||
sha = tagInfo.objectId,
|
||||
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/tags/${tagInfo.objectId}"), // TODO This URL is not yet available?
|
||||
`type` = "tag"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.util.RepositoryName
|
||||
|
||||
case class ApiTagCommit(
|
||||
sha: String,
|
||||
url: ApiPath
|
||||
)
|
||||
|
||||
case class ApiTag(
|
||||
name: String,
|
||||
commit: ApiTagCommit,
|
||||
zipball_url: ApiPath,
|
||||
tarball_url: ApiPath
|
||||
)
|
||||
|
||||
object ApiTag {
|
||||
def apply(
|
||||
tagName: String,
|
||||
repositoryName: RepositoryName,
|
||||
commitId: String
|
||||
): ApiTag =
|
||||
ApiTag(
|
||||
name = tagName,
|
||||
commit = ApiTagCommit(sha = commitId, url = ApiPath(s"/${repositoryName.fullName}/commits/${commitId}")),
|
||||
zipball_url = ApiPath(s"/${repositoryName.fullName}/archive/${tagName}.zip"),
|
||||
tarball_url = ApiPath(s"/${repositoryName.fullName}/archive/${tagName}.tar.gz")
|
||||
)
|
||||
}
|
||||
@@ -86,7 +86,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
val newForm = mapping(
|
||||
"userName" -> trim(label("User name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||
"password" -> trim(label("Password", text(required, maxlength(20)))),
|
||||
"password" -> trim(label("Password", text(required, maxlength(40)))),
|
||||
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
||||
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))),
|
||||
"extraMailAddresses" -> list(
|
||||
@@ -98,7 +98,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
)(AccountNewForm.apply)
|
||||
|
||||
val editForm = mapping(
|
||||
"password" -> trim(label("Password", optional(text(maxlength(20))))),
|
||||
"password" -> trim(label("Password", optional(text(maxlength(40))))),
|
||||
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
||||
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))),
|
||||
"extraMailAddresses" -> list(
|
||||
@@ -434,7 +434,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
post("/:userName/_personalToken", personalTokenForm)(oneselfOnly { form =>
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).map { x =>
|
||||
getAccountByUserName(userName).foreach { x =>
|
||||
val (tokenId, token) = generateAccessToken(userName, form.note)
|
||||
flash.update("generatedToken", (tokenId, token))
|
||||
}
|
||||
|
||||
@@ -242,25 +242,21 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
branchIsOutOfDate = JGitUtil.getShaByRef(repository.owner, repository.name, pullreq.branch) != Some(
|
||||
pullreq.commitIdFrom
|
||||
),
|
||||
needStatusCheck = context.loginAccount
|
||||
.map { u =>
|
||||
branchProtection.needStatusCheck(u.userName)
|
||||
}
|
||||
.getOrElse(true),
|
||||
needStatusCheck = context.loginAccount.forall { u =>
|
||||
branchProtection.needStatusCheck(u.userName)
|
||||
},
|
||||
hasUpdatePermission = hasDeveloperRole(
|
||||
pullreq.requestUserName,
|
||||
pullreq.requestRepositoryName,
|
||||
context.loginAccount
|
||||
) &&
|
||||
context.loginAccount
|
||||
.map { u =>
|
||||
!getProtectedBranchInfo(
|
||||
pullreq.requestUserName,
|
||||
pullreq.requestRepositoryName,
|
||||
pullreq.requestBranch
|
||||
).needStatusCheck(u.userName)
|
||||
}
|
||||
.getOrElse(false),
|
||||
context.loginAccount.exists { u =>
|
||||
!getProtectedBranchInfo(
|
||||
pullreq.requestUserName,
|
||||
pullreq.requestRepositoryName,
|
||||
pullreq.requestBranch
|
||||
).needStatusCheck(u.userName)
|
||||
},
|
||||
hasMergePermission = hasMergePermission,
|
||||
commitIdTo = pullreq.commitIdTo
|
||||
)
|
||||
@@ -494,8 +490,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
(repository.userName, repository.repositoryName, repository.defaultBranch)
|
||||
},
|
||||
commits.flatten
|
||||
.map(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false))
|
||||
.flatten
|
||||
.flatMap(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false))
|
||||
.toList,
|
||||
originId,
|
||||
forkedId,
|
||||
|
||||
@@ -138,7 +138,7 @@ trait ReleaseControllerBase extends ControllerBase {
|
||||
|
||||
get("/:owner/:repository/changelog/*...*")(writableUsersOnly { repository =>
|
||||
val Seq(previousTag, currentTag) = multiParams("splat")
|
||||
val previousTagId = repository.tags.collectFirst { case x if x.name == previousTag => x.id }.getOrElse("")
|
||||
val previousTagId = repository.tags.collectFirst { case x if x.name == previousTag => x.commitId }.getOrElse("")
|
||||
|
||||
val commitLog = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val commits = JGitUtil.getCommitLog(git, previousTagId, currentTag).reverse
|
||||
|
||||
@@ -685,7 +685,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
getPathObjectId(git, path, revCommit).map {
|
||||
objectId =>
|
||||
if (raw) {
|
||||
// Download (This route is left for backword compatibility)
|
||||
// Download (This route is left for backward compatibility)
|
||||
responseRawFile(git, objectId, path, repository)
|
||||
} else {
|
||||
val info = EditorConfigUtil.getEditorConfigInfo(git, id, path)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import java.io.FileInputStream
|
||||
|
||||
import gitbucket.core.admin.html
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service.SystemSettingsService._
|
||||
@@ -50,8 +49,20 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
"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())))
|
||||
"bindAddress" -> mapping(
|
||||
"host" -> trim(label("Bind SSH host", optional(text()))),
|
||||
"port" -> trim(label("Bind SSH port", optional(number()))),
|
||||
)(
|
||||
(hostOption, portOption) =>
|
||||
hostOption.map(h => SshAddress(h, portOption.getOrElse(DefaultSshPort), GenericSshUser))
|
||||
),
|
||||
"publicAddress" -> mapping(
|
||||
"host" -> trim(label("Public SSH host", optional(text()))),
|
||||
"port" -> trim(label("Public SSH port", optional(number()))),
|
||||
)(
|
||||
(hostOption, portOption) =>
|
||||
hostOption.map(h => SshAddress(h, portOption.getOrElse(PublicSshPort), GenericSshUser))
|
||||
),
|
||||
)(Ssh.apply),
|
||||
"useSMTP" -> trim(label("SMTP", boolean())),
|
||||
"smtp" -> optionalIfNotChecked(
|
||||
@@ -116,8 +127,8 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
if (settings.ssh.enabled && settings.baseUrl.isEmpty) {
|
||||
Some("baseUrl" -> "Base URL is required if SSH access is enabled.")
|
||||
} else None,
|
||||
if (settings.ssh.enabled && settings.ssh.sshHost.isEmpty) {
|
||||
Some("sshHost" -> "SSH host is required if SSH access is enabled.")
|
||||
if (settings.ssh.enabled && settings.ssh.bindAddress.isEmpty) {
|
||||
Some("ssh.bindAddress.host" -> "SSH bind host is required if SSH access is enabled.")
|
||||
} else None
|
||||
).flatten
|
||||
}
|
||||
@@ -186,7 +197,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
val newUserForm = mapping(
|
||||
"userName" -> trim(label("Username", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||
"password" -> trim(label("Password", text(required, maxlength(20)))),
|
||||
"password" -> trim(label("Password", text(required, maxlength(40)))),
|
||||
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
||||
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))),
|
||||
"extraMailAddresses" -> list(
|
||||
@@ -200,7 +211,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
val editUserForm = mapping(
|
||||
"userName" -> trim(label("Username", text(required, maxlength(100), identifier))),
|
||||
"password" -> trim(label("Password", optional(text(maxlength(20))))),
|
||||
"password" -> trim(label("Password", optional(text(maxlength(40))))),
|
||||
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
||||
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))),
|
||||
"extraMailAddresses" -> list(
|
||||
@@ -308,12 +319,13 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
post("/admin/system", form)(adminOnly { form =>
|
||||
saveSystemSettings(form)
|
||||
|
||||
if (form.sshAddress != context.settings.sshAddress) {
|
||||
if (form.ssh.bindAddress != context.settings.sshBindAddress || form.ssh.publicAddress != context.settings.sshPublicAddress) {
|
||||
SshServer.stop()
|
||||
for {
|
||||
sshAddress <- form.sshAddress
|
||||
bindAddress <- form.ssh.bindAddress
|
||||
publicAddress <- form.ssh.publicAddress.orElse(form.ssh.bindAddress)
|
||||
baseUrl <- form.baseUrl
|
||||
} SshServer.start(sshAddress, baseUrl)
|
||||
} SshServer.start(bindAddress, publicAddress, baseUrl)
|
||||
}
|
||||
|
||||
flash.update("info", "System settings has been updated.")
|
||||
@@ -361,8 +373,8 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
})
|
||||
|
||||
get("/admin/users")(adminOnly {
|
||||
val includeRemoved = params.get("includeRemoved").map(_.toBoolean).getOrElse(false)
|
||||
val includeGroups = params.get("includeGroups").map(_.toBoolean).getOrElse(false)
|
||||
val includeRemoved = params.get("includeRemoved").exists(_.toBoolean)
|
||||
val includeGroups = params.get("includeGroups").exists(_.toBoolean)
|
||||
val users = getAllUsers(includeRemoved, includeGroups)
|
||||
val members = users.collect {
|
||||
case account if (account.isGroupAccount) =>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package gitbucket.core.controller.api
|
||||
import gitbucket.core.api.{ApiObject, ApiRef, CreateARef, JsonFormat, UpdateARef}
|
||||
import gitbucket.core.api.{ApiError, ApiRef, CreateARef, JsonFormat, UpdateARef}
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.util.Directory.getRepositoryDir
|
||||
import gitbucket.core.util.ReferrerAuthenticator
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.{ReferrerAuthenticator, RepositoryName, WritableUsersAuthenticator}
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.ObjectId
|
||||
import org.eclipse.jgit.lib.RefUpdate.Result
|
||||
@@ -14,47 +15,38 @@ import scala.jdk.CollectionConverters._
|
||||
import scala.util.Using
|
||||
|
||||
trait ApiGitReferenceControllerBase extends ControllerBase {
|
||||
self: ReferrerAuthenticator =>
|
||||
self: ReferrerAuthenticator with WritableUsersAuthenticator =>
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[ApiGitReferenceControllerBase])
|
||||
|
||||
get("/api/v3/repos/:owner/:repository/git/refs")(referrersOnly { repository =>
|
||||
val result = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val refs = git
|
||||
.getRepository()
|
||||
.getRefDatabase()
|
||||
.getRefsByPrefix("refs")
|
||||
.asScala
|
||||
|
||||
refs.map(ApiRef.fromRef(RepositoryName(s"${repository.owner}/${repository.name}"), _))
|
||||
}
|
||||
JsonFormat(result)
|
||||
})
|
||||
|
||||
/*
|
||||
* i. Get a reference
|
||||
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#get-a-reference
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/git/ref/*")(referrersOnly { repository =>
|
||||
getRef()
|
||||
val revstr = multiParams("splat").head
|
||||
getRef(revstr, repository)
|
||||
})
|
||||
|
||||
// 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)
|
||||
|
||||
if (ref != null) {
|
||||
val sha = ref.getObjectId().name()
|
||||
JsonFormat(ApiRef(revstr, ApiObject(sha)))
|
||||
|
||||
} else {
|
||||
val refs = git
|
||||
.getRepository()
|
||||
.getRefDatabase()
|
||||
.getRefsByPrefix("refs/")
|
||||
.asScala
|
||||
|
||||
JsonFormat(refs.map { ref =>
|
||||
val sha = ref.getObjectId().name()
|
||||
ApiRef(revstr, ApiObject(sha))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
getRef(revstr, repository)
|
||||
})
|
||||
|
||||
/*
|
||||
* ii. Get all references
|
||||
@@ -65,17 +57,17 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
|
||||
* iii. Create a reference
|
||||
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#create-a-reference
|
||||
*/
|
||||
post("/api/v3/repos/:owner/:repository/git/refs")(referrersOnly { _ =>
|
||||
post("/api/v3/repos/:owner/:repository/git/refs")(referrersOnly { repository =>
|
||||
extractFromJsonBody[CreateARef].map {
|
||||
data =>
|
||||
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.owner))) { git =>
|
||||
val ref = git.getRepository.findRef(data.ref)
|
||||
if (ref == null) {
|
||||
val update = git.getRepository.updateRef(data.ref)
|
||||
update.setNewObjectId(ObjectId.fromString(data.sha))
|
||||
val result = update.update()
|
||||
result match {
|
||||
case Result.NEW => JsonFormat(ApiRef(update.getName, ApiObject(update.getNewObjectId.getName)))
|
||||
case Result.NEW => JsonFormat(ApiRef.fromRef(RepositoryName(repository.owner, repository.name), ref))
|
||||
case _ => UnprocessableEntity(result.name())
|
||||
}
|
||||
} else {
|
||||
@@ -89,11 +81,11 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
|
||||
* iv. Update a reference
|
||||
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#update-a-reference
|
||||
*/
|
||||
patch("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { _ =>
|
||||
patch("/api/v3/repos/:owner/:repository/git/refs/*")(writableUsersOnly { repository =>
|
||||
val refName = multiParams("splat").mkString("/")
|
||||
extractFromJsonBody[UpdateARef].map {
|
||||
data =>
|
||||
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.owner))) { git =>
|
||||
val ref = git.getRepository.findRef(refName)
|
||||
if (ref == null) {
|
||||
UnprocessableEntity("Ref does not exist.")
|
||||
@@ -104,7 +96,7 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
|
||||
val result = update.update()
|
||||
result match {
|
||||
case Result.FORCED | Result.FAST_FORWARD | Result.NO_CHANGE =>
|
||||
JsonFormat(ApiRef(update.getName, ApiObject(update.getNewObjectId.getName)))
|
||||
JsonFormat(ApiRef.fromRef(RepositoryName(repository), update.getRef))
|
||||
case _ => UnprocessableEntity(result.name())
|
||||
}
|
||||
}
|
||||
@@ -116,7 +108,7 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
|
||||
* v. Delete a reference
|
||||
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#delete-a-reference
|
||||
*/
|
||||
delete("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { _ =>
|
||||
delete("/api/v3/repos/:owner/:repository/git/refs/*")(writableUsersOnly { _ =>
|
||||
val refName = multiParams("splat").mkString("/")
|
||||
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||
val ref = git.getRepository.findRef(refName)
|
||||
@@ -133,4 +125,34 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
private def notFound(): ApiError = {
|
||||
response.setStatus(404)
|
||||
ApiError("Not Found")
|
||||
}
|
||||
|
||||
protected def getRef(revstr: String, repository: RepositoryInfo): AnyRef = {
|
||||
logger.debug(s"getRef: path '${revstr}'")
|
||||
|
||||
val name = RepositoryName(repository)
|
||||
val result = JsonFormat(revstr match {
|
||||
case "tags" => repository.tags.map(ApiRef.fromTag(name, _))
|
||||
case x if x.startsWith("tags/") =>
|
||||
val tagName = x.substring("tags/".length)
|
||||
repository.tags.find(_.name == tagName) match {
|
||||
case Some(tagInfo) => ApiRef.fromTag(name, tagInfo)
|
||||
case None => notFound()
|
||||
}
|
||||
case other =>
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
git.getRepository().findRef(other) match {
|
||||
case null => notFound()
|
||||
case ref => ApiRef.fromRef(name, ref)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
logger.debug(s"json result: $result")
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@ trait ApiIssueControllerBase extends ControllerBase {
|
||||
user = ApiUser(issueUser),
|
||||
assignee = assignedUser.map(ApiUser(_)),
|
||||
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||
.map(ApiLabel(_, RepositoryName(repository)))
|
||||
.map(ApiLabel(_, RepositoryName(repository))),
|
||||
issue.milestoneId.flatMap { getApiMilestone(repository, _) }
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -69,7 +70,8 @@ trait ApiIssueControllerBase extends ControllerBase {
|
||||
RepositoryName(repository),
|
||||
ApiUser(openedUser),
|
||||
issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)),
|
||||
getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository)))
|
||||
getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository))),
|
||||
issue.milestoneId.flatMap { getApiMilestone(repository, _) }
|
||||
)
|
||||
)
|
||||
}) getOrElse NotFound()
|
||||
@@ -103,7 +105,8 @@ trait ApiIssueControllerBase extends ControllerBase {
|
||||
ApiUser(loginAccount),
|
||||
issue.assignedUserName.flatMap(getAccountByUserName(_)).map(ApiUser(_)),
|
||||
getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||
.map(ApiLabel(_, RepositoryName(repository)))
|
||||
.map(ApiLabel(_, RepositoryName(repository))),
|
||||
issue.milestoneId.flatMap { getApiMilestone(repository, _) }
|
||||
)
|
||||
)
|
||||
}) getOrElse NotFound()
|
||||
|
||||
@@ -121,7 +121,7 @@ trait ApiIssueLabelControllerBase extends ControllerBase {
|
||||
*/
|
||||
post("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
|
||||
JsonFormat(for {
|
||||
data <- extractFromJsonBody[Seq[String]];
|
||||
data <- extractFromJsonBody[Seq[String]]
|
||||
issueId <- params("id").toIntOpt
|
||||
} yield {
|
||||
data.map { labelName =>
|
||||
@@ -160,7 +160,7 @@ trait ApiIssueLabelControllerBase extends ControllerBase {
|
||||
*/
|
||||
put("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
|
||||
JsonFormat(for {
|
||||
data <- extractFromJsonBody[Seq[String]];
|
||||
data <- extractFromJsonBody[Seq[String]]
|
||||
issueId <- params("id").toIntOpt
|
||||
} yield {
|
||||
deleteAllIssueLabels(repository.owner, repository.name, issueId, true)
|
||||
|
||||
@@ -2,7 +2,6 @@ package gitbucket.core.controller.api
|
||||
import gitbucket.core.api._
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.service.MilestonesService
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import org.scalatra.NoContent
|
||||
@@ -102,17 +101,4 @@ trait ApiIssueMilestoneControllerBase extends ControllerBase {
|
||||
NoContent()
|
||||
})
|
||||
|
||||
private def getApiMilestone(repository: RepositoryInfo, milestoneId: Int): Option[ApiMilestone] = {
|
||||
getMilestonesWithIssueCount(repository.owner, repository.name)
|
||||
.find(p => p._1.milestoneId == milestoneId)
|
||||
.map(
|
||||
milestoneWithIssue =>
|
||||
ApiMilestone(
|
||||
repository.repository,
|
||||
milestoneWithIssue._1,
|
||||
milestoneWithIssue._2,
|
||||
milestoneWithIssue._3
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ trait ApiReleaseControllerBase extends ControllerBase {
|
||||
/**
|
||||
* vi. Edit a release
|
||||
* https://developer.github.com/v3/repos/releases/#edit-a-release
|
||||
* Incompatiblity info: GitHub API requires :release_id, but GitBucket API requires :tag_name
|
||||
* Incompatibility info: GitHub API requires :release_id, but GitBucket API requires :tag_name
|
||||
*/
|
||||
patch("/api/v3/repos/:owner/:repository/releases/:tag")(writableUsersOnly { repository =>
|
||||
(for {
|
||||
@@ -103,7 +103,7 @@ trait ApiReleaseControllerBase extends ControllerBase {
|
||||
/**
|
||||
* vii. Delete a release
|
||||
* https://developer.github.com/v3/repos/releases/#delete-a-release
|
||||
* Incompatiblity info: GitHub API requires :release_id, but GitBucket API requires :tag_name
|
||||
* Incompatibility info: GitHub API requires :release_id, but GitBucket API requires :tag_name
|
||||
*/
|
||||
delete("/api/v3/repos/:owner/:repository/releases/:tag")(writableUsersOnly { repository =>
|
||||
val tag = params("tag")
|
||||
|
||||
@@ -40,7 +40,7 @@ trait ApiRepositoryCommitControllerBase extends ControllerBase {
|
||||
diffs = JGitUtil.getDiffs(git, commitInfo.parents.headOption, commitInfo.id, false, true),
|
||||
author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress),
|
||||
committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress),
|
||||
commentCount = getCommitComment(repository.owner, repository.name, commitInfo.id.toString).size
|
||||
commentCount = getCommitComment(repository.owner, repository.name, commitInfo.id).size
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import scala.util.Using
|
||||
|
||||
trait ApiRepositoryControllerBase extends ControllerBase {
|
||||
self: RepositoryService
|
||||
with ApiGitReferenceControllerBase
|
||||
with RepositoryCreationService
|
||||
with AccountService
|
||||
with OwnerAuthenticator
|
||||
@@ -184,9 +185,11 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
||||
* https://docs.github.com/en/rest/reference/repos#list-repository-tags
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/tags")(referrersOnly { repository =>
|
||||
JsonFormat(
|
||||
repository.tags.map(tagInfo => ApiTag(tagInfo.name, RepositoryName(repository), tagInfo.id))
|
||||
)
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
JsonFormat(
|
||||
self.getRef("tags", repository)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
package gitbucket.core.plugin
|
||||
|
||||
import javax.servlet.ServletContext
|
||||
|
||||
import gitbucket.core.controller.{Context, ControllerBase}
|
||||
import gitbucket.core.model.{Account, Issue}
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import io.github.gitbucket.solidbase.model.Version
|
||||
import org.apache.sshd.server.channel.ChannelSession
|
||||
import org.apache.sshd.server.command.Command
|
||||
import play.twirl.api.Html
|
||||
|
||||
import scala.util.Using
|
||||
|
||||
/**
|
||||
@@ -323,7 +324,7 @@ abstract class Plugin {
|
||||
/**
|
||||
* Override to add ssh command providers.
|
||||
*/
|
||||
val sshCommandProviders: Seq[PartialFunction[String, Command]] = Nil
|
||||
val sshCommandProviders: Seq[PartialFunction[String, ChannelSession => Command]] = Nil
|
||||
|
||||
/**
|
||||
* Override to add ssh command providers.
|
||||
@@ -332,7 +333,7 @@ abstract class Plugin {
|
||||
registry: PluginRegistry,
|
||||
context: ServletContext,
|
||||
settings: SystemSettings
|
||||
): Seq[PartialFunction[String, Command]] = Nil
|
||||
): Seq[PartialFunction[String, ChannelSession => Command]] = Nil
|
||||
|
||||
/**
|
||||
* This method is invoked in initialization of plugin system.
|
||||
|
||||
@@ -6,7 +6,6 @@ import java.nio.file.{Files, Paths, StandardWatchEventKinds}
|
||||
import java.util.Base64
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
import javax.servlet.ServletContext
|
||||
import com.github.zafarkhaja.semver.Version
|
||||
import gitbucket.core.controller.{Context, ControllerBase}
|
||||
@@ -21,6 +20,7 @@ import io.github.gitbucket.solidbase.Solidbase
|
||||
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
|
||||
import io.github.gitbucket.solidbase.model.Module
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.apache.sshd.server.channel.ChannelSession
|
||||
import org.apache.sshd.server.command.Command
|
||||
import org.slf4j.LoggerFactory
|
||||
import play.twirl.api.Html
|
||||
@@ -58,7 +58,7 @@ class PluginRegistry {
|
||||
private val suggestionProviders = new ConcurrentLinkedQueue[SuggestionProvider]
|
||||
suggestionProviders.add(new UserNameSuggestionProvider())
|
||||
suggestionProviders.add(new IssueSuggestionProvider())
|
||||
private val sshCommandProviders = new ConcurrentLinkedQueue[PartialFunction[String, Command]]()
|
||||
private val sshCommandProviders = new ConcurrentLinkedQueue[PartialFunction[String, ChannelSession => Command]]()
|
||||
|
||||
def addPlugin(pluginInfo: PluginInfo): Unit = plugins.add(pluginInfo)
|
||||
|
||||
@@ -177,10 +177,11 @@ class PluginRegistry {
|
||||
|
||||
def getSuggestionProviders: Seq[SuggestionProvider] = suggestionProviders.asScala.toSeq
|
||||
|
||||
def addSshCommandProvider(sshCommandProvider: PartialFunction[String, Command]): Unit =
|
||||
def addSshCommandProvider(sshCommandProvider: PartialFunction[String, ChannelSession => Command]): Unit =
|
||||
sshCommandProviders.add(sshCommandProvider)
|
||||
|
||||
def getSshCommandProviders: Seq[PartialFunction[String, Command]] = sshCommandProviders.asScala.toSeq
|
||||
def getSshCommandProviders: Seq[PartialFunction[String, ChannelSession => Command]] =
|
||||
sshCommandProviders.asScala.toSeq
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -47,7 +47,7 @@ trait AccountService {
|
||||
case _ => None
|
||||
}
|
||||
case account if (!account.isGroupAccount && account.password == sha1(password)) => Some(account)
|
||||
} getOrElse None
|
||||
}.flatten
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,9 +39,7 @@ trait ActivityService {
|
||||
if (isPublic == false) {
|
||||
list += activity
|
||||
} else {
|
||||
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName)
|
||||
.map(_.isPrivate)
|
||||
.getOrElse(true)) {
|
||||
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) {
|
||||
list += activity
|
||||
}
|
||||
}
|
||||
@@ -61,9 +59,7 @@ trait ActivityService {
|
||||
var json: String = null
|
||||
while (list.length < 50 && { json = reader.readLine(); json } != null) {
|
||||
val activity = read[Activity](json)
|
||||
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName)
|
||||
.map(_.isPrivate)
|
||||
.getOrElse(true)) {
|
||||
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) {
|
||||
list += activity
|
||||
}
|
||||
}
|
||||
@@ -83,9 +79,7 @@ trait ActivityService {
|
||||
val activity = read[Activity](json)
|
||||
if (owners.contains(activity.userName)) {
|
||||
list += activity
|
||||
} else if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName)
|
||||
.map(_.isPrivate)
|
||||
.getOrElse(true)) {
|
||||
} else if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) {
|
||||
list += activity
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,13 +336,16 @@ trait IssuesService {
|
||||
implicit s: Session
|
||||
) =
|
||||
Issues filter { t1 =>
|
||||
(if (repos.size == 1) {
|
||||
(if (repos.sizeIs == 1) {
|
||||
t1.byRepository(repos.head._1, repos.head._2)
|
||||
} else {
|
||||
((t1.userName ++ "/" ++ t1.repositoryName) inSetBind (repos.map { case (owner, repo) => s"$owner/$repo" }))
|
||||
}) &&
|
||||
(t1.closed === (condition.state == "closed").bind)
|
||||
.&&(t1.milestoneId.? isEmpty, condition.milestone == Some(None))
|
||||
(condition.state match {
|
||||
case "open" => t1.closed === false
|
||||
case "closed" => t1.closed === true
|
||||
case _ => t1.closed === true || t1.closed === false
|
||||
}).&&(t1.milestoneId.? isEmpty, condition.milestone == Some(None))
|
||||
.&&(t1.priorityId.? isEmpty, condition.priority == Some(None))
|
||||
.&&(t1.assignedUserName.? isEmpty, condition.assigned == Some(None))
|
||||
.&&(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) &&
|
||||
@@ -939,7 +942,7 @@ object IssuesService {
|
||||
case x => Some(x)
|
||||
},
|
||||
param(request, "mentioned"),
|
||||
param(request, "state", Seq("open", "closed")).getOrElse("open"),
|
||||
param(request, "state", Seq("open", "closed", "all")).getOrElse("open"),
|
||||
param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"),
|
||||
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
|
||||
param(request, "visibility"),
|
||||
@@ -960,7 +963,7 @@ object IssuesService {
|
||||
case x => Some(x)
|
||||
},
|
||||
param(request, "mentioned"),
|
||||
param(request, "state", Seq("open", "closed")).getOrElse("open"),
|
||||
param(request, "state", Seq("open", "closed", "all")).getOrElse("open"),
|
||||
param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"),
|
||||
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
|
||||
param(request, "visibility"),
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.api.ApiMilestone
|
||||
import gitbucket.core.model.Milestone
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.Profile.dateColumnType
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
|
||||
trait MilestonesService {
|
||||
|
||||
@@ -73,4 +75,17 @@ trait MilestonesService {
|
||||
.sortBy(t => (t.dueDate.asc, t.closedDate.desc, t.milestoneId.desc))
|
||||
.list
|
||||
|
||||
def getApiMilestone(repository: RepositoryInfo, milestoneId: Int)(implicit s: Session): Option[ApiMilestone] = {
|
||||
getMilestonesWithIssueCount(repository.owner, repository.name)
|
||||
.find(p => p._1.milestoneId == milestoneId)
|
||||
.map(
|
||||
milestoneWithIssue =>
|
||||
ApiMilestone(
|
||||
repository.repository,
|
||||
milestoneWithIssue._1,
|
||||
milestoneWithIssue._2,
|
||||
milestoneWithIssue._3
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ object ProtectedBranchService {
|
||||
pusher: String,
|
||||
mergePullRequest: Boolean
|
||||
)(implicit session: Session): Option[String] = {
|
||||
if (mergePullRequest == true) {
|
||||
if (mergePullRequest) {
|
||||
None
|
||||
} else {
|
||||
checkBranchProtection(owner, repository, receivePack, command, pusher)
|
||||
@@ -153,9 +153,9 @@ object ProtectedBranchService {
|
||||
Some("Cannot force-push to a protected branch")
|
||||
case ReceiveCommand.Type.UPDATE | ReceiveCommand.Type.UPDATE_NONFASTFORWARD if needStatusCheck(pusher) =>
|
||||
unSuccessedContexts(command.getNewId.name) match {
|
||||
case s if s.size == 1 => Some(s"""Required status check "${s.toSeq(0)}" is expected""")
|
||||
case s if s.size >= 1 => Some(s"${s.size} of ${contexts.size} required status checks are expected")
|
||||
case _ => None
|
||||
case s if s.sizeIs == 1 => Some(s"""Required status check "${s.toSeq(0)}" is expected""")
|
||||
case s if s.sizeIs >= 1 => Some(s"${s.size} of ${contexts.size} required status checks are expected")
|
||||
case _ => None
|
||||
}
|
||||
case ReceiveCommand.Type.DELETE =>
|
||||
Some("Cannot delete a protected branch")
|
||||
|
||||
@@ -509,10 +509,11 @@ trait PullRequestService {
|
||||
def getPullRequestComments(userName: String, repositoryName: String, issueId: Int, commits: Seq[CommitInfo])(
|
||||
implicit s: Session
|
||||
): Seq[Comment] = {
|
||||
(commits
|
||||
.map(commit => getCommitComments(userName, repositoryName, commit.id, true))
|
||||
.flatten ++ getComments(userName, repositoryName, issueId))
|
||||
.groupBy {
|
||||
(commits.flatMap(commit => getCommitComments(userName, repositoryName, commit.id, true)) ++ getComments(
|
||||
userName,
|
||||
repositoryName,
|
||||
issueId
|
||||
)).groupBy {
|
||||
case x: IssueComment => (Some(x.commentId), None, None, None)
|
||||
case x: CommitComment if x.fileName.isEmpty => (Some(x.commentId), None, None, None)
|
||||
case x: CommitComment => (None, x.fileName, x.originalOldLine, x.originalNewLine)
|
||||
@@ -578,7 +579,7 @@ trait PullRequestService {
|
||||
case (oldGit, newGit) =>
|
||||
if (originRepository.branchList.contains(originId)) {
|
||||
val forkedId2 =
|
||||
forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.id }.getOrElse(forkedId)
|
||||
forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.commitId }.getOrElse(forkedId)
|
||||
|
||||
val originId2 = JGitUtil.getForkedCommitId(
|
||||
oldGit,
|
||||
@@ -595,9 +596,9 @@ trait PullRequestService {
|
||||
|
||||
} else {
|
||||
val originId2 =
|
||||
originRepository.tags.collectFirst { case x if x.name == originId => x.id }.getOrElse(originId)
|
||||
originRepository.tags.collectFirst { case x if x.name == originId => x.commitId }.getOrElse(originId)
|
||||
val forkedId2 =
|
||||
forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.id }.getOrElse(forkedId)
|
||||
forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.commitId }.getOrElse(forkedId)
|
||||
|
||||
(Option(oldGit.getRepository.resolve(originId2)), Option(newGit.getRepository.resolve(forkedId2)))
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ object RepositoryCreationService {
|
||||
private val Creating = new ConcurrentHashMap[String, Option[String]]()
|
||||
|
||||
def isCreating(owner: String, repository: String): Boolean = {
|
||||
Option(Creating.get(s"${owner}/${repository}")).map(_.isEmpty).getOrElse(false)
|
||||
Option(Creating.get(s"${owner}/${repository}")).exists(_.isEmpty)
|
||||
}
|
||||
|
||||
def startCreation(owner: String, repository: String): Unit = {
|
||||
@@ -40,7 +40,7 @@ object RepositoryCreationService {
|
||||
}
|
||||
|
||||
def getCreationError(owner: String, repository: String): Option[String] = {
|
||||
Option(Creating.remove(s"${owner}/${repository}")).getOrElse(None)
|
||||
Option(Creating.remove(s"${owner}/${repository}")).flatten
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import gitbucket.core.util.JGitUtil.FileInfo
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.{Repository => _}
|
||||
|
||||
import scala.util.Using
|
||||
|
||||
trait RepositoryService {
|
||||
@@ -766,7 +767,8 @@ trait RepositoryService {
|
||||
JGitUtil.getContentFromId(git, file.id, true).collect {
|
||||
case bytes if FileUtil.isText(bytes) => StringUtil.convertFromByteArray(bytes)
|
||||
}
|
||||
} getOrElse None
|
||||
}
|
||||
.flatten
|
||||
} getOrElse ""
|
||||
}
|
||||
}
|
||||
@@ -834,12 +836,10 @@ object RepositoryService {
|
||||
|
||||
def httpUrl(owner: String, name: String)(implicit context: Context): String =
|
||||
s"${context.baseUrl}/git/${owner}/${name}.git"
|
||||
|
||||
def sshUrl(owner: String, name: String)(implicit context: Context): Option[String] =
|
||||
if (context.settings.ssh.enabled) {
|
||||
context.settings.sshAddress.map { x =>
|
||||
s"ssh://${x.genericUser}@${x.host}:${x.port}/${owner}/${name}.git"
|
||||
}
|
||||
} else None
|
||||
context.settings.sshUrl(owner, name)
|
||||
|
||||
def openRepoUrl(openUrl: String)(implicit context: Context): String =
|
||||
s"github-${context.platform}://openRepo/${openUrl}"
|
||||
|
||||
|
||||
@@ -4,9 +4,10 @@ import javax.servlet.http.HttpServletRequest
|
||||
import com.nimbusds.jose.JWSAlgorithm
|
||||
import com.nimbusds.oauth2.sdk.auth.Secret
|
||||
import com.nimbusds.oauth2.sdk.id.{ClientID, Issuer}
|
||||
import gitbucket.core.service.SystemSettingsService._
|
||||
import gitbucket.core.service.SystemSettingsService.{getOptionValue, _}
|
||||
import gitbucket.core.util.ConfigUtil._
|
||||
import gitbucket.core.util.Directory._
|
||||
|
||||
import scala.util.Using
|
||||
|
||||
trait SystemSettingsService {
|
||||
@@ -29,8 +30,14 @@ trait SystemSettingsService {
|
||||
props.setProperty(Notification, settings.notification.toString)
|
||||
props.setProperty(LimitVisibleRepositories, settings.limitVisibleRepositories.toString)
|
||||
props.setProperty(SshEnabled, settings.ssh.enabled.toString)
|
||||
settings.ssh.sshHost.foreach(x => props.setProperty(SshHost, x.trim))
|
||||
settings.ssh.sshPort.foreach(x => props.setProperty(SshPort, x.toString))
|
||||
settings.ssh.bindAddress.foreach { bindAddress =>
|
||||
props.setProperty(SshBindAddressHost, bindAddress.host.trim())
|
||||
props.setProperty(SshBindAddressPort, bindAddress.port.toString)
|
||||
}
|
||||
settings.ssh.publicAddress.foreach { publicAddress =>
|
||||
props.setProperty(SshPublicAddressHost, publicAddress.host.trim())
|
||||
props.setProperty(SshPublicAddressPort, publicAddress.port.toString)
|
||||
}
|
||||
props.setProperty(UseSMTP, settings.useSMTP.toString)
|
||||
if (settings.useSMTP) {
|
||||
settings.smtp.foreach { smtp =>
|
||||
@@ -72,7 +79,7 @@ trait SystemSettingsService {
|
||||
}
|
||||
}
|
||||
}
|
||||
props.setProperty(SkinName, settings.skinName.toString)
|
||||
props.setProperty(SkinName, settings.skinName)
|
||||
settings.userDefinedCss.foreach(x => props.setProperty(UserDefinedCss, x))
|
||||
props.setProperty(ShowMailAddress, settings.showMailAddress.toString)
|
||||
props.setProperty(WebHookBlockPrivateAddress, settings.webHook.blockPrivateAddress.toString)
|
||||
@@ -95,6 +102,10 @@ trait SystemSettingsService {
|
||||
props.load(in)
|
||||
}
|
||||
}
|
||||
loadSystemSettings(props)
|
||||
}
|
||||
|
||||
def loadSystemSettings(props: java.util.Properties): SystemSettings = {
|
||||
SystemSettings(
|
||||
getOptionValue[String](props, BaseURL, None).map(x => x.replaceFirst("/\\Z", "")),
|
||||
getOptionValue(props, Information, None),
|
||||
@@ -112,9 +123,20 @@ trait SystemSettingsService {
|
||||
getValue(props, Notification, false),
|
||||
getValue(props, LimitVisibleRepositories, false),
|
||||
Ssh(
|
||||
getValue(props, SshEnabled, false),
|
||||
getOptionValue[String](props, SshHost, None).map(_.trim),
|
||||
getOptionValue(props, SshPort, Some(DefaultSshPort))
|
||||
enabled = getValue(props, SshEnabled, false),
|
||||
bindAddress = {
|
||||
// try the new-style configuration first
|
||||
getOptionValue[String](props, SshBindAddressHost, None)
|
||||
.map(h => SshAddress(h, getValue(props, SshBindAddressPort, DefaultSshPort), GenericSshUser))
|
||||
.orElse(
|
||||
// otherwise try to get old-style configuration
|
||||
getOptionValue[String](props, SshHost, None)
|
||||
.map(_.trim)
|
||||
.map(h => SshAddress(h, getValue(props, SshPort, DefaultSshPort), GenericSshUser))
|
||||
)
|
||||
},
|
||||
publicAddress = getOptionValue[String](props, SshPublicAddressHost, None)
|
||||
.map(h => SshAddress(h, getValue(props, SshPublicAddressPort, PublicSshPort), GenericSshUser))
|
||||
),
|
||||
getValue(
|
||||
props,
|
||||
@@ -182,7 +204,6 @@ trait SystemSettingsService {
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object SystemSettingsService {
|
||||
@@ -214,7 +235,6 @@ object SystemSettingsService {
|
||||
upload: Upload,
|
||||
repositoryViewer: RepositoryViewerSettings
|
||||
) {
|
||||
|
||||
def baseUrl(request: HttpServletRequest): String =
|
||||
baseUrl.getOrElse(parseBaseUrl(request)).stripSuffix("/")
|
||||
|
||||
@@ -231,11 +251,17 @@ 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 sshBindAddress: Option[SshAddress] =
|
||||
ssh.bindAddress
|
||||
|
||||
def sshPublicAddress: Option[SshAddress] =
|
||||
ssh.publicAddress.orElse(ssh.bindAddress)
|
||||
|
||||
def sshUrl: Option[String] =
|
||||
ssh.getUrl
|
||||
|
||||
def sshUrl(owner: String, name: String): Option[String] =
|
||||
ssh.getUrl(owner: String, name: String)
|
||||
}
|
||||
|
||||
case class RepositoryOperation(
|
||||
@@ -248,9 +274,35 @@ object SystemSettingsService {
|
||||
|
||||
case class Ssh(
|
||||
enabled: Boolean,
|
||||
sshHost: Option[String],
|
||||
sshPort: Option[Int]
|
||||
)
|
||||
bindAddress: Option[SshAddress],
|
||||
publicAddress: Option[SshAddress]
|
||||
) {
|
||||
|
||||
def getUrl: Option[String] =
|
||||
if (enabled) {
|
||||
publicAddress.map(_.getUrl).orElse(bindAddress.map(_.getUrl))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
def getUrl(owner: String, name: String): Option[String] =
|
||||
if (enabled) {
|
||||
publicAddress
|
||||
.map(_.getUrl(owner, name))
|
||||
.orElse(bindAddress.map(_.getUrl(owner, name)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
object Ssh {
|
||||
def apply(
|
||||
enabled: Boolean,
|
||||
bindAddress: Option[SshAddress],
|
||||
publicAddress: Option[SshAddress]
|
||||
): Ssh =
|
||||
new Ssh(enabled, bindAddress, publicAddress.orElse(bindAddress))
|
||||
}
|
||||
|
||||
case class Ldap(
|
||||
host: String,
|
||||
@@ -296,7 +348,25 @@ object SystemSettingsService {
|
||||
password: Option[String]
|
||||
)
|
||||
|
||||
case class SshAddress(host: String, port: Int, genericUser: String)
|
||||
case class SshAddress(host: String, port: Int, genericUser: String) {
|
||||
|
||||
def isDefaultPort: Boolean =
|
||||
port == PublicSshPort
|
||||
|
||||
def getUrl: String =
|
||||
if (isDefaultPort) {
|
||||
s"${genericUser}@${host}"
|
||||
} else {
|
||||
s"${genericUser}@${host}:${port}"
|
||||
}
|
||||
|
||||
def getUrl(owner: String, name: String): String =
|
||||
if (isDefaultPort) {
|
||||
s"${genericUser}@${host}:${owner}/${name}.git"
|
||||
} else {
|
||||
s"ssh://${genericUser}@${host}:${port}/${owner}/${name}.git"
|
||||
}
|
||||
}
|
||||
|
||||
case class WebHook(blockPrivateAddress: Boolean, whitelist: Seq[String])
|
||||
|
||||
@@ -304,6 +374,8 @@ object SystemSettingsService {
|
||||
|
||||
case class RepositoryViewerSettings(maxFiles: Int)
|
||||
|
||||
val GenericSshUser = "git"
|
||||
val PublicSshPort = 22
|
||||
val DefaultSshPort = 29418
|
||||
val DefaultSmtpPort = 25
|
||||
val DefaultLdapPort = 389
|
||||
@@ -325,6 +397,10 @@ object SystemSettingsService {
|
||||
private val SshEnabled = "ssh"
|
||||
private val SshHost = "ssh.host"
|
||||
private val SshPort = "ssh.port"
|
||||
private val SshBindAddressHost = "ssh.bindAddress.host"
|
||||
private val SshBindAddressPort = "ssh.bindAddress.port"
|
||||
private val SshPublicAddressHost = "ssh.publicAddress.host"
|
||||
private val SshPublicAddressPort = "ssh.publicAddress.port"
|
||||
private val UseSMTP = "useSMTP"
|
||||
private val SmtpHost = "smtp.host"
|
||||
private val SmtpPort = "smtp.port"
|
||||
|
||||
@@ -35,6 +35,7 @@ import org.apache.http.HttpRequest
|
||||
import org.apache.http.HttpResponse
|
||||
import gitbucket.core.model.WebHookContentType
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import gitbucket.core.view.helpers.getApiMilestone
|
||||
import org.apache.http.client.entity.EntityBuilder
|
||||
import org.apache.http.entity.ContentType
|
||||
|
||||
@@ -394,7 +395,8 @@ trait WebHookPullRequestService extends WebHookService {
|
||||
ApiUser(issueUser),
|
||||
issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)),
|
||||
getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||
.map(ApiLabel(_, RepositoryName(repository)))
|
||||
.map(ApiLabel(_, RepositoryName(repository))),
|
||||
getApiMilestone(repository, issue.milestoneId getOrElse (0))
|
||||
),
|
||||
sender = ApiUser(sender)
|
||||
)
|
||||
@@ -576,6 +578,7 @@ trait WebHookIssueCommentService extends WebHookPullRequestService {
|
||||
commenter <- users.get(issueComment.commentedUserName)
|
||||
assignedUser = issue.assignedUserName.flatMap(users.get(_))
|
||||
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||
milestone = getApiMilestone(repository, issue.milestoneId getOrElse (0))
|
||||
} yield {
|
||||
WebHookIssueCommentPayload(
|
||||
issue = issue,
|
||||
@@ -586,7 +589,8 @@ trait WebHookIssueCommentService extends WebHookPullRequestService {
|
||||
repositoryUser = repoOwner,
|
||||
assignedUser = assignedUser,
|
||||
sender = sender,
|
||||
labels = labels
|
||||
labels = labels,
|
||||
milestone = milestone
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -760,7 +764,8 @@ object WebHookService {
|
||||
repositoryUser: Account,
|
||||
assignedUser: Option[Account],
|
||||
sender: Account,
|
||||
labels: List[Label]
|
||||
labels: List[Label],
|
||||
milestone: Option[ApiMilestone]
|
||||
): WebHookIssueCommentPayload =
|
||||
WebHookIssueCommentPayload(
|
||||
action = "created",
|
||||
@@ -770,7 +775,8 @@ object WebHookService {
|
||||
RepositoryName(repository),
|
||||
ApiUser(issueUser),
|
||||
assignedUser.map(ApiUser(_)),
|
||||
labels.map(ApiLabel(_, RepositoryName(repository)))
|
||||
labels.map(ApiLabel(_, RepositoryName(repository))),
|
||||
milestone
|
||||
),
|
||||
comment =
|
||||
ApiComment(comment, RepositoryName(repository), issue.issueId, ApiUser(commentUser), issue.isPullRequest),
|
||||
|
||||
@@ -75,13 +75,15 @@ trait WikiService {
|
||||
def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = {
|
||||
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
if (!JGitUtil.isEmpty(git)) {
|
||||
JGitUtil.getFileList(git, "master", ".").find(_.name == pageName + ".md").map { file =>
|
||||
val fileName = pageName + ".md"
|
||||
JGitUtil.getLatestCommitFromPath(git, fileName, "master").map { latestCommit =>
|
||||
val content = JGitUtil.getContentFromPath(git, latestCommit.getTree, fileName, true)
|
||||
WikiPageInfo(
|
||||
file.name,
|
||||
StringUtil.convertFromByteArray(git.getRepository.open(file.id).getBytes),
|
||||
file.author,
|
||||
file.time,
|
||||
file.commitId
|
||||
fileName,
|
||||
StringUtil.convertFromByteArray(content.getOrElse(Array.empty)),
|
||||
latestCommit.getAuthorIdent.getName,
|
||||
latestCommit.getAuthorIdent.getWhen,
|
||||
latestCommit.getName
|
||||
)
|
||||
}
|
||||
} else None
|
||||
@@ -145,7 +147,7 @@ trait WikiService {
|
||||
if (!p.getErrors.isEmpty) {
|
||||
throw new PatchFormatException(p.getErrors())
|
||||
}
|
||||
val revertInfo = (p.getFiles.asScala.map { fh =>
|
||||
val revertInfo = p.getFiles.asScala.flatMap { fh =>
|
||||
fh.getChangeType match {
|
||||
case DiffEntry.ChangeType.MODIFY => {
|
||||
val source =
|
||||
@@ -174,7 +176,7 @@ trait WikiService {
|
||||
}
|
||||
case _ => Nil
|
||||
}
|
||||
}).flatten
|
||||
}
|
||||
|
||||
if (revertInfo.nonEmpty) {
|
||||
val builder = DirCache.newInCore.builder()
|
||||
@@ -255,8 +257,7 @@ trait WikiService {
|
||||
created = false
|
||||
updated = JGitUtil
|
||||
.getContentFromId(git, tree.getEntryObjectId, true)
|
||||
.map(new String(_, "UTF-8") != content)
|
||||
.getOrElse(false)
|
||||
.exists(new String(_, "UTF-8") != content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ class GitAuthenticationFilter extends Filter with RepositoryService with Account
|
||||
case _ =>
|
||||
() =>
|
||||
{
|
||||
logger.debug(s"Not enough path arguments: ${request.paths}")
|
||||
logger.debug(s"Not enough path arguments: ${request.paths.mkString(", ")}")
|
||||
response.sendError(HttpServletResponse.SC_NOT_FOUND)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package gitbucket.core.servlet
|
||||
import java.io.File
|
||||
import java.util
|
||||
import java.util.Date
|
||||
|
||||
import scala.util.Using
|
||||
import gitbucket.core.api
|
||||
import gitbucket.core.api.JsonFormat.Context
|
||||
@@ -209,9 +208,7 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
|
||||
|
||||
val settings = loadSystemSettings()
|
||||
val baseUrl = settings.baseUrl(request)
|
||||
val sshUrl = settings.sshAddress.map { x =>
|
||||
s"${x.genericUser}@${x.host}:${x.port}"
|
||||
}
|
||||
val sshUrl = settings.sshUrl(owner, repository)
|
||||
|
||||
if (!repository.endsWith(".wiki")) {
|
||||
val hook = new CommitLogHook(owner, repository, pusher, baseUrl, sshUrl)
|
||||
@@ -347,9 +344,9 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
// set PR as merged
|
||||
val pulls = getPullRequestsByBranch(owner, repository, branchName, Some(false))
|
||||
pulls.foreach { pull =>
|
||||
if (commits.find { c =>
|
||||
if (commits.exists { c =>
|
||||
c.id == pull.commitIdTo
|
||||
}.isDefined) {
|
||||
}) {
|
||||
markMergeAndClosePullRequest(pusher, owner, repository, pull)
|
||||
getAccountByUserName(pusher).foreach { pusherAccount =>
|
||||
callPullRequestWebHook("closed", repositoryInfo, pull.issueId, pusherAccount, settings)
|
||||
|
||||
@@ -5,26 +5,31 @@ import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
|
||||
import gitbucket.core.service.{AccountService, DeployKeyService, RepositoryService, SystemSettingsService}
|
||||
import gitbucket.core.servlet.{CommitLogHook, Database}
|
||||
import gitbucket.core.util.Directory
|
||||
import org.apache.sshd.server.{Environment, ExitCallback, SessionAware}
|
||||
import org.apache.sshd.server.{Environment, ExitCallback}
|
||||
import org.apache.sshd.server.command.{Command, CommandFactory}
|
||||
import org.apache.sshd.server.session.ServerSession
|
||||
import org.apache.sshd.server.session.{ServerSession, ServerSessionAware}
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.{File, InputStream, OutputStream}
|
||||
|
||||
import java.io.{File, InputStream, OutputStream}
|
||||
import org.eclipse.jgit.api.Git
|
||||
import Directory._
|
||||
import gitbucket.core.service.SystemSettingsService.SshAddress
|
||||
import gitbucket.core.ssh.PublicKeyAuthenticator.AuthType
|
||||
import org.apache.sshd.server.channel.ChannelSession
|
||||
import org.eclipse.jgit.transport.{ReceivePack, UploadPack}
|
||||
import org.apache.sshd.server.shell.UnknownCommand
|
||||
import org.eclipse.jgit.errors.RepositoryNotFoundException
|
||||
|
||||
import scala.util.Using
|
||||
|
||||
object GitCommand {
|
||||
val DefaultCommandRegex = """\Agit-(upload|receive)-pack '/([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-\+_.]+).git'\Z""".r
|
||||
val SimpleCommandRegex = """\Agit-(upload|receive)-pack '/(.+\.git)'\Z""".r
|
||||
val DefaultCommandRegexPort22 = """\Agit-(upload|receive)-pack '/?([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-\+_.]+).git'\Z""".r
|
||||
val SimpleCommandRegexPort22 = """\Agit-(upload|receive)-pack '/?(.+\.git)'\Z""".r
|
||||
}
|
||||
|
||||
abstract class GitCommand extends Command with SessionAware {
|
||||
abstract class GitCommand extends Command with ServerSessionAware {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[GitCommand])
|
||||
|
||||
@@ -57,12 +62,12 @@ abstract class GitCommand extends Command with SessionAware {
|
||||
}
|
||||
}
|
||||
|
||||
final override def start(env: Environment): Unit = {
|
||||
final override def start(channel: ChannelSession, env: Environment): Unit = {
|
||||
val thread = new Thread(newTask())
|
||||
thread.start()
|
||||
}
|
||||
|
||||
override def destroy(): Unit = {}
|
||||
override def destroy(channel: ChannelSession): Unit = {}
|
||||
|
||||
override def setExitCallback(callback: ExitCallback): Unit = {
|
||||
this.callback = callback
|
||||
@@ -144,11 +149,9 @@ class DefaultGitUploadPack(owner: String, repoName: String)
|
||||
|
||||
override protected def runTask(authType: AuthType): Unit = {
|
||||
val execute = Database() withSession { implicit session =>
|
||||
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", ""))
|
||||
.map { repositoryInfo =>
|
||||
!repositoryInfo.repository.isPrivate || isReadableUser(authType, repositoryInfo)
|
||||
}
|
||||
.getOrElse(false)
|
||||
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")).exists { repositoryInfo =>
|
||||
!repositoryInfo.repository.isPrivate || isReadableUser(authType, repositoryInfo)
|
||||
}
|
||||
}
|
||||
|
||||
if (execute) {
|
||||
@@ -161,7 +164,7 @@ class DefaultGitUploadPack(owner: String, repoName: String)
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String, sshUrl: Option[String])
|
||||
class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String, sshAddress: SshAddress)
|
||||
extends DefaultGitCommand(owner, repoName)
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
@@ -169,11 +172,9 @@ class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String, ss
|
||||
|
||||
override protected def runTask(authType: AuthType): Unit = {
|
||||
val execute = Database() withSession { implicit session =>
|
||||
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", ""))
|
||||
.map { repositoryInfo =>
|
||||
isWritableUser(authType, repositoryInfo)
|
||||
}
|
||||
.getOrElse(false)
|
||||
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")).exists { repositoryInfo =>
|
||||
isWritableUser(authType, repositoryInfo)
|
||||
}
|
||||
}
|
||||
|
||||
if (execute) {
|
||||
@@ -181,7 +182,8 @@ class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String, ss
|
||||
val repository = git.getRepository
|
||||
val receive = new ReceivePack(repository)
|
||||
if (!repoName.endsWith(".wiki")) {
|
||||
val hook = new CommitLogHook(owner, repoName, userName(authType), baseUrl, sshUrl)
|
||||
val hook =
|
||||
new CommitLogHook(owner, repoName, userName(authType), baseUrl, Some(sshAddress.getUrl(owner, repoName)))
|
||||
receive.setPreReceiveHook(hook)
|
||||
receive.setPostReceiveHook(hook)
|
||||
}
|
||||
@@ -231,10 +233,10 @@ class PluginGitReceivePack(repoName: String, routing: GitRepositoryRouting)
|
||||
}
|
||||
}
|
||||
|
||||
class GitCommandFactory(baseUrl: String, sshUrl: Option[String]) extends CommandFactory {
|
||||
class GitCommandFactory(baseUrl: String, sshAddress: SshAddress) extends CommandFactory {
|
||||
private val logger = LoggerFactory.getLogger(classOf[GitCommandFactory])
|
||||
|
||||
override def createCommand(command: String): Command = {
|
||||
override def createCommand(channel: ChannelSession, command: String): Command = {
|
||||
import GitCommand._
|
||||
logger.debug(s"command: $command")
|
||||
|
||||
@@ -242,19 +244,24 @@ class GitCommandFactory(baseUrl: String, sshUrl: Option[String]) extends Command
|
||||
case f if f.isDefinedAt(command) => f(command)
|
||||
}
|
||||
|
||||
pluginCommand match {
|
||||
case Some(x) => x
|
||||
case None =>
|
||||
command match {
|
||||
case SimpleCommandRegex("upload", repoName) if (pluginRepository(repoName)) =>
|
||||
new PluginGitUploadPack(repoName, routing(repoName))
|
||||
case SimpleCommandRegex("receive", repoName) if (pluginRepository(repoName)) =>
|
||||
new PluginGitReceivePack(repoName, routing(repoName))
|
||||
case DefaultCommandRegex("upload", owner, repoName) => new DefaultGitUploadPack(owner, repoName)
|
||||
case DefaultCommandRegex("receive", owner, repoName) =>
|
||||
new DefaultGitReceivePack(owner, repoName, baseUrl, sshUrl)
|
||||
case _ => new UnknownCommand(command)
|
||||
pluginCommand.map(_.apply(channel)).getOrElse {
|
||||
val (simpleRegex, defaultRegex) =
|
||||
if (sshAddress.isDefaultPort) {
|
||||
(SimpleCommandRegexPort22, DefaultCommandRegexPort22)
|
||||
} else {
|
||||
(SimpleCommandRegex, DefaultCommandRegex)
|
||||
}
|
||||
command match {
|
||||
case simpleRegex("upload", repoName) if pluginRepository(repoName) =>
|
||||
new PluginGitUploadPack(repoName, routing(repoName))
|
||||
case simpleRegex("receive", repoName) if pluginRepository(repoName) =>
|
||||
new PluginGitReceivePack(repoName, routing(repoName))
|
||||
case defaultRegex("upload", owner, repoName) =>
|
||||
new DefaultGitUploadPack(owner, repoName)
|
||||
case defaultRegex("receive", owner, repoName) =>
|
||||
new DefaultGitReceivePack(owner, repoName, baseUrl, sshAddress)
|
||||
case _ => new UnknownCommand(command)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
package gitbucket.core.ssh
|
||||
|
||||
import gitbucket.core.service.SystemSettingsService.SshAddress
|
||||
import org.apache.sshd.common.Factory
|
||||
import org.apache.sshd.server.channel.ChannelSession
|
||||
import org.apache.sshd.server.{Environment, ExitCallback}
|
||||
import org.apache.sshd.server.command.Command
|
||||
import java.io.{OutputStream, InputStream}
|
||||
import org.apache.sshd.server.shell.ShellFactory
|
||||
|
||||
import java.io.{InputStream, OutputStream}
|
||||
import org.eclipse.jgit.lib.Constants
|
||||
|
||||
class NoShell(sshAddress: SshAddress) extends Factory[Command] {
|
||||
override def create(): Command = new Command() {
|
||||
class NoShell(sshAddress: SshAddress) extends ShellFactory {
|
||||
override def createShell(channel: ChannelSession): Command = new Command() {
|
||||
private var in: InputStream = null
|
||||
private var out: OutputStream = null
|
||||
private var err: OutputStream = null
|
||||
private var callback: ExitCallback = null
|
||||
|
||||
override def start(env: Environment): Unit = {
|
||||
override def start(channel: ChannelSession, env: Environment): Unit = {
|
||||
val placeholderAddress = sshAddress.getUrl("OWNER", "REPOSITORY_NAME")
|
||||
val message =
|
||||
"""
|
||||
| Welcome to
|
||||
@@ -30,8 +33,8 @@ class NoShell(sshAddress: SshAddress) extends Factory[Command] {
|
||||
|
|
||||
| Please use:
|
||||
|
|
||||
| git clone ssh://%s@%s:%d/OWNER/REPOSITORY_NAME.git
|
||||
""".stripMargin.format(sshAddress.genericUser, sshAddress.host, sshAddress.port).replace("\n", "\r\n") + "\r\n"
|
||||
| git clone %s
|
||||
""".stripMargin.format(placeholderAddress).replace("\n", "\r\n") + "\r\n"
|
||||
err.write(Constants.encode(message))
|
||||
err.flush()
|
||||
in.close()
|
||||
@@ -40,7 +43,7 @@ class NoShell(sshAddress: SshAddress) extends Factory[Command] {
|
||||
callback.onExit(127)
|
||||
}
|
||||
|
||||
override def destroy(): Unit = {}
|
||||
override def destroy(channel: ChannelSession): Unit = {}
|
||||
|
||||
override def setInputStream(in: InputStream): Unit = {
|
||||
this.in = in
|
||||
|
||||
@@ -8,13 +8,13 @@ import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.ssh.PublicKeyAuthenticator.AuthType
|
||||
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator
|
||||
import org.apache.sshd.server.session.ServerSession
|
||||
import org.apache.sshd.common.AttributeStore
|
||||
import org.apache.sshd.common.AttributeRepository
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
object PublicKeyAuthenticator {
|
||||
|
||||
// put in the ServerSession here to be read by GitCommand later
|
||||
private val authTypeSessionKey = new AttributeStore.AttributeKey[AuthType]
|
||||
private val authTypeSessionKey = new AttributeRepository.AttributeKey[AuthType]
|
||||
|
||||
def putAuthType(serverSession: ServerSession, authType: AuthType): Unit =
|
||||
serverSession.setAttribute(authTypeSessionKey, authType)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package gitbucket.core.ssh
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import javax.servlet.{ServletContextEvent, ServletContextListener}
|
||||
|
||||
import gitbucket.core.service.SystemSettingsService
|
||||
import gitbucket.core.service.SystemSettingsService.SshAddress
|
||||
import gitbucket.core.util.Directory
|
||||
@@ -11,40 +10,48 @@ import org.slf4j.LoggerFactory
|
||||
|
||||
object SshServer {
|
||||
private val logger = LoggerFactory.getLogger(SshServer.getClass)
|
||||
private val server = org.apache.sshd.server.SshServer.setUpDefaultServer()
|
||||
private val active = new AtomicBoolean(false)
|
||||
private val server = new AtomicReference[org.apache.sshd.server.SshServer](null)
|
||||
|
||||
private def configure(sshAddress: SshAddress, baseUrl: String) = {
|
||||
server.setPort(sshAddress.port)
|
||||
private def configure(
|
||||
bindAddress: SshAddress,
|
||||
publicAddress: SshAddress,
|
||||
baseUrl: String
|
||||
): org.apache.sshd.server.SshServer = {
|
||||
val server = org.apache.sshd.server.SshServer.setUpDefaultServer()
|
||||
server.setPort(bindAddress.port)
|
||||
val provider = new SimpleGeneratorHostKeyProvider(
|
||||
java.nio.file.Paths.get(s"${Directory.GitBucketHome}/gitbucket.ser")
|
||||
)
|
||||
provider.setAlgorithm("RSA")
|
||||
provider.setOverwriteAllowed(false)
|
||||
server.setKeyPairProvider(provider)
|
||||
server.setPublickeyAuthenticator(new PublicKeyAuthenticator(sshAddress.genericUser))
|
||||
server.setPublickeyAuthenticator(new PublicKeyAuthenticator(bindAddress.genericUser))
|
||||
server.setCommandFactory(
|
||||
new GitCommandFactory(baseUrl, Some(s"${sshAddress.genericUser}@${sshAddress.host}:${sshAddress.port}"))
|
||||
new GitCommandFactory(baseUrl, publicAddress)
|
||||
)
|
||||
server.setShellFactory(new NoShell(sshAddress))
|
||||
server.setShellFactory(new NoShell(publicAddress))
|
||||
server
|
||||
}
|
||||
|
||||
def start(sshAddress: SshAddress, baseUrl: String) = {
|
||||
if (active.compareAndSet(false, true)) {
|
||||
configure(sshAddress, baseUrl)
|
||||
server.start()
|
||||
logger.info(s"Start SSH Server Listen on ${server.getPort}")
|
||||
def start(bindAddress: SshAddress, publicAddress: SshAddress, baseUrl: String): Unit = {
|
||||
this.server.synchronized {
|
||||
val server = configure(bindAddress, publicAddress, baseUrl)
|
||||
if (this.server.compareAndSet(null, server)) {
|
||||
server.start()
|
||||
logger.info(s"Start SSH Server Listen on ${server.getPort}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def stop() = {
|
||||
if (active.compareAndSet(true, false)) {
|
||||
server.stop(true)
|
||||
logger.info("SSH Server is stopped.")
|
||||
def stop(): Unit = {
|
||||
this.server.synchronized {
|
||||
val server = this.server.getAndSet(null)
|
||||
if (server != null) {
|
||||
server.stop()
|
||||
logger.info("SSH Server is stopped.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def isActive = active.get
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -59,13 +66,14 @@ class SshServerListener extends ServletContextListener with SystemSettingsServic
|
||||
|
||||
override def contextInitialized(sce: ServletContextEvent): Unit = {
|
||||
val settings = loadSystemSettings()
|
||||
if (settings.sshAddress.isDefined && settings.baseUrl.isEmpty) {
|
||||
if (settings.sshBindAddress.isDefined && settings.baseUrl.isEmpty) {
|
||||
logger.error("Could not start SshServer because the baseUrl is not configured.")
|
||||
}
|
||||
for {
|
||||
sshAddress <- settings.sshAddress
|
||||
bindAddress <- settings.sshBindAddress
|
||||
publicAddress <- settings.sshPublicAddress
|
||||
baseUrl <- settings.baseUrl
|
||||
} SshServer.start(sshAddress, baseUrl)
|
||||
} SshServer.start(bindAddress, publicAddress, baseUrl)
|
||||
}
|
||||
|
||||
override def contextDestroyed(sce: ServletContextEvent): Unit = {
|
||||
|
||||
@@ -38,7 +38,7 @@ trait OwnerAuthenticator { self: ControllerBase with RepositoryService with Acco
|
||||
case Some(x) if (repository.owner == x.userName) => action(repository)
|
||||
// TODO Repository management is allowed for only group managers?
|
||||
case Some(x) if (getGroupMembers(repository.owner).exists { m =>
|
||||
m.userName == x.userName && m.isManager == true
|
||||
m.userName == x.userName && m.isManager
|
||||
}) =>
|
||||
action(repository)
|
||||
case Some(x) if (getCollaboratorUserNames(userName, repoName, Seq(Role.ADMIN)).contains(x.userName)) =>
|
||||
|
||||
@@ -52,7 +52,7 @@ object EditorConfigUtil {
|
||||
}
|
||||
|
||||
override def getParent: ResourcePath = {
|
||||
Option(path.getParentPath).map { new JGitResourcePath(repo, revStr, _) }.getOrElse(null)
|
||||
Option(path.getParentPath).map { new JGitResourcePath(repo, revStr, _) }.orNull
|
||||
}
|
||||
|
||||
override def openRandomReader(): Resource.RandomReader = {
|
||||
@@ -70,7 +70,7 @@ object EditorConfigUtil {
|
||||
private class JGitResourcePath(repo: Repository, revStr: String, path: Ec4jPath) extends ResourcePath {
|
||||
|
||||
override def getParent: ResourcePath = {
|
||||
Option(path.getParentPath).map { new JGitResourcePath(repo, revStr, _) }.getOrElse(null)
|
||||
Option(path.getParentPath).map { new JGitResourcePath(repo, revStr, _) }.orNull
|
||||
}
|
||||
|
||||
override def getPath: Ec4jPath = {
|
||||
|
||||
@@ -22,9 +22,7 @@ object Implicits {
|
||||
implicit def request2Session(implicit request: HttpServletRequest): JdbcBackend#Session = Database.getSession(request)
|
||||
|
||||
implicit def context2ApiJsonFormatContext(implicit context: Context): JsonFormat.Context =
|
||||
JsonFormat.Context(context.baseUrl, context.settings.sshAddress.map { x =>
|
||||
s"${x.genericUser}@${x.host}:${x.port}"
|
||||
})
|
||||
JsonFormat.Context(context.baseUrl, context.settings.sshUrl)
|
||||
|
||||
implicit class RichSeq[A](private val seq: Seq[A]) extends AnyVal {
|
||||
|
||||
|
||||
@@ -227,7 +227,7 @@ object JDBCUtil {
|
||||
if (noPreds.isEmpty) {
|
||||
if (hasPreds.isEmpty) done else sys.error(hasPreds.toString)
|
||||
} else {
|
||||
val found = noPreds.map { _._1 }
|
||||
val found = noPreds.keys
|
||||
tsort(hasPreds.map { case (k, v) => (k, v -- found) }, done ++ found)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,9 +37,8 @@ object JGitUtil {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(JGitUtil.getClass)
|
||||
|
||||
implicit val objectDatabaseReleasable: Releasable[ObjectDatabase] = new Releasable[ObjectDatabase] {
|
||||
override def release(resource: ObjectDatabase): Unit = resource.close()
|
||||
}
|
||||
implicit val objectDatabaseReleasable: Releasable[ObjectDatabase] =
|
||||
_.close()
|
||||
|
||||
/**
|
||||
* The repository data.
|
||||
@@ -229,10 +228,11 @@ object JGitUtil {
|
||||
*
|
||||
* @param name the tag name
|
||||
* @param time the tagged date
|
||||
* @param id the commit id
|
||||
* @param commitId the commit id
|
||||
* @param message the message of the tagged commit
|
||||
* @param objectId the tag object id
|
||||
*/
|
||||
case class TagInfo(name: String, time: Date, id: String, message: String)
|
||||
case class TagInfo(name: String, time: Date, commitId: String, message: String, objectId: String)
|
||||
|
||||
/**
|
||||
* The submodule data
|
||||
@@ -348,7 +348,8 @@ object JGitUtil {
|
||||
ref.getName.stripPrefix("refs/tags/"),
|
||||
revCommit.getCommitterIdent.getWhen,
|
||||
revCommit.getName,
|
||||
revCommit.getShortMessage
|
||||
revCommit.getShortMessage,
|
||||
ref.getObjectId.getName
|
||||
)
|
||||
)
|
||||
} catch {
|
||||
@@ -383,7 +384,7 @@ object JGitUtil {
|
||||
path: String = ".",
|
||||
baseUrl: Option[String] = None,
|
||||
commitCount: Int = 0,
|
||||
maxFiles: Int = 100
|
||||
maxFiles: Int = 5
|
||||
): List[FileInfo] = {
|
||||
Using.resource(new RevWalk(git.getRepository)) { revWalk =>
|
||||
val objectId = git.getRepository.resolve(revision)
|
||||
@@ -659,9 +660,13 @@ object JGitUtil {
|
||||
*/
|
||||
def getLatestCommitFromPaths(git: Git, paths: List[String], revision: String): Map[String, RevCommit] = {
|
||||
val start = getRevCommitFromId(git, git.getRepository.resolve(revision))
|
||||
paths.map { path =>
|
||||
paths.flatMap { path =>
|
||||
val commit = git.log.add(start).addPath(path).setMaxCount(1).call.iterator.next
|
||||
(path, commit)
|
||||
if (commit == null) {
|
||||
None
|
||||
} else {
|
||||
Some((path, commit))
|
||||
}
|
||||
}.toMap
|
||||
}
|
||||
|
||||
@@ -672,11 +677,10 @@ object JGitUtil {
|
||||
df.setDiffComparator(RawTextComparator.DEFAULT)
|
||||
df.setDetectRenames(true)
|
||||
getDiffEntries(git, from, to)
|
||||
.map { entry =>
|
||||
.foreach { entry =>
|
||||
df.format(entry)
|
||||
new String(out.toByteArray, "UTF-8")
|
||||
}
|
||||
.mkString("\n")
|
||||
new String(out.toByteArray, "UTF-8")
|
||||
}
|
||||
|
||||
private def getDiffEntries(git: Git, from: Option[String], to: String): Seq[DiffEntry] = {
|
||||
@@ -1259,7 +1263,7 @@ object JGitUtil {
|
||||
val blame = blamer.call()
|
||||
var blameMap = Map[String, JGitUtil.BlameInfo]()
|
||||
var idLine = List[(String, Int)]()
|
||||
0.to(blame.getResultContents().size() - 1).map { i =>
|
||||
0.until(blame.getResultContents().size()).foreach { i =>
|
||||
val c = blame.getSourceCommit(i)
|
||||
if (!blameMap.contains(c.name)) {
|
||||
blameMap += c.name -> JGitUtil.BlameInfo(
|
||||
|
||||
@@ -41,7 +41,7 @@ class Mailer(settings: SystemSettings) {
|
||||
htmlMsg: Option[String] = None,
|
||||
loginAccount: Option[Account] = None
|
||||
): Option[HtmlEmail] = {
|
||||
if (settings.notification == true) {
|
||||
if (settings.notification) {
|
||||
settings.smtp.map { smtp =>
|
||||
val email = new HtmlEmail
|
||||
email.setHostName(smtp.host)
|
||||
@@ -51,7 +51,7 @@ class Mailer(settings: SystemSettings) {
|
||||
}
|
||||
smtp.ssl.foreach { ssl =>
|
||||
email.setSSLOnConnect(ssl)
|
||||
if (ssl == true) {
|
||||
if (ssl) {
|
||||
email.setSslSmtpPort(smtp.port.get.toString)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,8 +184,7 @@ object StringUtil {
|
||||
def removeUserName(baseUrl: String): String = baseUrl.replaceFirst("(https?://).+@", "$1")
|
||||
|
||||
gitRepositoryUrl match {
|
||||
case GitBucketUrlPattern(base, user, repository)
|
||||
if baseUrl.map(removeUserName(base).startsWith).getOrElse(false) =>
|
||||
case GitBucketUrlPattern(base, user, repository) if baseUrl.exists(removeUserName(base).startsWith) =>
|
||||
s"${removeUserName(base)}/$user/$repository"
|
||||
case GitHubUrlPattern(_, user, repository) => s"https://github.com/$user/$repository"
|
||||
case BitBucketUrlPattern(_, user, repository) => s"https://bitbucket.org/$user/$repository"
|
||||
|
||||
@@ -45,11 +45,14 @@ trait AvatarImageProvider { self: RequestCache =>
|
||||
|
||||
if (tooltip) {
|
||||
Html(
|
||||
s"""<img src="${src}" class="${if (size > 20) { "avatar" } else { "avatar-mini" }}" style="width: ${size}px; height: ${size}px;" data-toggle="tooltip" title="${userName}" alt="@${userName}" />"""
|
||||
s"""<img src="${src}" class="${if (size > 20) { "avatar" } else { "avatar-mini" }}" style="width: ${size}px; height: ${size}px;"
|
||||
| alt="@${StringUtil.escapeHtml(userName)}"
|
||||
| data-toggle="tooltip" title="${StringUtil.escapeHtml(userName)}" />""".stripMargin
|
||||
)
|
||||
} else {
|
||||
Html(
|
||||
s"""<img src="${src}" class="${if (size > 20) { "avatar" } else { "avatar-mini" }}" style="width: ${size}px; height: ${size}px;" alt="@${userName}" />"""
|
||||
s"""<img src="${src}" class="${if (size > 20) { "avatar" } else { "avatar-mini" }}" style="width: ${size}px; height: ${size}px;"
|
||||
| alt="@${StringUtil.escapeHtml(userName)}" />""".stripMargin
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,7 +336,7 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
)(implicit context: Context): Html = {
|
||||
|
||||
val avatarHtml = avatar(userName, size, tooltip, mailAddress)
|
||||
val contentHtml = if (label == true) Html(avatarHtml.body + " " + userName) else avatarHtml
|
||||
val contentHtml = if (label) Html(avatarHtml.body + " " + userName) else avatarHtml
|
||||
|
||||
userWithContent(userName, mailAddress)(contentHtml)
|
||||
}
|
||||
@@ -446,14 +446,14 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
}
|
||||
result.append(c)
|
||||
}
|
||||
case '>' if tag == true => {
|
||||
case '>' if tag => {
|
||||
tag = false
|
||||
result.append(c)
|
||||
}
|
||||
case _ if tag == false => {
|
||||
text.append(c)
|
||||
}
|
||||
case _ if tag == true => {
|
||||
case _ if tag => {
|
||||
result.append(c)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
@(account: gitbucket.core.model.Account,
|
||||
personalTokens: List[gitbucket.core.model.AccessToken],
|
||||
gneratedToken: Option[(gitbucket.core.model.AccessToken, String)])(implicit context: gitbucket.core.controller.Context)
|
||||
generatedToken: Option[(gitbucket.core.model.AccessToken, String)])(implicit context: gitbucket.core.controller.Context)
|
||||
@gitbucket.core.html.main("Applications"){
|
||||
@gitbucket.core.account.html.menu("application", context.loginAccount.get.userName, false){
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading strong">Personal access tokens</div>
|
||||
<div class="panel-body">
|
||||
@if(personalTokens.isEmpty && gneratedToken.isEmpty){
|
||||
@if(personalTokens.isEmpty && generatedToken.isEmpty){
|
||||
No tokens.
|
||||
} else {
|
||||
Tokens you have generated which can be used to access the GitBucket API.
|
||||
<hr style="margin-top: 10px;">
|
||||
}
|
||||
@gneratedToken.map { case (token, tokenString) =>
|
||||
@generatedToken.map { case (token, tokenString) =>
|
||||
<div class="alert alert-info">
|
||||
Make sure to copy your new personal access token now. You won't be able to see it again!
|
||||
</div>
|
||||
|
||||
@@ -19,22 +19,40 @@
|
||||
<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>
|
||||
<span class="muted normal">(Both SSH bind 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 class="bindAddress">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-2" for="sshBindHost">SSH bind host</label>
|
||||
<div class="col-md-10">
|
||||
<input type="text" id="sshBindHost" name="ssh.bindAddress.host" class="form-control" value="@context.settings.ssh.bindAddress.map(_.host)"/>
|
||||
<span id="error-ssh_bindAddress_host" class="error"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-2" for="sshBindPort">SSH bind port</label>
|
||||
<div class="col-md-10">
|
||||
<input type="text" id="sshBindPort" name="ssh.bindAddress.port" class="form-control" value="@context.settings.ssh.bindAddress.map(_.port)"/>
|
||||
<span id="error-ssh_bindAddress_port" class="error"></span>
|
||||
</div>
|
||||
</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 class="publicAddress">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-2" for="sshPublicHost">SSH public host</label>
|
||||
<div class="col-md-10">
|
||||
<input type="text" id="sshPublicHost" name="ssh.publicAddress.host" class="form-control" value="@context.settings.ssh.publicAddress.map(_.host)"/>
|
||||
<span id="error-ssh_publicAddress_host" class="error"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-2" for="sshPublicPort">SSH public port</label>
|
||||
<div class="col-md-10">
|
||||
<input type="text" id="sshPublicPort" name="ssh.publicAddress.port" class="form-control" value="@context.settings.ssh.publicAddress.map(_.port)"/>
|
||||
<span id="error-ssh_publicAddress_port" class="error"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<link href="@helpers.assets("/vendors/google-code-prettify/prettify.css")" type="text/css" rel="stylesheet"/>
|
||||
<script src="@helpers.assets("/vendors/google-code-prettify/prettify.js")"></script>
|
||||
<script>
|
||||
$(function(){
|
||||
@if(elastic){
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<td>
|
||||
<div class="col-md-2 text-right">
|
||||
<a href="@helpers.url(repository)/tree/@helpers.urlEncode(tag.name)" class="strong"><i class="octicon octicon-tag"></i>@tag.name</a><br>
|
||||
<a href="@helpers.url(repository)/commit/@tag.id" class="monospace muted"><i class="octicon octicon-git-commit"></i>@tag.id.substring(0, 7)</a><br>
|
||||
<a href="@helpers.url(repository)/commit/@tag.commitId" class="monospace muted"><i class="octicon octicon-git-commit"></i>@tag.commitId.substring(0, 7)</a><br>
|
||||
<span class="muted">@gitbucket.core.helper.html.datetimeago(tag.time)</span>
|
||||
</div>
|
||||
<div class="col-md-10" style="border-left: 1px solid #eee">
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
@defining(repository.tags.find(_.name == release.tag)){ tag =>
|
||||
@tag.map { tag =>
|
||||
<a href="@helpers.url(repository)/tree/@helpers.urlEncode(tag.name)" class="strong"><i class="octicon octicon-tag"></i>@tag.name</a><br>
|
||||
<a href="@helpers.url(repository)/commit/@tag.id" class="monospace muted"><i class="octicon octicon-git-commit"></i>@tag.id.substring(0, 7)</a><br>
|
||||
<a href="@helpers.url(repository)/commit/@tag.commitId" class="monospace muted"><i class="octicon octicon-git-commit"></i>@tag.commitId.substring(0, 7)</a><br>
|
||||
<span class="muted">@gitbucket.core.helper.html.datetimeago(tag.time)</span>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ function updateHighlighting() {
|
||||
const isDark = @{highlighterTheme.contains("dark").toString};
|
||||
if (hash.match(/#L\d+(-L\d+)?/)) {
|
||||
if (isDark) {
|
||||
$('li.highlight').removeClass('highlight-dark');
|
||||
$('li.highlight-dark').removeClass('highlight-dark');
|
||||
} else {
|
||||
$('li.highlight').removeClass('highlight');
|
||||
}
|
||||
|
||||
@@ -9,32 +9,22 @@
|
||||
@gitbucket.core.html.main(s"Compare Revisions - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@gitbucket.core.helper.html.information(info)
|
||||
@gitbucket.core.html.menu("wiki", repository){
|
||||
<ul class="nav nav-tabs fill-width">
|
||||
<li>
|
||||
<h1 class="wiki-title"><span class="muted">Compare Revisions</span></h1>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<div class="btn-group">
|
||||
@if(pageName.isDefined){
|
||||
<a class="btn btn-small btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)">View Page</a>
|
||||
<a class="btn btn-small btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_history">Back to Page History</a>
|
||||
<div class="pull-right">
|
||||
@if(pageName.isDefined){
|
||||
<a class="btn btn-small btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)">View Page</a>
|
||||
<a class="btn btn-small btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_history">Back to Page History</a>
|
||||
} else {
|
||||
<a class="btn btn-small btn-default" href="@helpers.url(repository)/wiki/_history">Back to Wiki History</a>
|
||||
}
|
||||
@if(isEditable) {
|
||||
@if(pageName.isDefined) {
|
||||
<a href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_revert/@from...@to" class="btn btn-danger">Revert Changes</a>
|
||||
} else {
|
||||
<a class="btn btn-small btn-default" href="@helpers.url(repository)/wiki/_history">Back to Wiki History</a>
|
||||
<a href="@helpers.url(repository)/wiki/_revert/@from...@to" class="btn btn-danger">Revert Changes</a>
|
||||
}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="pull-left">
|
||||
@gitbucket.core.helper.html.diff(diffs, repository, None, None, false, None, false, false)
|
||||
}
|
||||
</div>
|
||||
@if(isEditable){
|
||||
<div>
|
||||
@if(pageName.isDefined){
|
||||
<a href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_revert/@from...@to" class="btn">Revert Changes</a>
|
||||
} else {
|
||||
<a href="@helpers.url(repository)/wiki/_revert/@from...@to" class="btn">Revert Changes</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<h1 class="body-title"><span class="muted">Compare Revisions</span></h1>
|
||||
@gitbucket.core.helper.html.diff(diffs, repository, None, None, false, None, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<a class="btn btn-danger" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_delete" id="delete">Delete Page</a>
|
||||
}
|
||||
</div>
|
||||
<h1 class="wiki-title"><span class="muted">Editing</span> @if(pageName.isEmpty){New Page} else {@pageName}</h1>
|
||||
<h1 class="body-title"><span class="muted">Editing</span> @if(pageName.isEmpty){New Page} else {@pageName}</h1>
|
||||
<form action="@helpers.url(repository)/wiki/@if(page.isEmpty){_new} else {_edit}" method="POST" validate="true" autocomplete="off">
|
||||
<span id="error-pageName" class="error"></span>
|
||||
<input type="text" name="pageName" value="@pageName" class="form-control" style="font-weight: bold; margin-bottom: 10px;" placeholder="Input a page name." aria-label="Page name"/>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<h1 class="wiki-title">
|
||||
<h1 class="body-title">
|
||||
@if(pageName.isEmpty){
|
||||
<span class="muted">History</span>
|
||||
} else {
|
||||
|
||||
@@ -4,20 +4,17 @@
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.html.main(s"Pages - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@gitbucket.core.html.menu("wiki", repository){
|
||||
<ul class="nav nav-tabs fill-width">
|
||||
<li>
|
||||
<h1 class="wiki-title"><span class="muted">Pages</span></h1>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
@if(isEditable){
|
||||
<a class="btn btn-default" href="@helpers.url(repository)/wiki/_new">New Page</a>
|
||||
}
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="pull-left">
|
||||
<div class="pull-right">
|
||||
@if(isEditable){
|
||||
<a class="btn btn-default" href="@helpers.url(repository)/wiki/_new">New Page</a>
|
||||
}
|
||||
</div>
|
||||
<h1 class="body-title"><span class="muted">Pages</span></h1>
|
||||
<hr>
|
||||
<ul>
|
||||
@pages.map { page =>
|
||||
<li><a href="@helpers.url(repository)/wiki/@helpers.urlEncode(page)">@page</a></li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
29
src/test/resources/logback-test.xml
Normal file
29
src/test/resources/logback-test.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!--
|
||||
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
||||
<file>gitbucket.log</file>
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
-->
|
||||
|
||||
<logger name="gitbucket" level="DEBUG"/>
|
||||
<root level="WARN">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
|
||||
<!--
|
||||
<logger name="service.WebHookService" level="DEBUG" />
|
||||
<logger name="servlet" level="DEBUG" />
|
||||
<logger name="scala.slick.jdbc.JdbcBackend.statement" level="DEBUG" />
|
||||
-->
|
||||
|
||||
</configuration>
|
||||
@@ -52,6 +52,8 @@ class TestingGitBucketServer(val port: Int = 19999) extends AutoCloseable {
|
||||
def client(login: String, password: String): GitHub =
|
||||
GitHub.connectToEnterprise(s"http://localhost:${port}/api/v3", login, password)
|
||||
|
||||
def getDirectory(): File = dir
|
||||
|
||||
private def 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
|
||||
val statisticsHandler = new StatisticsHandler
|
||||
|
||||
@@ -2,10 +2,13 @@ package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.TestingGitBucketServer
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
|
||||
import scala.util.Using
|
||||
import org.kohsuke.github.{GHCommitState, GitHub}
|
||||
import org.kohsuke.github.GHCommitState
|
||||
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Need to run `sbt package` before running this test.
|
||||
@@ -134,6 +137,27 @@ class ApiIntegrationTest extends AnyFunSuite {
|
||||
assert(statusList.get(1).getState == GHCommitState.FAILURE)
|
||||
assert(statusList.get(1).getContext == "context")
|
||||
}
|
||||
|
||||
// get master ref
|
||||
{
|
||||
val ref = repo.getRef("heads/master")
|
||||
assert(ref.getRef == "refs/heads/master")
|
||||
assert(
|
||||
ref.getUrl.toString == "http://localhost:19999/api/v3/repos/root/create_status_test/git/refs/heads/master"
|
||||
)
|
||||
assert(ref.getObject.getType == "commit")
|
||||
}
|
||||
|
||||
// get tag v1.0
|
||||
{
|
||||
Using.resource(Git.open(new File(server.getDirectory(), "repositories/root/create_status_test"))) { git =>
|
||||
git.tag().setName("v1.0").call()
|
||||
}
|
||||
val ref = repo.getRef("tags/v1.0")
|
||||
assert(ref.getRef == "refs/tags/v1.0")
|
||||
assert(ref.getUrl.toString == "http://localhost:19999/api/v3/repos/root/create_status_test/git/refs/tags/v1.0")
|
||||
assert(ref.getObject.getType == "tag")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +174,7 @@ class ApiIntegrationTest extends AnyFunSuite {
|
||||
.content("create")
|
||||
.message("Create content")
|
||||
.path("README.md")
|
||||
.commit();
|
||||
.commit()
|
||||
|
||||
assert(createResult.getContent.isFile == true)
|
||||
assert(IOUtils.toString(createResult.getContent.read(), "UTF-8") == "create")
|
||||
@@ -168,7 +192,7 @@ class ApiIntegrationTest extends AnyFunSuite {
|
||||
.message("Update content")
|
||||
.path("README.md")
|
||||
.sha(content1.getSha)
|
||||
.commit();
|
||||
.commit()
|
||||
|
||||
assert(updateResult.getContent.isFile == true)
|
||||
assert(IOUtils.toString(updateResult.getContent.read(), "UTF-8") == "update")
|
||||
|
||||
@@ -83,8 +83,20 @@ object ApiSpecModels {
|
||||
milestoneCount = 1,
|
||||
branchList = Seq("master", "develop"),
|
||||
tags = Seq(
|
||||
TagInfo(name = "v1.0", time = date("2015-05-05T23:40:27Z"), id = "id1", message = "1.0 released"),
|
||||
TagInfo(name = "v2.0", time = date("2016-05-05T23:40:27Z"), id = "id2", message = "2.0 released")
|
||||
TagInfo(
|
||||
name = "v1.0",
|
||||
time = date("2015-05-05T23:40:27Z"),
|
||||
commitId = "id1",
|
||||
message = "1.0 released",
|
||||
objectId = "id1"
|
||||
),
|
||||
TagInfo(
|
||||
name = "v2.0",
|
||||
time = date("2016-05-05T23:40:27Z"),
|
||||
commitId = "id2",
|
||||
message = "2.0 released",
|
||||
objectId = "id2"
|
||||
)
|
||||
),
|
||||
managers = Seq("myboss")
|
||||
)
|
||||
@@ -177,6 +189,16 @@ object ApiSpecModels {
|
||||
updatedDate = date1
|
||||
)
|
||||
|
||||
val milestone = Milestone(
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
milestoneId = 1,
|
||||
title = "Test milestone",
|
||||
description = Some("Milestone description"),
|
||||
dueDate = Some(date1),
|
||||
closedDate = Some(date1)
|
||||
)
|
||||
|
||||
// APIs
|
||||
|
||||
val apiUser = ApiUser(account)
|
||||
@@ -193,12 +215,20 @@ object ApiSpecModels {
|
||||
repositoryName = repo1Name
|
||||
)
|
||||
|
||||
val apiMilestone = ApiMilestone(
|
||||
repository = repository,
|
||||
milestone = milestone,
|
||||
open_issue_count = 1,
|
||||
closed_issue_count = 1
|
||||
)
|
||||
|
||||
val apiIssue = ApiIssue(
|
||||
issue = issue,
|
||||
repositoryName = repo1Name,
|
||||
user = apiUser,
|
||||
assignee = Some(apiUser),
|
||||
labels = List(apiLabel)
|
||||
labels = List(apiLabel),
|
||||
milestone = Some(apiMilestone)
|
||||
)
|
||||
|
||||
val apiNotAssignedIssue = ApiIssue(
|
||||
@@ -206,7 +236,8 @@ object ApiSpecModels {
|
||||
repositoryName = repo1Name,
|
||||
user = apiUser,
|
||||
assignee = None,
|
||||
labels = List(apiLabel)
|
||||
labels = List(apiLabel),
|
||||
milestone = Some(apiMilestone)
|
||||
)
|
||||
|
||||
val apiIssuePR = ApiIssue(
|
||||
@@ -214,7 +245,8 @@ object ApiSpecModels {
|
||||
repositoryName = repo1Name,
|
||||
user = apiUser,
|
||||
assignee = Some(apiUser),
|
||||
labels = List(apiLabel)
|
||||
labels = List(apiLabel),
|
||||
milestone = Some(apiMilestone)
|
||||
)
|
||||
|
||||
val apiComment = ApiComment(
|
||||
@@ -412,9 +444,29 @@ object ApiSpecModels {
|
||||
|
||||
val apiPusher = ApiPusher(account)
|
||||
|
||||
val apiRef = ApiRef(
|
||||
ref = "refs/heads/featureA",
|
||||
`object` = ApiObject(sha1)
|
||||
//have both urls as https, as the expected samples are using https
|
||||
val gitHubContext = JsonFormat.Context("https://api.github.com", Some("https://api.github.com"))
|
||||
|
||||
val apiRefHeadsMaster = ApiRef(
|
||||
ref = "refs/heads/master",
|
||||
url = ApiPath("/repos/gitbucket/gitbucket/git/refs/heads/master"),
|
||||
node_id = "MDM6UmVmOTM1MDc0NjpyZWZzL2hlYWRzL21hc3Rlcg==",
|
||||
`object` = ApiRefCommit(
|
||||
sha = "6b2d124d092402f2c2b7131caada05ead9e7de6d",
|
||||
`type` = "commit",
|
||||
url = ApiPath("/repos/gitbucket/gitbucket/git/commits/6b2d124d092402f2c2b7131caada05ead9e7de6d")
|
||||
)
|
||||
)
|
||||
|
||||
val apiRefTag = ApiRef(
|
||||
ref = "refs/tags/1.0",
|
||||
url = ApiPath("/repos/gitbucket/gitbucket/git/refs/tags/1.0"),
|
||||
node_id = "MDM6UmVmOTM1MDc0NjpyZWZzL3RhZ3MvMS4w",
|
||||
`object` = ApiRefCommit(
|
||||
sha = "1f164ecf2f59190afc8d7204a221c739e707df4c",
|
||||
`type` = "tag",
|
||||
url = ApiPath("/repos/gitbucket/gitbucket/git/tags/1f164ecf2f59190afc8d7204a221c739e707df4c")
|
||||
)
|
||||
)
|
||||
|
||||
val assetFileName = "010203040a0b0c0d"
|
||||
@@ -471,6 +523,19 @@ object ApiSpecModels {
|
||||
val jsonLabel =
|
||||
"""{"name":"bug","color":"f29513","url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/labels/bug"}"""
|
||||
|
||||
val jsonMilestone = """{
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/milestones/1",
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/milestone/1",
|
||||
|"id":1,
|
||||
|"number":1,
|
||||
|"state":"closed",
|
||||
|"title":"Test milestone",
|
||||
|"description":"Milestone description",
|
||||
|"open_issues":1,"closed_issues":1,
|
||||
|"closed_at":"2011-04-14T16:00:49Z",
|
||||
|"due_on":"2011-04-14T16:00:49Z"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonIssue = s"""{
|
||||
|"number":1347,
|
||||
|"title":"Found a bug",
|
||||
@@ -481,6 +546,7 @@ object ApiSpecModels {
|
||||
|"created_at":"2011-04-14T16:00:49Z",
|
||||
|"updated_at":"2011-04-14T16:00:49Z",
|
||||
|"body":"I'm having a problem with this.",
|
||||
|"milestone":$jsonMilestone,
|
||||
|"id":0,
|
||||
|"assignees":[$jsonUser],
|
||||
|"comments_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/issues/1347/comments",
|
||||
@@ -496,6 +562,7 @@ object ApiSpecModels {
|
||||
|"created_at":"2011-04-14T16:00:49Z",
|
||||
|"updated_at":"2011-04-14T16:00:49Z",
|
||||
|"body":"I'm having a problem with this.",
|
||||
|"milestone":$jsonMilestone,
|
||||
|"id":0,
|
||||
|"assignees":[],
|
||||
|"comments_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/issues/1347/comments",
|
||||
@@ -512,6 +579,7 @@ object ApiSpecModels {
|
||||
|"created_at":"2011-04-14T16:00:49Z",
|
||||
|"updated_at":"2011-04-14T16:00:49Z",
|
||||
|"body":"Please pull these awesome changes",
|
||||
|"milestone":$jsonMilestone,
|
||||
|"id":0,
|
||||
|"assignees":[$jsonUser],
|
||||
|"comments_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/issues/1347/comments",
|
||||
@@ -729,8 +797,33 @@ object ApiSpecModels {
|
||||
|
||||
val jsonPusher = """{"name":"octocat","email":"octocat@example.com"}"""
|
||||
|
||||
//I checked all refs in gitbucket repo, and there appears to be only type "commit" and type "tag"
|
||||
val jsonRef = """{"ref":"refs/heads/featureA","object":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
|
||||
|
||||
val jsonRefHeadsMaster =
|
||||
"""{
|
||||
|"ref": "refs/heads/master",
|
||||
|"node_id": "MDM6UmVmOTM1MDc0NjpyZWZzL2hlYWRzL21hc3Rlcg==",
|
||||
|"url": "https://api.github.com/repos/gitbucket/gitbucket/git/refs/heads/master",
|
||||
|"object": {
|
||||
|"sha": "6b2d124d092402f2c2b7131caada05ead9e7de6d",
|
||||
|"type": "commit",
|
||||
|"url": "https://api.github.com/repos/gitbucket/gitbucket/git/commits/6b2d124d092402f2c2b7131caada05ead9e7de6d"
|
||||
|}
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonRefTag =
|
||||
"""{
|
||||
|"ref": "refs/tags/1.0",
|
||||
|"node_id": "MDM6UmVmOTM1MDc0NjpyZWZzL3RhZ3MvMS4w",
|
||||
|"url": "https://api.github.com/repos/gitbucket/gitbucket/git/refs/tags/1.0",
|
||||
|"object": {
|
||||
|"sha": "1f164ecf2f59190afc8d7204a221c739e707df4c",
|
||||
|"type": "tag",
|
||||
|"url": "https://api.github.com/repos/gitbucket/gitbucket/git/tags/1f164ecf2f59190afc8d7204a221c739e707df4c"
|
||||
|}
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonReleaseAsset =
|
||||
s"""{
|
||||
|"name":"release.zip",
|
||||
|
||||
@@ -8,6 +8,12 @@ class JsonFormatSpec extends AnyFunSuite {
|
||||
implicit val format = JsonFormat.jsonFormats
|
||||
|
||||
private def expected(json: String) = json.replaceAll("\n", "")
|
||||
def normalizeJson(json: String) = {
|
||||
org.json4s.jackson.parseJson(json)
|
||||
}
|
||||
def assertEqualJson(actual: String, expected: String) = {
|
||||
assert(normalizeJson(actual) == normalizeJson(expected))
|
||||
}
|
||||
|
||||
test("apiUser") {
|
||||
assert(JsonFormat(apiUser) == expected(jsonUser))
|
||||
@@ -76,8 +82,11 @@ class JsonFormatSpec extends AnyFunSuite {
|
||||
test("apiPusher") {
|
||||
assert(JsonFormat(apiPusher) == expected(jsonPusher))
|
||||
}
|
||||
test("apiRef") {
|
||||
assert(JsonFormat(apiRef) == expected(jsonRef))
|
||||
test("apiRefHead") {
|
||||
assertEqualJson(JsonFormat(apiRefHeadsMaster)(gitHubContext), jsonRefHeadsMaster)
|
||||
}
|
||||
test("apiRefTag") {
|
||||
assertEqualJson(JsonFormat(apiRefTag)(gitHubContext), jsonRefTag)
|
||||
}
|
||||
test("apiReleaseAsset") {
|
||||
assert(JsonFormat(apiReleaseAsset) == expected(jsonReleaseAsset))
|
||||
|
||||
@@ -47,8 +47,8 @@ trait ServiceSpecBase {
|
||||
limitVisibleRepositories = false,
|
||||
ssh = Ssh(
|
||||
enabled = false,
|
||||
sshHost = None,
|
||||
sshPort = None
|
||||
bindAddress = None,
|
||||
publicAddress = None
|
||||
),
|
||||
useSMTP = false,
|
||||
smtp = None,
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.service.SystemSettingsService.SshAddress
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.wordspec.AnyWordSpecLike
|
||||
|
||||
import java.util.Properties
|
||||
|
||||
class SystemSettingsServiceSpec extends AnyWordSpecLike with Matchers {
|
||||
|
||||
"loadSystemSettings" should {
|
||||
"read old-style ssh configuration" in new SystemSettingsService {
|
||||
val props = new Properties()
|
||||
props.setProperty("ssh", "true")
|
||||
props.setProperty("ssh.host", "127.0.0.1")
|
||||
props.setProperty("ssh.port", "8022")
|
||||
|
||||
val settings = loadSystemSettings(props)
|
||||
settings.ssh.enabled shouldBe true
|
||||
settings.ssh.bindAddress shouldBe Some(SshAddress("127.0.0.1", 8022, "git"))
|
||||
settings.ssh.publicAddress shouldBe settings.ssh.bindAddress
|
||||
}
|
||||
"read new-style ssh configuration" in new SystemSettingsService {
|
||||
val props = new Properties()
|
||||
props.setProperty("ssh", "true")
|
||||
props.setProperty("ssh.bindAddress.host", "127.0.0.1")
|
||||
props.setProperty("ssh.bindAddress.port", "8022")
|
||||
props.setProperty("ssh.publicAddress.host", "code.these.solutions")
|
||||
props.setProperty("ssh.publicAddress.port", "22")
|
||||
|
||||
val settings = loadSystemSettings(props)
|
||||
settings.ssh.enabled shouldBe true
|
||||
settings.ssh.bindAddress shouldBe Some(SshAddress("127.0.0.1", 8022, "git"))
|
||||
settings.ssh.publicAddress shouldBe Some(SshAddress("code.these.solutions", 22, "git"))
|
||||
}
|
||||
"default the ssh port if not specified" in new SystemSettingsService {
|
||||
val props = new Properties()
|
||||
props.setProperty("ssh", "true")
|
||||
props.setProperty("ssh.bindAddress.host", "127.0.0.1")
|
||||
props.setProperty("ssh.publicAddress.host", "code.these.solutions")
|
||||
|
||||
val settings = loadSystemSettings(props)
|
||||
settings.ssh.enabled shouldBe true
|
||||
settings.ssh.bindAddress shouldBe Some(SshAddress("127.0.0.1", 29418, "git"))
|
||||
settings.ssh.publicAddress shouldBe Some(SshAddress("code.these.solutions", 22, "git"))
|
||||
}
|
||||
"default the public address if not specified" in new SystemSettingsService {
|
||||
val props = new Properties()
|
||||
props.setProperty("ssh", "true")
|
||||
props.setProperty("ssh.bindAddress.host", "127.0.0.1")
|
||||
props.setProperty("ssh.bindAddress.port", "8022")
|
||||
|
||||
val settings = loadSystemSettings(props)
|
||||
settings.ssh.enabled shouldBe true
|
||||
settings.ssh.bindAddress shouldBe Some(SshAddress("127.0.0.1", 8022, "git"))
|
||||
settings.ssh.publicAddress shouldBe settings.ssh.bindAddress
|
||||
}
|
||||
"return addresses even if ssh is not enabled" in new SystemSettingsService {
|
||||
val props = new Properties()
|
||||
props.setProperty("ssh", "false")
|
||||
props.setProperty("ssh.bindAddress.host", "127.0.0.1")
|
||||
props.setProperty("ssh.bindAddress.port", "8022")
|
||||
props.setProperty("ssh.publicAddress.host", "code.these.solutions")
|
||||
props.setProperty("ssh.publicAddress.port", "22")
|
||||
|
||||
val settings = loadSystemSettings(props)
|
||||
settings.ssh.enabled shouldBe false
|
||||
settings.ssh.bindAddress shouldNot be(empty)
|
||||
settings.ssh.publicAddress shouldNot be(empty)
|
||||
}
|
||||
}
|
||||
|
||||
"SshAddress" can {
|
||||
trait MockContext {
|
||||
val host = "code.these.solutions"
|
||||
val port = 1337
|
||||
val user = "git"
|
||||
lazy val sshAddress = SshAddress(host, port, user)
|
||||
}
|
||||
"isDefaultPort" which {
|
||||
"returns true if using port 22" in new MockContext {
|
||||
override val port = 22
|
||||
sshAddress.isDefaultPort shouldBe true
|
||||
}
|
||||
"returns false if using a different port" in new MockContext {
|
||||
override val port = 8022
|
||||
sshAddress.isDefaultPort shouldBe false
|
||||
}
|
||||
}
|
||||
"getUrl" which {
|
||||
"returns the port number when not using port 22" in new MockContext {
|
||||
override val port = 8022
|
||||
sshAddress.getUrl shouldBe "git@code.these.solutions:8022"
|
||||
}
|
||||
"leaves off the port number when using port 22" in new MockContext {
|
||||
override val port = 22
|
||||
sshAddress.getUrl shouldBe "git@code.these.solutions"
|
||||
}
|
||||
}
|
||||
"getUrl for owner and repo" which {
|
||||
"returns an ssh-protocol url when not using port 22" in new MockContext {
|
||||
override val port = 8022
|
||||
sshAddress.getUrl("np-hard", "quantum-crypto-cracker") shouldBe
|
||||
"ssh://git@code.these.solutions:8022/np-hard/quantum-crypto-cracker.git"
|
||||
}
|
||||
"returns a bare-protocol url when using port 22" in new MockContext {
|
||||
override val port = 22
|
||||
sshAddress.getUrl("syntactic", "brace-stretcher") shouldBe
|
||||
"git@code.these.solutions:syntactic/brace-stretcher.git"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,7 +121,8 @@ class WebHookJsonFormatSpec extends AnyFunSuite {
|
||||
repositoryUser = account,
|
||||
assignedUser = Some(account),
|
||||
sender = account,
|
||||
labels = List(label)
|
||||
labels = List(label),
|
||||
milestone = Some(apiMilestone)
|
||||
)
|
||||
val expected = s"""{
|
||||
|"action":"created",
|
||||
|
||||
@@ -1,38 +1,102 @@
|
||||
package gitbucket.core.ssh
|
||||
|
||||
import gitbucket.core.service.SystemSettingsService.SshAddress
|
||||
import org.apache.sshd.server.channel.ChannelSession
|
||||
import org.apache.sshd.server.shell.UnknownCommand
|
||||
import org.scalatest.funspec.AnyFunSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.wordspec.AnyWordSpec
|
||||
|
||||
class GitCommandFactorySpec extends AnyFunSpec {
|
||||
class GitCommandFactorySpec extends AnyWordSpec with Matchers {
|
||||
|
||||
val factory = new GitCommandFactory("http://localhost:8080", None)
|
||||
|
||||
describe("createCommand") {
|
||||
it("should return GitReceivePack when command is git-receive-pack") {
|
||||
assert(factory.createCommand("git-receive-pack '/owner/repo.git'").isInstanceOf[DefaultGitReceivePack] == true)
|
||||
assert(
|
||||
factory.createCommand("git-receive-pack '/owner/repo.wiki.git'").isInstanceOf[DefaultGitReceivePack] == true
|
||||
)
|
||||
}
|
||||
it("should return GitUploadPack when command is git-upload-pack") {
|
||||
assert(factory.createCommand("git-upload-pack '/owner/repo.git'").isInstanceOf[DefaultGitUploadPack] == true)
|
||||
assert(factory.createCommand("git-upload-pack '/owner/repo.wiki.git'").isInstanceOf[DefaultGitUploadPack] == true)
|
||||
}
|
||||
it("should return UnknownCommand when command is not git-(upload|receive)-pack") {
|
||||
assert(factory.createCommand("git- '/owner/repo.git'").isInstanceOf[UnknownCommand] == true)
|
||||
assert(factory.createCommand("git-pack '/owner/repo.git'").isInstanceOf[UnknownCommand] == true)
|
||||
assert(factory.createCommand("git-a-pack '/owner/repo.git'").isInstanceOf[UnknownCommand] == true)
|
||||
assert(factory.createCommand("git-up-pack '/owner/repo.git'").isInstanceOf[UnknownCommand] == true)
|
||||
assert(factory.createCommand("\ngit-upload-pack '/owner/repo.git'").isInstanceOf[UnknownCommand] == true)
|
||||
}
|
||||
it("should return UnknownCommand when git command has no valid arguments") {
|
||||
// must be: git-upload-pack '/owner/repository_name.git'
|
||||
assert(factory.createCommand("git-upload-pack").isInstanceOf[UnknownCommand] == true)
|
||||
assert(factory.createCommand("git-upload-pack /owner/repo.git").isInstanceOf[UnknownCommand] == true)
|
||||
assert(factory.createCommand("git-upload-pack 'owner/repo.git'").isInstanceOf[UnknownCommand] == true)
|
||||
assert(factory.createCommand("git-upload-pack '/ownerrepo.git'").isInstanceOf[UnknownCommand] == true)
|
||||
assert(factory.createCommand("git-upload-pack '/owner/repo.wiki'").isInstanceOf[UnknownCommand] == true)
|
||||
}
|
||||
trait MockContext {
|
||||
val baseUrl = "https://some.example.tech:8080/code-context"
|
||||
val sshHost = "localhost"
|
||||
val sshPort = 2222
|
||||
lazy val factory = new GitCommandFactory(baseUrl, SshAddress(sshHost, sshPort, "git"))
|
||||
}
|
||||
|
||||
"createCommand" when {
|
||||
val channel = new ChannelSession()
|
||||
"receiving a git-receive-pack command" should {
|
||||
"return DefaultGitReceivePack" when {
|
||||
"the path matches owner/repo" in new MockContext {
|
||||
assert(
|
||||
factory.createCommand(channel, "git-receive-pack '/owner/repo.git'").isInstanceOf[DefaultGitReceivePack]
|
||||
)
|
||||
assert(
|
||||
factory
|
||||
.createCommand(channel, "git-receive-pack '/owner/repo.wiki.git'")
|
||||
.isInstanceOf[DefaultGitReceivePack]
|
||||
)
|
||||
}
|
||||
"the leading slash is left off and running on port 22" in new MockContext {
|
||||
override val sshPort: Int = 22
|
||||
assert(
|
||||
factory.createCommand(channel, "git-receive-pack 'owner/repo.git'").isInstanceOf[DefaultGitReceivePack]
|
||||
)
|
||||
assert(
|
||||
factory.createCommand(channel, "git-receive-pack 'owner/repo.wiki.git'").isInstanceOf[DefaultGitReceivePack]
|
||||
)
|
||||
}
|
||||
}
|
||||
"return UnknownCommand" when {
|
||||
"the ssh port is not 22 and the leading slash is missing" in new MockContext {
|
||||
override val sshPort: Int = 1337
|
||||
assert(factory.createCommand(channel, "git-receive-pack 'owner/repo.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand(channel, "git-receive-pack 'owner/repo.wiki.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand(channel, "git-receive-pack 'oranges.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand(channel, "git-receive-pack 'apples.git'").isInstanceOf[UnknownCommand])
|
||||
}
|
||||
"the path is malformed" in new MockContext {
|
||||
assert(
|
||||
factory.createCommand(channel, "git-receive-pack '/owner/repo/wrong.git'").isInstanceOf[UnknownCommand]
|
||||
)
|
||||
assert(factory.createCommand(channel, "git-receive-pack '/owner:repo.wiki.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand(channel, "git-receive-pack '/oranges'").isInstanceOf[UnknownCommand])
|
||||
}
|
||||
}
|
||||
}
|
||||
"receiving a git-upload-pack command" should {
|
||||
"return DefaultGitUploadPack" when {
|
||||
"the path matches owner/repo" in new MockContext {
|
||||
assert(factory.createCommand(channel, "git-upload-pack '/owner/repo.git'").isInstanceOf[DefaultGitUploadPack])
|
||||
assert(
|
||||
factory.createCommand(channel, "git-upload-pack '/owner/repo.wiki.git'").isInstanceOf[DefaultGitUploadPack]
|
||||
)
|
||||
}
|
||||
"the leading slash is left off and running on port 22" in new MockContext {
|
||||
override val sshPort = 22
|
||||
assert(factory.createCommand(channel, "git-upload-pack 'owner/repo.git'").isInstanceOf[DefaultGitUploadPack])
|
||||
assert(
|
||||
factory.createCommand(channel, "git-upload-pack 'owner/repo.wiki.git'").isInstanceOf[DefaultGitUploadPack]
|
||||
)
|
||||
}
|
||||
}
|
||||
"return UnknownCommand" when {
|
||||
"the ssh port is not 22 and the leading slash is missing" in new MockContext {
|
||||
override val sshPort = 1337
|
||||
assert(factory.createCommand(channel, "git-upload-pack 'owner/repo.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand(channel, "git-upload-pack 'owner/repo.wiki.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand(channel, "git-upload-pack 'oranges.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand(channel, "git-upload-pack 'apples.git'").isInstanceOf[UnknownCommand])
|
||||
}
|
||||
"the path is malformed" in new MockContext {
|
||||
assert(factory.createCommand(channel, "git-upload-pack '/owner/repo'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand(channel, "git-upload-pack '/owner:repo.wiki.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand(channel, "git-upload-pack '/oranges'").isInstanceOf[UnknownCommand])
|
||||
}
|
||||
}
|
||||
}
|
||||
"receiving any command not matching git-(receive|upload)-pack" should {
|
||||
"return UnknownCommand" in new MockContext {
|
||||
assert(factory.createCommand(channel, "git-destroy-pack '/owner/repo.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand(channel, "git-irrigate-pack '/apples.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand(channel, "git-force-push '/stolen/nuke.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand(channel, "git-delete '/backups.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand(channel, "git-pack '/your/bags.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand(channel, "git- '/bananas.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand(channel, "99 tickets of bugs on the wall").isInstanceOf[UnknownCommand])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,8 @@ class AvatarImageProviderSpec extends AnyFunSpec {
|
||||
|
||||
assert(
|
||||
provider.toHtml("user", 32).toString ==
|
||||
"""<img src="https://www.gravatar.com/avatar/d41d8cd98f00b204e9800998ecf8427e?s=32&d=retro&r=g" class="avatar" style="width: 32px; height: 32px;" alt="@user" />"""
|
||||
"""<img src="https://www.gravatar.com/avatar/d41d8cd98f00b204e9800998ecf8427e?s=32&d=retro&r=g" class="avatar" style="width: 32px; height: 32px;"
|
||||
| alt="@user" />""".stripMargin
|
||||
)
|
||||
}
|
||||
|
||||
@@ -47,7 +48,8 @@ class AvatarImageProviderSpec extends AnyFunSpec {
|
||||
|
||||
assert(
|
||||
provider.toHtml("user", 32).toString ==
|
||||
s"""<img src="/user/_avatar?${date}" class="avatar" style="width: 32px; height: 32px;" alt="@user" />"""
|
||||
s"""<img src="/user/_avatar?${date}" class="avatar" style="width: 32px; height: 32px;"
|
||||
| alt="@user" />""".stripMargin
|
||||
)
|
||||
}
|
||||
|
||||
@@ -59,7 +61,8 @@ class AvatarImageProviderSpec extends AnyFunSpec {
|
||||
|
||||
assert(
|
||||
provider.toHtml("user", 32).toString ==
|
||||
s"""<img src="/user/_avatar?${date}" class="avatar" style="width: 32px; height: 32px;" alt="@user" />"""
|
||||
s"""<img src="/user/_avatar?${date}" class="avatar" style="width: 32px; height: 32px;"
|
||||
| alt="@user" />""".stripMargin
|
||||
)
|
||||
}
|
||||
|
||||
@@ -69,7 +72,8 @@ class AvatarImageProviderSpec extends AnyFunSpec {
|
||||
|
||||
assert(
|
||||
provider.toHtml("user", 20, "hoge@hoge.com").toString ==
|
||||
"""<img src="https://www.gravatar.com/avatar/4712f9b0e63f56ad952ad387eaa23b9c?s=20&d=retro&r=g" class="avatar-mini" style="width: 20px; height: 20px;" alt="@user" />"""
|
||||
"""<img src="https://www.gravatar.com/avatar/4712f9b0e63f56ad952ad387eaa23b9c?s=20&d=retro&r=g" class="avatar-mini" style="width: 20px; height: 20px;"
|
||||
| alt="@user" />""".stripMargin
|
||||
)
|
||||
}
|
||||
|
||||
@@ -79,7 +83,8 @@ class AvatarImageProviderSpec extends AnyFunSpec {
|
||||
|
||||
assert(
|
||||
provider.toHtml("user", 20).toString ==
|
||||
"""<img src="/_unknown/_avatar" class="avatar-mini" style="width: 20px; height: 20px;" alt="@user" />"""
|
||||
"""<img src="/_unknown/_avatar" class="avatar-mini" style="width: 20px; height: 20px;"
|
||||
| alt="@user" />""".stripMargin
|
||||
)
|
||||
}
|
||||
|
||||
@@ -89,7 +94,8 @@ class AvatarImageProviderSpec extends AnyFunSpec {
|
||||
|
||||
assert(
|
||||
provider.toHtml("user", 20, "hoge@hoge.com").toString ==
|
||||
"""<img src="/_unknown/_avatar" class="avatar-mini" style="width: 20px; height: 20px;" alt="@user" />"""
|
||||
"""<img src="/_unknown/_avatar" class="avatar-mini" style="width: 20px; height: 20px;"
|
||||
| alt="@user" />""".stripMargin
|
||||
)
|
||||
}
|
||||
|
||||
@@ -99,7 +105,27 @@ class AvatarImageProviderSpec extends AnyFunSpec {
|
||||
|
||||
assert(
|
||||
provider.toHtml("user", 20, "hoge@hoge.com", true).toString ==
|
||||
"""<img src="/_unknown/_avatar" class="avatar-mini" style="width: 20px; height: 20px;" data-toggle="tooltip" title="user" alt="@user" />"""
|
||||
"""<img src="/_unknown/_avatar" class="avatar-mini" style="width: 20px; height: 20px;"
|
||||
| alt="@user"
|
||||
| data-toggle="tooltip" title="user" />""".stripMargin
|
||||
)
|
||||
}
|
||||
|
||||
it("should escape user name") {
|
||||
implicit val context = Context(createSystemSettings(false), None, request)
|
||||
val provider = new AvatarImageProviderImpl(None)
|
||||
|
||||
assert(
|
||||
provider.toHtml("""<user>"<name>""", 20, "hoge@hoge.com").toString ==
|
||||
"""<img src="/_unknown/_avatar" class="avatar-mini" style="width: 20px; height: 20px;"
|
||||
| alt="@<user>"<name>" />""".stripMargin
|
||||
)
|
||||
|
||||
assert(
|
||||
provider.toHtml("""<user>"<name>""", 20, "hoge@hoge.com", true).toString ==
|
||||
"""<img src="/_unknown/_avatar" class="avatar-mini" style="width: 20px; height: 20px;"
|
||||
| alt="@<user>"<name>"
|
||||
| data-toggle="tooltip" title="<user>"<name>" />""".stripMargin
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -140,8 +166,8 @@ class AvatarImageProviderSpec extends AnyFunSpec {
|
||||
limitVisibleRepositories = false,
|
||||
ssh = Ssh(
|
||||
enabled = false,
|
||||
sshHost = None,
|
||||
sshPort = None
|
||||
bindAddress = None,
|
||||
publicAddress = None
|
||||
),
|
||||
useSMTP = false,
|
||||
smtp = None,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package gitbucket.core.view
|
||||
|
||||
import gitbucket.core.util.SyntaxSugars
|
||||
import SyntaxSugars._
|
||||
import org.scalatest.funspec.AnyFunSpec
|
||||
|
||||
class PaginationSpec extends AnyFunSpec {
|
||||
|
||||
Reference in New Issue
Block a user