Compare commits

..

72 Commits

Author SHA1 Message Date
Naoki Takezoe
ff8a5f6b77 Release 4.37.0 (#2940) 2021-12-11 14:28:23 +09:00
Scala Steward
ec953df156 Update sbt, sbt-dependency-tree to 1.5.6 2021-12-10 22:27:40 +09:00
Naoki Takezoe
d6a191d95b Enhance Git Reference APIs (#2937) 2021-12-06 17:16:33 +09:00
Naoki Takezoe
aba428bba1 Fix refs API as far as Jenkins github-branch-source plugin can detect tags (#2936)
Co-authored-by: Thomas Geier <thomas.geier@solidat.de>
2021-12-06 01:06:59 +09:00
Scala Steward
6ab37fd596 Update thumbnailator to 0.4.15 2021-12-05 16:11:17 +09:00
Scala Steward
73fc70f55b Update apache-sshd to 2.8.0 2021-12-04 08:27:01 +09:00
Scala Steward
aad18b7a50 Update sbt-scalafmt to 2.4.5 2021-12-04 08:26:37 +09:00
dependabot[bot]
cc278be5cd Bump actions/cache from 2.1.6 to 2.1.7
Bumps [actions/cache](https://github.com/actions/cache) from 2.1.6 to 2.1.7.
- [Release notes](https://github.com/actions/cache/releases)
- [Commits](https://github.com/actions/cache/compare/v2.1.6...v2.1.7)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-30 09:24:19 +09:00
kenji yoshida
d0f4f82a0f pin jgit 5.x 2021-11-30 09:23:36 +09:00
Naoki Takezoe
1dcbf386b1 Fix SSH server handling (#2930) 2021-11-28 17:39:31 +09:00
Naoki Takezoe
414afd285c Remove unused imports 2021-11-28 15:14:51 +09:00
Naoki Takezoe
35b645d8b5 Merge branch 'keywordsalad-custom-ssh-url' 2021-11-28 15:08:04 +09:00
Naoki Takezoe
b3cba53866 Reformat GitCommandSpec 2021-11-28 15:02:29 +09:00
Naoki Takezoe
a4773bb3ca Merge branch 'master' into custom-ssh-url 2021-11-28 14:56:55 +09:00
Naoki Takezoe
863d8a4af5 Bump apache-sshd to 2.7.0 (#2929) 2021-11-28 14:47:58 +09:00
Scala Steward
3fccd7b53c Update oauth2-oidc-sdk to 9.20 2021-11-25 20:46:24 +09:00
Scala Steward
dd2760eaf7 Update github-api to 1.301 2021-11-24 12:10:25 +09:00
Scala Steward
824bafa739 Update github-api to 1.300 2021-11-22 11:15:11 +09:00
kaz-on
60cdaec05f Fix line highlighting in dark themes (#2921) 2021-11-22 01:31:52 +09:00
kaz-on
c204a435b3 Remove unnecessary loading of google-code-prettify (#2922) 2021-11-22 01:31:16 +09:00
Scala Steward
37accd92d6 Update mockito-core to 4.1.0 2021-11-20 07:35:42 +09:00
Scala Steward
01fd0ee1f0 Update sbt-scalafmt to 2.4.4 2021-11-19 07:19:22 +09:00
Scala Steward
fab1c74473 Update testcontainers-scala to 0.39.12 2021-11-15 06:14:54 +09:00
Scala Steward
0d8fcfd28d Update logback-classic to 1.2.7 2021-11-12 03:45:11 +09:00
Naoki Takezoe
b91a7c32a6 Relax max length limitation for WebHook URLs (#2915) 2021-11-11 01:39:12 +09:00
Naoki Takezoe
7f665c649b Fix duplicated diff issue in exporting patch feature(#2913) 2021-11-05 19:31:52 +09:00
Scala Steward
dcbadb4071 Update sbt-scoverage to 1.9.2 2021-11-03 02:44:03 +09:00
Mira
e3096d15ff resolve #2907 API - not milestone data in issue list (#2908) 2021-11-03 01:23:18 +09:00
Scala Steward
a83c24e7b3 Update scala-library to 2.13.7 2021-11-02 07:15:21 +09:00
Scala Steward
73457c9658 Update testcontainers-scala to 0.39.11 2021-11-02 06:59:06 +09:00
Naoki Takezoe
cfc8d9f3f1 Relax max password length limitation to 40 (#2906) 2021-10-31 11:30:39 +09:00
Scala Steward
8f423b83ea Update postgresql to 42.3.1 2021-10-30 07:15:00 +09:00
Scala Steward
1e7ac532b6 Update testcontainers-scala to 0.39.10 2021-10-28 12:17:14 +09:00
Scala Steward
0fd1db4596 Update mysql, postgresql to 1.16.2 2021-10-23 06:48:28 +09:00
Scala Steward
0755b7ab7f Update testcontainers-scala to 0.39.9 2021-10-22 07:58:09 +09:00
Scala Steward
052382e5c4 Update github-api to 1.135 2021-10-22 07:22:16 +09:00
Scala Steward
f44a63cec1 Update oauth2-oidc-sdk to 9.19 2021-10-20 22:36:12 +09:00
Scala Steward
603d67354a Update mysql, postgresql to 1.16.1 2021-10-19 22:06:56 +09:00
Scala Steward
aafa423b9f Update postgresql to 42.3.0 2021-10-19 06:59:29 +09:00
Mészáros István
2da9d0a801 Add HTTPS support for JettyLauncher (#2896) 2021-10-14 11:27:26 +09:00
Scala Steward
b60c112a74 Update sbt-scoverage to 1.9.1 2021-10-11 19:04:31 +09:00
Scala Steward
843ed6df37 Update mockito-core to 4.0.0 2021-10-07 23:48:03 +09:00
Scala Steward
0f0a849677 Update jetty-continuation, jetty-http, ... to 9.4.44.v20210927 2021-10-01 12:15:06 +09:00
Scala Steward
682901ccbb Update oauth2-oidc-sdk to 9.18 2021-09-30 17:50:08 +09:00
Naoki Takezoe
048fdb8837 Remove unnecessary workaround for scala-xml version conflict (#2888) 2021-09-27 03:00:15 +09:00
scala-steward-bot
3353616789 Update scalatra, scalatra-forms, ... to 2.8.2 (#2886) 2021-09-25 19:07:58 +09:00
scala-steward-bot
b6cb4c865f Update scalatra, scalatra-forms, ... to 2.8.1 (#2885) 2021-09-25 16:00:15 +09:00
kenji yoshida
1fcfd093f7 add jdk 17 test 2021-09-25 10:51:36 +09:00
Scala Steward
3f27c6e733 Update postgresql to 42.2.24 2021-09-23 21:00:09 +09:00
Scala Steward
b6bd9bfc3b Update oauth2-oidc-sdk to 9.17 2021-09-22 07:27:44 +09:00
Scala Steward
6c392f0056 Update oauth2-oidc-sdk to 9.16 2021-09-21 21:18:30 +09:00
Scala Steward
9a38de9a23 Update testcontainers-scala to 0.39.8 2021-09-20 19:57:18 +09:00
Scala Steward
8883600090 Update sbt-scoverage to 1.9.0 2021-09-17 01:27:52 +09:00
Naoki Takezoe
ab822a3c27 Fix Wiki page editing bug when over 100 pages (#2869) 2021-09-13 00:48:38 +09:00
Scala Steward
0e4d64de23 Update org.eclipse.jgit.archive, ... to 5.13.0.202109080827-r 2021-09-12 23:08:48 +09:00
Scala Steward
fbc6bd36bd Update logback-classic to 1.2.6 2021-09-10 13:09:30 +09:00
Naoki Takezoe
ed90ca2dce Bump gitbucket-pages-plugin to 1.10.0 (#2856) 2021-09-10 09:03:51 +09:00
Scala Steward
537ef92149 Update github-api to 1.133 2021-09-09 15:56:16 +09:00
Scala Steward
d51afa7d40 Update java-diff-utils to 4.11 2021-09-08 21:27:57 +09:00
Scala Steward
975cffff48 Update sbt-assembly to 1.1.0 2021-09-01 23:08:42 +09:00
Scala Steward
d92e9c00e8 Update testcontainers-scala to 0.39.7 2021-08-31 14:52:37 +09:00
KN4CK3R
12d72cbb19 Add support for "all" in issue list API (#2859) 2021-08-31 00:38:38 +09:00
Logan McGrath
e7a6f0930b Closes #2818 - Supporting custom SSH URL's when hosting behind a proxy 2021-08-29 16:38:06 -07:00
Scala Steward
d8e03bed1f Update mockito-core to 3.12.4 2021-08-26 08:43:19 +09:00
Scala Steward
f48c087cd8 Update mockito-core to 3.12.3 2021-08-25 08:39:46 +09:00
Scala Steward
917663e0df Update mockito-core to 3.12.2 2021-08-25 06:11:05 +09:00
Scala Steward
556ddbc926 Update tika-core to 2.1.0 2021-08-24 23:07:23 +09:00
Scala Steward
1c6f37b8e8 Update testcontainers-scala to 0.39.6 2021-08-23 15:26:54 +09:00
Scala Steward
720a329a50 Update oauth2-oidc-sdk to 9.15 2021-08-21 07:09:24 +09:00
Scala Steward
220a8f076a Update mockito-core to 3.12.1 2021-08-21 07:09:08 +09:00
Scala Steward
43be8333c7 Update oauth2-oidc-sdk to 9.14 2021-08-20 21:08:36 +09:00
Scala Steward
08706ab4df Update mockito-core to 3.12.0 2021-08-20 08:04:50 +09:00
55 changed files with 1013 additions and 391 deletions

View File

@@ -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:

View File

@@ -4,4 +4,5 @@ updates.includeScala = true
updates.pin = [
{ groupId = "org.eclipse.jetty", version = "9." }
{ groupId = "org.eclipse.jgit", version = "5." }
]

View File

@@ -1,6 +1,15 @@
# Changelog
All changes to the project will be documented in this file.
### 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

View File

@@ -61,18 +61,15 @@ 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.2 - 16 Aug 2021
- Escape user name in avatar image tag
### 4.36.1 - 22 Jul 2021
- Bump gitbucket-gist-plugin to 4.21.0
### 4.36.0 - 17 Jul 2021
- Tag selector in the repository viewer
- Link issues/pull requests of other repositories
- Files and lines can be linked in the diff view
- Option to disable XSS protection
### 4.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.

View File

@@ -3,10 +3,10 @@ import com.jsuereth.sbtpgp.PgpKeys._
val Organization = "io.github.gitbucket"
val Name = "gitbucket"
val GitBucketVersion = "4.36.2"
val ScalatraVersion = "2.8.0"
val JettyVersion = "9.4.43.v20210629"
val JgitVersion = "5.12.0.202106070339-r"
val GitBucketVersion = "4.37.0"
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
@@ -28,8 +28,6 @@ resolvers ++= Seq(
"sonatype-snapshot" at "https://oss.sonatype.org/content/repositories/snapshots/"
)
libraryDependencySchemes += "org.scala-lang.modules" %% "scala-xml" % "always"
libraryDependencies ++= Seq(
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % JgitVersion,
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % JgitVersion,
@@ -44,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.4",
"org.postgresql" % "postgresql" % "42.2.23",
"ch.qos.logback" % "logback-classic" % "1.2.5",
"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.12",
"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.132" % "test"
"org.kohsuke" % "github-api" % "1.301" % "test"
)
libraryDependencies ~= {

View File

@@ -1 +1 @@
sbt.version=1.5.5
sbt.version=1.5.6

View File

@@ -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

View File

@@ -1,30 +1,66 @@
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 connectors = getEnvironmentVariable("gitbucket.connectors");
String host = getEnvironmentVariable("gitbucket.host");
String port = getEnvironmentVariable("gitbucket.port");
InetSocketAddress address;
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 forceHttps = false;
boolean saveSessions = false;
for(String arg: args) {
@@ -32,15 +68,33 @@ public class JettyLauncher {
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;
@@ -62,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);
@@ -107,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);
@@ -131,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();
@@ -165,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);
}
}

View File

@@ -1,4 +1,4 @@
notifications:1.10.0
gist:4.21.0
emoji:4.6.0
pages:1.9.0
pages:1.10.0

View 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>

View File

@@ -119,5 +119,6 @@ object GitBucketCoreModule
new Version("4.35.3"),
new Version("4.36.0", new LiquibaseMigration("update/gitbucket-core_4.36.xml")),
new Version("4.36.1"),
new Version("4.36.2")
new Version("4.36.2"),
new Version("4.37.0", new LiquibaseMigration("update/gitbucket-core_4.37.xml")),
)

View File

@@ -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,

View File

@@ -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"
)
)
}

View File

@@ -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")
)
}

View File

@@ -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(

View File

@@ -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

View File

@@ -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.")

View File

@@ -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
}
}

View File

@@ -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()

View File

@@ -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
)
)
}
}

View File

@@ -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)
)
}
})
/*

View File

@@ -341,8 +341,11 @@ trait IssuesService {
} 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"),

View File

@@ -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
)
)
}
}

View File

@@ -579,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,
@@ -596,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)))
}

View File

@@ -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 {
@@ -835,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}"

View File

@@ -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 =>
@@ -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"

View File

@@ -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),

View File

@@ -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

View File

@@ -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)

View File

@@ -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
@@ -159,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
@@ -177,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)
}
@@ -227,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")
@@ -238,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.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)
}
}
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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 = {

View File

@@ -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 {

View File

@@ -228,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
@@ -347,7 +348,8 @@ object JGitUtil {
ref.getName.stripPrefix("refs/tags/"),
revCommit.getCommitterIdent.getWhen,
revCommit.getName,
revCommit.getShortMessage
revCommit.getShortMessage,
ref.getObjectId.getName
)
)
} catch {
@@ -382,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)
@@ -658,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
}
@@ -671,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] = {

View File

@@ -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>

View File

@@ -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){

View File

@@ -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">

View File

@@ -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>
}
}

View File

@@ -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');
}

View File

@@ -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)
}
}

View File

@@ -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"/>

View File

@@ -15,7 +15,7 @@
}
</div>
}
<h1 class="wiki-title">
<h1 class="body-title">
@if(pageName.isEmpty){
<span class="muted">History</span>
} else {

View File

@@ -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>
}
}
}

View 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>

View File

@@ -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

View File

@@ -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")
}
}
}

View File

@@ -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",

View File

@@ -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))

View File

@@ -47,8 +47,8 @@ trait ServiceSpecBase {
limitVisibleRepositories = false,
ssh = Ssh(
enabled = false,
sshHost = None,
sshPort = None
bindAddress = None,
publicAddress = None
),
useSMTP = false,
smtp = None,

View File

@@ -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"
}
}
}
}

View File

@@ -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",

View File

@@ -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])
}
}
}
}

View File

@@ -166,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,

View File

@@ -1,7 +1,6 @@
package gitbucket.core.view
import gitbucket.core.util.SyntaxSugars
import SyntaxSugars._
import org.scalatest.funspec.AnyFunSpec
class PaginationSpec extends AnyFunSpec {