mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-01-04 22:59:58 +01:00
Merge branch 'master' into custom-ssh-url
This commit is contained in:
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
java: [8, 11]
|
||||
java: [8, 11, 17]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Cache
|
||||
|
||||
30
build.sbt
30
build.sbt
@@ -4,9 +4,9 @@ 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 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.sshd" % "apache-sshd" % "2.7.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",
|
||||
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
|
||||
"com.nimbusds" % "oauth2-oidc-sdk" % "9.15",
|
||||
"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.12.4" % "test",
|
||||
"com.dimafeng" %% "testcontainers-scala" % "0.39.6" % "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 ~= {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
||||
|
||||
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3")
|
||||
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.4")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.1")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "1.0.0")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "1.1.0")
|
||||
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.4")
|
||||
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0")
|
||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.8.2")
|
||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.2")
|
||||
|
||||
addDependencyTreePlugin
|
||||
|
||||
@@ -1,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
notifications:1.10.0
|
||||
gist:4.21.0
|
||||
emoji:4.6.0
|
||||
pages:1.9.0
|
||||
pages:1.10.0
|
||||
|
||||
7
src/main/resources/update/gitbucket-core_4.37.xml
Normal file
7
src/main/resources/update/gitbucket-core_4.37.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<changeSet>
|
||||
<dropForeignKeyConstraint constraintName="IDX_WEB_HOOK_EVENT_FK0" baseTableName="WEB_HOOK_EVENT"/>
|
||||
<modifyDataType columnName="URL" newDataType="varchar(400)" tableName="WEB_HOOK_EVENT"/>
|
||||
<modifyDataType columnName="URL" newDataType="varchar(400)" tableName="WEB_HOOK"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_WEB_HOOK_EVENT_FK0" baseTableName="WEB_HOOK_EVENT" baseColumnNames="USER_NAME, REPOSITORY_NAME, URL" referencedTableName="WEB_HOOK" referencedColumnNames="USER_NAME, REPOSITORY_NAME, URL" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||
</changeSet>
|
||||
@@ -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")),
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -197,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(
|
||||
@@ -211,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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -102,17 +102,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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import org.apache.http.HttpRequest
|
||||
import org.apache.http.HttpResponse
|
||||
import gitbucket.core.model.WebHookContentType
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import gitbucket.core.view.helpers.getApiMilestone
|
||||
import org.apache.http.client.entity.EntityBuilder
|
||||
import org.apache.http.entity.ContentType
|
||||
|
||||
@@ -394,7 +395,8 @@ trait WebHookPullRequestService extends WebHookService {
|
||||
ApiUser(issueUser),
|
||||
issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)),
|
||||
getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||
.map(ApiLabel(_, RepositoryName(repository)))
|
||||
.map(ApiLabel(_, RepositoryName(repository))),
|
||||
getApiMilestone(repository, issue.milestoneId getOrElse (0))
|
||||
),
|
||||
sender = ApiUser(sender)
|
||||
)
|
||||
@@ -576,6 +578,7 @@ trait WebHookIssueCommentService extends WebHookPullRequestService {
|
||||
commenter <- users.get(issueComment.commentedUserName)
|
||||
assignedUser = issue.assignedUserName.flatMap(users.get(_))
|
||||
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||
milestone = getApiMilestone(repository, issue.milestoneId getOrElse (0))
|
||||
} yield {
|
||||
WebHookIssueCommentPayload(
|
||||
issue = issue,
|
||||
@@ -586,7 +589,8 @@ trait WebHookIssueCommentService extends WebHookPullRequestService {
|
||||
repositoryUser = repoOwner,
|
||||
assignedUser = assignedUser,
|
||||
sender = sender,
|
||||
labels = labels
|
||||
labels = labels,
|
||||
milestone = milestone
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -760,7 +764,8 @@ object WebHookService {
|
||||
repositoryUser: Account,
|
||||
assignedUser: Option[Account],
|
||||
sender: Account,
|
||||
labels: List[Label]
|
||||
labels: List[Label],
|
||||
milestone: Option[ApiMilestone]
|
||||
): WebHookIssueCommentPayload =
|
||||
WebHookIssueCommentPayload(
|
||||
action = "created",
|
||||
@@ -770,7 +775,8 @@ object WebHookService {
|
||||
RepositoryName(repository),
|
||||
ApiUser(issueUser),
|
||||
assignedUser.map(ApiUser(_)),
|
||||
labels.map(ApiLabel(_, RepositoryName(repository)))
|
||||
labels.map(ApiLabel(_, RepositoryName(repository))),
|
||||
milestone
|
||||
),
|
||||
comment =
|
||||
ApiComment(comment, RepositoryName(repository), issue.issueId, ApiUser(commentUser), issue.isPullRequest),
|
||||
|
||||
@@ -75,13 +75,15 @@ trait WikiService {
|
||||
def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = {
|
||||
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
if (!JGitUtil.isEmpty(git)) {
|
||||
JGitUtil.getFileList(git, "master", ".").find(_.name == pageName + ".md").map { file =>
|
||||
val fileName = pageName + ".md"
|
||||
JGitUtil.getLatestCommitFromPath(git, fileName, "master").map { latestCommit =>
|
||||
val content = JGitUtil.getContentFromPath(git, latestCommit.getTree, fileName, true)
|
||||
WikiPageInfo(
|
||||
file.name,
|
||||
StringUtil.convertFromByteArray(git.getRepository.open(file.id).getBytes),
|
||||
file.author,
|
||||
file.time,
|
||||
file.commitId
|
||||
fileName,
|
||||
StringUtil.convertFromByteArray(content.getOrElse(Array.empty)),
|
||||
latestCommit.getAuthorIdent.getName,
|
||||
latestCommit.getAuthorIdent.getWhen,
|
||||
latestCommit.getName
|
||||
)
|
||||
}
|
||||
} else None
|
||||
|
||||
@@ -5,9 +5,9 @@ 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}
|
||||
@@ -15,6 +15,7 @@ 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
|
||||
@@ -28,7 +29,7 @@ object GitCommand {
|
||||
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])
|
||||
|
||||
@@ -61,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
|
||||
@@ -235,7 +236,7 @@ class PluginGitReceivePack(repoName: String, routing: GitRepositoryRouting)
|
||||
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")
|
||||
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
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 =
|
||||
"""
|
||||
@@ -41,7 +43,7 @@ class NoShell(sshAddress: SshAddress) extends Factory[Command] {
|
||||
callback.onExit(127)
|
||||
}
|
||||
|
||||
override def destroy(): Unit = {}
|
||||
override def destroy(channel: ChannelSession): Unit = {}
|
||||
|
||||
override def setInputStream(in: InputStream): Unit = {
|
||||
this.in = in
|
||||
|
||||
@@ -8,13 +8,13 @@ import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.ssh.PublicKeyAuthenticator.AuthType
|
||||
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator
|
||||
import org.apache.sshd.server.session.ServerSession
|
||||
import org.apache.sshd.common.AttributeStore
|
||||
import org.apache.sshd.common.AttributeRepository
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
object PublicKeyAuthenticator {
|
||||
|
||||
// put in the ServerSession here to be read by GitCommand later
|
||||
private val authTypeSessionKey = new AttributeStore.AttributeKey[AuthType]
|
||||
private val authTypeSessionKey = new AttributeRepository.AttributeKey[AuthType]
|
||||
|
||||
def putAuthType(serverSession: ServerSession, authType: AuthType): Unit =
|
||||
serverSession.setAttribute(authTypeSessionKey, authType)
|
||||
|
||||
@@ -382,7 +382,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 +658,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 +675,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] = {
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -244,7 +244,7 @@ function updateHighlighting() {
|
||||
const isDark = @{highlighterTheme.contains("dark").toString};
|
||||
if (hash.match(/#L\d+(-L\d+)?/)) {
|
||||
if (isDark) {
|
||||
$('li.highlight').removeClass('highlight-dark');
|
||||
$('li.highlight-dark').removeClass('highlight-dark');
|
||||
} else {
|
||||
$('li.highlight').removeClass('highlight');
|
||||
}
|
||||
|
||||
@@ -9,32 +9,22 @@
|
||||
@gitbucket.core.html.main(s"Compare Revisions - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@gitbucket.core.helper.html.information(info)
|
||||
@gitbucket.core.html.menu("wiki", repository){
|
||||
<ul class="nav nav-tabs fill-width">
|
||||
<li>
|
||||
<h1 class="wiki-title"><span class="muted">Compare Revisions</span></h1>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<div class="btn-group">
|
||||
@if(pageName.isDefined){
|
||||
<a class="btn btn-small btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)">View Page</a>
|
||||
<a class="btn btn-small btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_history">Back to Page History</a>
|
||||
<div class="pull-right">
|
||||
@if(pageName.isDefined){
|
||||
<a class="btn btn-small btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)">View Page</a>
|
||||
<a class="btn btn-small btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_history">Back to Page History</a>
|
||||
} else {
|
||||
<a class="btn btn-small btn-default" href="@helpers.url(repository)/wiki/_history">Back to Wiki History</a>
|
||||
}
|
||||
@if(isEditable) {
|
||||
@if(pageName.isDefined) {
|
||||
<a href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_revert/@from...@to" class="btn btn-danger">Revert Changes</a>
|
||||
} else {
|
||||
<a class="btn btn-small btn-default" href="@helpers.url(repository)/wiki/_history">Back to Wiki History</a>
|
||||
<a href="@helpers.url(repository)/wiki/_revert/@from...@to" class="btn btn-danger">Revert Changes</a>
|
||||
}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="pull-left">
|
||||
@gitbucket.core.helper.html.diff(diffs, repository, None, None, false, None, false, false)
|
||||
}
|
||||
</div>
|
||||
@if(isEditable){
|
||||
<div>
|
||||
@if(pageName.isDefined){
|
||||
<a href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_revert/@from...@to" class="btn">Revert Changes</a>
|
||||
} else {
|
||||
<a href="@helpers.url(repository)/wiki/_revert/@from...@to" class="btn">Revert Changes</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<h1 class="body-title"><span class="muted">Compare Revisions</span></h1>
|
||||
@gitbucket.core.helper.html.diff(diffs, repository, None, None, false, None, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<a class="btn btn-danger" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_delete" id="delete">Delete Page</a>
|
||||
}
|
||||
</div>
|
||||
<h1 class="wiki-title"><span class="muted">Editing</span> @if(pageName.isEmpty){New Page} else {@pageName}</h1>
|
||||
<h1 class="body-title"><span class="muted">Editing</span> @if(pageName.isEmpty){New Page} else {@pageName}</h1>
|
||||
<form action="@helpers.url(repository)/wiki/@if(page.isEmpty){_new} else {_edit}" method="POST" validate="true" autocomplete="off">
|
||||
<span id="error-pageName" class="error"></span>
|
||||
<input type="text" name="pageName" value="@pageName" class="form-control" style="font-weight: bold; margin-bottom: 10px;" placeholder="Input a page name." aria-label="Page name"/>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<h1 class="wiki-title">
|
||||
<h1 class="body-title">
|
||||
@if(pageName.isEmpty){
|
||||
<span class="muted">History</span>
|
||||
} else {
|
||||
|
||||
@@ -4,20 +4,17 @@
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.html.main(s"Pages - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@gitbucket.core.html.menu("wiki", repository){
|
||||
<ul class="nav nav-tabs fill-width">
|
||||
<li>
|
||||
<h1 class="wiki-title"><span class="muted">Pages</span></h1>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
@if(isEditable){
|
||||
<a class="btn btn-default" href="@helpers.url(repository)/wiki/_new">New Page</a>
|
||||
}
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="pull-left">
|
||||
<div class="pull-right">
|
||||
@if(isEditable){
|
||||
<a class="btn btn-default" href="@helpers.url(repository)/wiki/_new">New Page</a>
|
||||
}
|
||||
</div>
|
||||
<h1 class="body-title"><span class="muted">Pages</span></h1>
|
||||
<hr>
|
||||
<ul>
|
||||
@pages.map { page =>
|
||||
<li><a href="@helpers.url(repository)/wiki/@helpers.urlEncode(page)">@page</a></li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,6 +177,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 +203,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 +224,8 @@ object ApiSpecModels {
|
||||
repositoryName = repo1Name,
|
||||
user = apiUser,
|
||||
assignee = None,
|
||||
labels = List(apiLabel)
|
||||
labels = List(apiLabel),
|
||||
milestone = Some(apiMilestone)
|
||||
)
|
||||
|
||||
val apiIssuePR = ApiIssue(
|
||||
@@ -214,7 +233,8 @@ object ApiSpecModels {
|
||||
repositoryName = repo1Name,
|
||||
user = apiUser,
|
||||
assignee = Some(apiUser),
|
||||
labels = List(apiLabel)
|
||||
labels = List(apiLabel),
|
||||
milestone = Some(apiMilestone)
|
||||
)
|
||||
|
||||
val apiComment = ApiComment(
|
||||
@@ -471,6 +491,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 +514,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 +530,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 +547,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",
|
||||
|
||||
@@ -121,7 +121,8 @@ class WebHookJsonFormatSpec extends AnyFunSuite {
|
||||
repositoryUser = account,
|
||||
assignedUser = Some(account),
|
||||
sender = account,
|
||||
labels = List(label)
|
||||
labels = List(label),
|
||||
milestone = Some(apiMilestone)
|
||||
)
|
||||
val expected = s"""{
|
||||
|"action":"created",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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.matchers.should.Matchers
|
||||
import org.scalatest.wordspec.AnyWordSpec
|
||||
@@ -15,69 +16,70 @@ class GitCommandFactorySpec extends AnyWordSpec with Matchers {
|
||||
}
|
||||
|
||||
"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("git-receive-pack '/owner/repo.git'").isInstanceOf[DefaultGitReceivePack])
|
||||
assert(factory.createCommand("git-receive-pack '/owner/repo.wiki.git'").isInstanceOf[DefaultGitReceivePack])
|
||||
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("git-receive-pack 'owner/repo.git'").isInstanceOf[DefaultGitReceivePack])
|
||||
assert(factory.createCommand("git-receive-pack 'owner/repo.wiki.git'").isInstanceOf[DefaultGitReceivePack])
|
||||
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("git-receive-pack 'owner/repo.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand("git-receive-pack 'owner/repo.wiki.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand("git-receive-pack 'oranges.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand("git-receive-pack 'apples.git'").isInstanceOf[UnknownCommand])
|
||||
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("git-receive-pack '/owner/repo/wrong.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand("git-receive-pack '/owner:repo.wiki.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand("git-receive-pack '/oranges'").isInstanceOf[UnknownCommand])
|
||||
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("git-upload-pack '/owner/repo.git'").isInstanceOf[DefaultGitUploadPack])
|
||||
assert(factory.createCommand("git-upload-pack '/owner/repo.wiki.git'").isInstanceOf[DefaultGitUploadPack])
|
||||
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("git-upload-pack 'owner/repo.git'").isInstanceOf[DefaultGitUploadPack])
|
||||
assert(factory.createCommand("git-upload-pack 'owner/repo.wiki.git'").isInstanceOf[DefaultGitUploadPack])
|
||||
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("git-upload-pack 'owner/repo.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand("git-upload-pack 'owner/repo.wiki.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand("git-upload-pack 'oranges.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand("git-upload-pack 'apples.git'").isInstanceOf[UnknownCommand])
|
||||
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("git-upload-pack '/owner/repo'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand("git-upload-pack '/owner:repo.wiki.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand("git-upload-pack '/oranges'").isInstanceOf[UnknownCommand])
|
||||
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("git-destroy-pack '/owner/repo.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand("git-irrigate-pack '/apples.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand("git-force-push '/stolen/nuke.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand("git-delete '/backups.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand("git-pack '/your/bags.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand("git- '/bananas.git'").isInstanceOf[UnknownCommand])
|
||||
assert(factory.createCommand("99 tickets of bugs on the wall").isInstanceOf[UnknownCommand])
|
||||
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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user