mirror of
				https://github.com/gitbucket/gitbucket.git
				synced 2025-10-31 18:46:28 +01:00 
			
		
		
		
	Compare commits
	
		
			86 Commits
		
	
	
		
			4.36.0
			...
			optimize-c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 9cc3a0d36e | ||
|  | 7f665c649b | ||
|  | dcbadb4071 | ||
|  | e3096d15ff | ||
|  | a83c24e7b3 | ||
|  | 73457c9658 | ||
|  | cfc8d9f3f1 | ||
|  | 8f423b83ea | ||
|  | 1e7ac532b6 | ||
|  | 0fd1db4596 | ||
|  | 0755b7ab7f | ||
|  | 052382e5c4 | ||
|  | f44a63cec1 | ||
|  | 603d67354a | ||
|  | aafa423b9f | ||
|  | 2da9d0a801 | ||
|  | b60c112a74 | ||
|  | 843ed6df37 | ||
|  | 0f0a849677 | ||
|  | 682901ccbb | ||
|  | 048fdb8837 | ||
|  | 3353616789 | ||
|  | b6cb4c865f | ||
|  | 1fcfd093f7 | ||
|  | 3f27c6e733 | ||
|  | b6bd9bfc3b | ||
|  | 6c392f0056 | ||
|  | 9a38de9a23 | ||
|  | 8883600090 | ||
|  | ab822a3c27 | ||
|  | 0e4d64de23 | ||
|  | fbc6bd36bd | ||
|  | ed90ca2dce | ||
|  | 537ef92149 | ||
|  | d51afa7d40 | ||
|  | 975cffff48 | ||
|  | d92e9c00e8 | ||
|  | 12d72cbb19 | ||
|  | d8e03bed1f | ||
|  | f48c087cd8 | ||
|  | 917663e0df | ||
|  | 556ddbc926 | ||
|  | 1c6f37b8e8 | ||
|  | 720a329a50 | ||
|  | 220a8f076a | ||
|  | 43be8333c7 | ||
|  | 08706ab4df | ||
|  | b1196657e0 | ||
|  | 334bd0c919 | ||
|  | cf0f896972 | ||
|  | d21ca3ff8a | ||
|  | 83f1f16de7 | ||
|  | 0fa2ccf107 | ||
|  | 18e3dd431b | ||
|  | f25dee2249 | ||
|  | 575ffa9580 | ||
|  | f17af5aeb0 | ||
|  | 639f153589 | ||
|  | fb07098c13 | ||
|  | 74fc08b039 | ||
|  | 2776e00004 | ||
|  | 5932fce303 | ||
|  | 39c9fc4261 | ||
|  | 89ea4509a3 | ||
|  | d19d838ead | ||
|  | 633a2699a8 | ||
|  | e79bca4a3c | ||
|  | f8ab480d20 | ||
|  | a14129e340 | ||
|  | 5ec39df6e0 | ||
|  | 956e0c6321 | ||
|  | 0660a9203a | ||
|  | 1b660272a1 | ||
|  | d9ef9b874d | ||
|  | 7824f796ee | ||
|  | 838a8d4c7b | ||
|  | 8db9f77f91 | ||
|  | 4f82a469d9 | ||
|  | 0f6ae8559b | ||
|  | 3f4b2eec35 | ||
|  | a020601d3a | ||
|  | d511205588 | ||
|  | fc896b2ea1 | ||
|  | 3819670535 | ||
|  | 067669a18c | ||
|  | bef7cee8db | 
							
								
								
									
										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 | ||||
|   | ||||
| @@ -1,9 +1,15 @@ | ||||
| # Changelog | ||||
| All changes to the project will be documented in this file. | ||||
|  | ||||
| ### 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 iusses/pull requests of other repositories | ||||
| - Link issues/pull requests of other repositories | ||||
| - Files and lines can be linked in the diff view | ||||
| - Option to disable XSS protection | ||||
|  | ||||
|   | ||||
| @@ -63,9 +63,15 @@ Support | ||||
|  | ||||
| What's New in 4.36.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 iusses/pull requests of other repositories | ||||
| - Link issues/pull requests of other repositories | ||||
| - Files and lines can be linked in the diff view | ||||
| - Option to disable XSS protection | ||||
|  | ||||
|   | ||||
							
								
								
									
										38
									
								
								build.sbt
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								build.sbt
									
									
									
									
									
								
							| @@ -3,10 +3,10 @@ import com.jsuereth.sbtpgp.PgpKeys._ | ||||
|  | ||||
| val Organization = "io.github.gitbucket" | ||||
| val Name = "gitbucket" | ||||
| val GitBucketVersion = "4.36.0" | ||||
| val ScalatraVersion = "2.7.1" | ||||
| val JettyVersion = "9.4.43.v20210629" | ||||
| val JgitVersion = "5.12.0.202106070339-r" | ||||
| val GitBucketVersion = "4.36.2" | ||||
| val ScalatraVersion = "2.8.2" | ||||
| val JettyVersion = "9.4.44.v20210927" | ||||
| val JgitVersion = "5.13.0.202109080827-r" | ||||
|  | ||||
| lazy val root = (project in file(".")) | ||||
|   .enablePlugins(SbtTwirl, ScalatraPlugin) | ||||
| @@ -15,7 +15,7 @@ sourcesInBase := false | ||||
| organization := Organization | ||||
| name := Name | ||||
| version := GitBucketVersion | ||||
| scalaVersion := "2.13.6" | ||||
| scalaVersion := "2.13.7" | ||||
|  | ||||
| scalafmtOnCompile := true | ||||
|  | ||||
| @@ -34,7 +34,7 @@ libraryDependencies ++= Seq( | ||||
|   "org.scalatra"                    %% "scalatra"                    % ScalatraVersion cross CrossVersion.for3Use2_13, | ||||
|   "org.scalatra"                    %% "scalatra-json"               % ScalatraVersion cross CrossVersion.for3Use2_13, | ||||
|   "org.scalatra"                    %% "scalatra-forms"              % ScalatraVersion cross CrossVersion.for3Use2_13, | ||||
|   "org.json4s"                      %% "json4s-jackson"              % "3.6.11" cross CrossVersion.for3Use2_13, | ||||
|   "org.json4s"                      %% "json4s-jackson"              % "4.0.3" cross CrossVersion.for3Use2_13, | ||||
|   "commons-io"                      % "commons-io"                   % "2.11.0", | ||||
|   "io.github.gitbucket"             % "solidbase"                    % "1.0.3", | ||||
|   "io.github.gitbucket"             % "markedj"                      % "1.0.16", | ||||
| @@ -43,33 +43,33 @@ libraryDependencies ++= Seq( | ||||
|   "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"                    % "1.27", | ||||
|   "org.apache.tika"                 % "tika-core"                    % "2.1.0", | ||||
|   "com.github.takezoe"              %% "blocking-slick-32"           % "0.0.12" cross CrossVersion.for3Use2_13, | ||||
|   "com.novell.ldap"                 % "jldap"                        % "2009-10-07", | ||||
|   "com.h2database"                  % "h2"                           % "1.4.199", | ||||
|   "org.mariadb.jdbc"                % "mariadb-java-client"          % "2.7.3", | ||||
|   "org.postgresql"                  % "postgresql"                   % "42.2.23", | ||||
|   "ch.qos.logback"                  % "logback-classic"              % "1.2.3", | ||||
|   "com.zaxxer"                      % "HikariCP"                     % "4.0.3", | ||||
|   "org.mariadb.jdbc"                % "mariadb-java-client"          % "2.7.4", | ||||
|   "org.postgresql"                  % "postgresql"                   % "42.3.1", | ||||
|   "ch.qos.logback"                  % "logback-classic"              % "1.2.6", | ||||
|   "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.10.1", | ||||
|   "com.nimbusds"                    % "oauth2-oidc-sdk"              % "9.19", | ||||
|   "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.15.3" % "test", | ||||
|   "org.testcontainers"              % "postgresql"                   % "1.15.3" % "test", | ||||
|   "org.mockito"                     % "mockito-core"                 % "4.0.0" % "test", | ||||
|   "com.dimafeng"                    %% "testcontainers-scala"        % "0.39.11" % "test", | ||||
|   "org.testcontainers"              % "mysql"                        % "1.16.2" % "test", | ||||
|   "org.testcontainers"              % "postgresql"                   % "1.16.2" % "test", | ||||
|   "net.i2p.crypto"                  % "eddsa"                        % "0.3.0", | ||||
|   "is.tagomor.woothee"              % "woothee-java"                 % "1.11.0", | ||||
|   "org.ec4j.core"                   % "ec4j-core"                    % "0.3.0", | ||||
|   "org.kohsuke"                     % "github-api"                   % "1.131" % "test" | ||||
|   "org.kohsuke"                     % "github-api"                   % "1.135" % "test" | ||||
| ) | ||||
|  | ||||
| libraryDependencies ~= { | ||||
| @@ -200,7 +200,7 @@ executableKey := { | ||||
|   manifest.getMainAttributes put (AttrName.MANIFEST_VERSION, "1.0") | ||||
|   manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher") | ||||
|   val outputFile = workDir / warName | ||||
|   IO jar (contentMappings.map { case (file, path) => (file, path.toString) }, outputFile, manifest) | ||||
|   IO jar (contentMappings.map { case (file, path) => (file, path.toString) }, outputFile, manifest, None) | ||||
|  | ||||
|   // generate checksums | ||||
|   Seq( | ||||
|   | ||||
| @@ -2,9 +2,10 @@ scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature") | ||||
|  | ||||
| addSbtPlugin("org.scalameta"    % "sbt-scalafmt"       % "2.4.3") | ||||
| 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("net.virtual-void" % "sbt-dependency-graph" % "0.9.2") | ||||
| addSbtPlugin("org.scoverage"    % "sbt-scoverage"        % "1.8.2") | ||||
| addSbtPlugin("org.scoverage"    % "sbt-scoverage"      % "1.9.2") | ||||
|  | ||||
| addDependencyTreePlugin | ||||
|   | ||||
| @@ -1,51 +1,100 @@ | ||||
| import org.eclipse.jetty.server.ConnectionFactory; | ||||
| import org.eclipse.jetty.server.Connector; | ||||
| import org.eclipse.jetty.http.HttpVersion; | ||||
| import org.eclipse.jetty.server.Handler; | ||||
| import org.eclipse.jetty.server.HttpConfiguration; | ||||
| import org.eclipse.jetty.server.HttpConnectionFactory; | ||||
| import org.eclipse.jetty.server.SecureRequestCustomizer; | ||||
| import org.eclipse.jetty.server.Server; | ||||
| import org.eclipse.jetty.server.ServerConnector; | ||||
| import org.eclipse.jetty.server.SslConnectionFactory; | ||||
| import org.eclipse.jetty.server.handler.HandlerList; | ||||
| import org.eclipse.jetty.server.handler.SecuredRedirectHandler; | ||||
| import org.eclipse.jetty.server.handler.StatisticsHandler; | ||||
| import org.eclipse.jetty.server.session.DefaultSessionCache; | ||||
| import org.eclipse.jetty.server.session.FileSessionDataStore; | ||||
| import org.eclipse.jetty.server.session.SessionCache; | ||||
| import org.eclipse.jetty.server.session.SessionHandler; | ||||
| import org.eclipse.jetty.util.ssl.SslContextFactory; | ||||
| import org.eclipse.jetty.webapp.WebAppContext; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.net.InetAddress; | ||||
| import java.net.URL; | ||||
| import java.net.InetSocketAddress; | ||||
| import java.security.ProtectionDomain; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| import java.util.Set; | ||||
| import java.util.function.Function; | ||||
| import java.util.stream.Stream; | ||||
|  | ||||
| import static java.util.function.Function.identity; | ||||
| import static java.util.stream.Collectors.toSet; | ||||
|  | ||||
| public class JettyLauncher { | ||||
|  | ||||
|     private interface Defaults { | ||||
|  | ||||
|         String CONNECTORS = "http"; | ||||
|         String HOST = "0.0.0.0"; | ||||
|  | ||||
|         int HTTP_PORT = 8080; | ||||
|         int HTTPS_PORT = 8443; | ||||
|  | ||||
|         boolean REDIRECT_HTTPS = false; | ||||
|     } | ||||
|  | ||||
|     private interface Connectors { | ||||
|  | ||||
|         String HTTP = "http"; | ||||
|         String HTTPS = "https"; | ||||
|     } | ||||
|  | ||||
|     public static void main(String[] args) throws Exception { | ||||
|         System.setProperty("java.awt.headless", "true"); | ||||
|  | ||||
|         String host = null; | ||||
|         String port = null; | ||||
|         InetSocketAddress address = null; | ||||
|         String contextPath = "/"; | ||||
|         String tmpDirPath=""; | ||||
|         boolean forceHttps = false; | ||||
|         String connectors = getEnvironmentVariable("gitbucket.connectors"); | ||||
|         String host = getEnvironmentVariable("gitbucket.host"); | ||||
|         String port = getEnvironmentVariable("gitbucket.port"); | ||||
|         String securePort = getEnvironmentVariable("gitbucket.securePort"); | ||||
|         String keyStorePath = getEnvironmentVariable("gitbucket.keyStorePath"); | ||||
|         String keyStorePassword = getEnvironmentVariable("gitbucket.keyStorePassword"); | ||||
|         String keyManagerPassword = getEnvironmentVariable("gitbucket.keyManagerPassword"); | ||||
|         String redirectHttps = getEnvironmentVariable("gitbucket.redirectHttps"); | ||||
|         String contextPath = getEnvironmentVariable("gitbucket.prefix"); | ||||
|         String tmpDirPath = getEnvironmentVariable("gitbucket.tempDir"); | ||||
|         boolean saveSessions = false; | ||||
|  | ||||
|         host = getEnvironmentVariable("gitbucket.host"); | ||||
|         port = getEnvironmentVariable("gitbucket.port"); | ||||
|         contextPath = getEnvironmentVariable("gitbucket.prefix"); | ||||
|         tmpDirPath = getEnvironmentVariable("gitbucket.tempDir"); | ||||
|  | ||||
|         for(String arg: args) { | ||||
|             if(arg.equals("--save_sessions")) { | ||||
|                 saveSessions = true; | ||||
|             } | ||||
|             if(arg.startsWith("--") && arg.contains("=")) { | ||||
|                 String[] dim = arg.split("="); | ||||
|                 if(dim.length >= 2) { | ||||
|                 String[] dim = arg.split("=", 2); | ||||
|                 if(dim.length == 2) { | ||||
|                     switch (dim[0]) { | ||||
|                         case "--connectors": | ||||
|                             connectors = dim[1]; | ||||
|                             break; | ||||
|                         case "--host": | ||||
|                             host = dim[1]; | ||||
|                             break; | ||||
|                         case "--port": | ||||
|                             port = dim[1]; | ||||
|                             break; | ||||
|                         case "--secure_port": | ||||
|                             securePort = dim[1]; | ||||
|                             break; | ||||
|                         case "--key_store_path": | ||||
|                             keyStorePath = dim[1]; | ||||
|                             break; | ||||
|                         case "--key_store_password": | ||||
|                             keyStorePassword = dim[1]; | ||||
|                             break; | ||||
|                         case "--key_manager_password": | ||||
|                             keyManagerPassword = dim[1]; | ||||
|                             break; | ||||
|                         case "--redirect_https": | ||||
|                             redirectHttps = dim[1]; | ||||
|                             break; | ||||
|                         case "--prefix": | ||||
|                             contextPath = dim[1]; | ||||
|                             break; | ||||
| @@ -67,38 +116,69 @@ public class JettyLauncher { | ||||
|             contextPath = "/" + contextPath; | ||||
|         } | ||||
|  | ||||
|         if(host != null) { | ||||
|             address = new InetSocketAddress(host, getPort(port)); | ||||
|         } else { | ||||
|             address = new InetSocketAddress(getPort(port)); | ||||
|         final String hostName = InetAddress.getByName(fallback(host, Defaults.HOST)).getHostName(); | ||||
|  | ||||
|         final Server server = new Server(); | ||||
|  | ||||
|         final Set<String> connectorsSet = Stream.of(fallback(connectors, Defaults.CONNECTORS) | ||||
|             .toLowerCase().split(",")).map(String::trim).collect(toSet()); | ||||
|  | ||||
|         final List<ServerConnector> connectorInstances = new ArrayList<>(); | ||||
|  | ||||
|         final HttpConfiguration httpConfig = new HttpConfiguration(); | ||||
|         httpConfig.setSendServerVersion(false); | ||||
|         if (connectorsSet.contains(Connectors.HTTPS)) { | ||||
|             httpConfig.setSecurePort(fallback(securePort, Defaults.HTTPS_PORT, Integer::parseInt)); | ||||
|         } | ||||
|  | ||||
|         Server server = new Server(address); | ||||
|         if (connectorsSet.contains(Connectors.HTTP)) { | ||||
|             final ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(httpConfig)); | ||||
|             connector.setHost(hostName); | ||||
|             connector.setPort(fallback(port, Defaults.HTTP_PORT, Integer::parseInt)); | ||||
|  | ||||
| //        SelectChannelConnector connector = new SelectChannelConnector(); | ||||
| //        if(host != null) { | ||||
| //            connector.setHost(host); | ||||
| //        } | ||||
| //        connector.setMaxIdleTime(1000 * 60 * 60); | ||||
| //        connector.setSoLingerTime(-1); | ||||
| //        connector.setPort(port); | ||||
| //        server.addConnector(connector); | ||||
|             connectorInstances.add(connector); | ||||
|         } | ||||
|  | ||||
|         // Disabling Server header | ||||
|         for (Connector connector : server.getConnectors()) { | ||||
|             for (ConnectionFactory factory : connector.getConnectionFactories()) { | ||||
|                 if (factory instanceof HttpConnectionFactory) { | ||||
|                     ((HttpConnectionFactory) factory).getHttpConfiguration().setSendServerVersion(false); | ||||
|                 } | ||||
|             } | ||||
|         if (connectorsSet.contains(Connectors.HTTPS)) { | ||||
|             final SslContextFactory sslContextFactory = new SslContextFactory.Server(); | ||||
|  | ||||
|             sslContextFactory.setKeyStorePath(requireNonNull(keyStorePath, | ||||
|                 "You must specify a path to an SSL keystore via the --key_store_path command line argument" + | ||||
|                     " or GITBUCKET_KEYSTOREPATH environment variable.")); | ||||
|  | ||||
|             sslContextFactory.setKeyStorePassword(requireNonNull(keyStorePassword, | ||||
|                 "You must specify a an SSL keystore password via the --key_store_password argument" + | ||||
|                     " or GITBUCKET_KEYSTOREPASSWORD environment variable.")); | ||||
|  | ||||
|             sslContextFactory.setKeyManagerPassword(requireNonNull(keyManagerPassword, | ||||
|                 "You must specify a key manager password via the --key_manager_password' argument" + | ||||
|                     " or GITBUCKET_KEYMANAGERPASSWORD environment variable.")); | ||||
|  | ||||
|             final HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig); | ||||
|             httpsConfig.addCustomizer(new SecureRequestCustomizer()); | ||||
|  | ||||
|             final ServerConnector connector = new ServerConnector(server, | ||||
|                 new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()), | ||||
|                 new HttpConnectionFactory(httpsConfig)); | ||||
|  | ||||
|             connector.setHost(hostName); | ||||
|             connector.setPort(fallback(securePort, Defaults.HTTPS_PORT, Integer::parseInt)); | ||||
|  | ||||
|             connectorInstances.add(connector); | ||||
|         } | ||||
|  | ||||
|         require(!connectorInstances.isEmpty(), | ||||
|             "No server connectors could be configured, please check your --connectors command line argument" + | ||||
|                 " or GITBUCKET_CONNECTORS environment variable."); | ||||
|  | ||||
|         server.setConnectors(connectorInstances.toArray(new ServerConnector[0])); | ||||
|  | ||||
|         WebAppContext context = new WebAppContext(); | ||||
|  | ||||
|         if(saveSessions) { | ||||
|             File sessDir = new File(getGitBucketHome(), "sessions"); | ||||
|             if(!sessDir.exists()){ | ||||
|                 sessDir.mkdirs(); | ||||
|                 mkdir(sessDir); | ||||
|             } | ||||
|             SessionHandler sessions = context.getSessionHandler(); | ||||
|             SessionCache cache = new DefaultSessionCache(sessions); | ||||
| @@ -112,7 +192,7 @@ public class JettyLauncher { | ||||
|         if(tmpDirPath == null || tmpDirPath.equals("")){ | ||||
|             tmpDir = new File(getGitBucketHome(), "tmp"); | ||||
|             if(!tmpDir.exists()){ | ||||
|                 tmpDir.mkdirs(); | ||||
|                 mkdir(tmpDir); | ||||
|             } | ||||
|         } else { | ||||
|             tmpDir = new File(tmpDirPath); | ||||
| @@ -136,13 +216,16 @@ public class JettyLauncher { | ||||
|         context.setDescriptor(location.toExternalForm() + "/WEB-INF/web.xml"); | ||||
|         context.setServer(server); | ||||
|         context.setWar(location.toExternalForm()); | ||||
|         if (forceHttps) { | ||||
|             context.setInitParameter("org.scalatra.ForceHttps", "true"); | ||||
|  | ||||
|         final HandlerList handlers = new HandlerList(); | ||||
|  | ||||
|         if (fallback(redirectHttps, Defaults.REDIRECT_HTTPS, Boolean::parseBoolean)) { | ||||
|             handlers.addHandler(new SecuredRedirectHandler()); | ||||
|         } | ||||
|  | ||||
|         Handler handler = addStatisticsHandler(context); | ||||
|         handlers.addHandler(addStatisticsHandler(context)); | ||||
|  | ||||
|         server.setHandler(handler); | ||||
|         server.setHandler(handlers); | ||||
|         server.setStopAtShutdown(true); | ||||
|         server.setStopTimeout(7_000); | ||||
|         server.start(); | ||||
| @@ -170,11 +253,28 @@ public class JettyLauncher { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static int getPort(String port){ | ||||
|         if(port == null) { | ||||
|             return 8080; | ||||
|         } else { | ||||
|             return Integer.parseInt(port); | ||||
|     private static <T, R> T fallback(R value, T defaultValue, Function<R, T> converter) { | ||||
|         return value == null ? defaultValue : converter.apply(value); | ||||
|     } | ||||
|  | ||||
|     private static <T> T fallback(T value, T defaultValue) { | ||||
|         return fallback(value, defaultValue, identity()); | ||||
|     } | ||||
|  | ||||
|     private static void require(boolean condition, String message) { | ||||
|         if (!condition) { | ||||
|             throw new IllegalArgumentException(message); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static <T> T requireNonNull(T value, String message) { | ||||
|         require(value != null, message); | ||||
|         return value; | ||||
|     } | ||||
|  | ||||
|     private static void mkdir(File dir) { | ||||
|         if (!dir.mkdirs()) { | ||||
|             throw new RuntimeException("Unable to create directory: " + dir); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -20,15 +20,15 @@ public class PatchUtil { | ||||
|     public static String apply(String source, String patch, FileHeader fh) | ||||
|             throws IOException, PatchApplyException { | ||||
|         RawText rt = new RawText(source.getBytes("UTF-8")); | ||||
|         List<String> oldLines = new ArrayList<String>(rt.size()); | ||||
|         List<String> oldLines = new ArrayList<>(rt.size()); | ||||
|         for (int i = 0; i < rt.size(); i++) | ||||
|             oldLines.add(rt.getString(i)); | ||||
|         List<String> newLines = new ArrayList<String>(oldLines); | ||||
|         List<String> newLines = new ArrayList<>(oldLines); | ||||
|         for (HunkHeader hh : fh.getHunks()) { | ||||
|             ByteArrayOutputStream out = new ByteArrayOutputStream(); | ||||
|             out.write(patch.getBytes("UTF-8"), hh.getStartOffset(), hh.getEndOffset() - hh.getStartOffset()); | ||||
|             RawText hrt = new RawText(out.toByteArray()); | ||||
|             List<String> hunkLines = new ArrayList<String>(hrt.size()); | ||||
|             List<String> hunkLines = new ArrayList<>(hrt.size()); | ||||
|             for (int i = 0; i < hrt.size(); i++) | ||||
|                 hunkLines.add(hrt.getString(i)); | ||||
|             int pos = 0; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| notifications:1.10.0 | ||||
| gist:4.20.0 | ||||
| gist:4.21.0 | ||||
| emoji:4.6.0 | ||||
| pages:1.9.0 | ||||
| pages:1.10.0 | ||||
|   | ||||
| @@ -3,7 +3,6 @@ package gitbucket.core | ||||
| import java.io.FileOutputStream | ||||
| import java.nio.charset.StandardCharsets | ||||
| import java.sql.Connection | ||||
| import java.util | ||||
| import java.util.UUID | ||||
|  | ||||
| import gitbucket.core.model.Activity | ||||
| @@ -84,7 +83,7 @@ object GitBucketCoreModule | ||||
|       new Version( | ||||
|         "4.34.0", | ||||
|         new Migration() { | ||||
|           override def migrate(moduleId: String, version: String, context: util.Map[String, AnyRef]): Unit = { | ||||
|           override def migrate(moduleId: String, version: String, context: java.util.Map[String, AnyRef]): Unit = { | ||||
|             implicit val formats: Formats = Serialization.formats(NoTypeHints) | ||||
|             import JDBCUtil._ | ||||
|  | ||||
| @@ -118,5 +117,7 @@ object GitBucketCoreModule | ||||
|       new Version("4.35.1"), | ||||
|       new Version("4.35.2"), | ||||
|       new Version("4.35.3"), | ||||
|       new Version("4.36.0", new LiquibaseMigration("update/gitbucket-core_4.36.xml")) | ||||
|       new Version("4.36.0", new LiquibaseMigration("update/gitbucket-core_4.36.xml")), | ||||
|       new Version("4.36.1"), | ||||
|       new Version("4.36.2") | ||||
|     ) | ||||
|   | ||||
| @@ -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( | ||||
| @@ -434,7 +434,7 @@ trait AccountControllerBase extends AccountManagementControllerBase { | ||||
|  | ||||
|   post("/:userName/_personalToken", personalTokenForm)(oneselfOnly { form => | ||||
|     val userName = params("userName") | ||||
|     getAccountByUserName(userName).map { x => | ||||
|     getAccountByUserName(userName).foreach { x => | ||||
|       val (tokenId, token) = generateAccessToken(userName, form.note) | ||||
|       flash.update("generatedToken", (tokenId, token)) | ||||
|     } | ||||
|   | ||||
| @@ -32,6 +32,7 @@ trait PreProcessControllerBase extends ControllerBase { | ||||
|   get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) { | ||||
|     if (!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") && | ||||
|         !context.currentPath.startsWith("/register") && !context.currentPath.endsWith("/info/refs") && | ||||
|         !context.currentPath.startsWith("/plugin-assets") && | ||||
|         !PluginRegistry().getAnonymousAccessiblePaths().exists { path => | ||||
|           context.currentPath.startsWith(path) | ||||
|         }) { | ||||
|   | ||||
| @@ -242,25 +242,21 @@ trait PullRequestsControllerBase extends ControllerBase { | ||||
|               branchIsOutOfDate = JGitUtil.getShaByRef(repository.owner, repository.name, pullreq.branch) != Some( | ||||
|                 pullreq.commitIdFrom | ||||
|               ), | ||||
|               needStatusCheck = context.loginAccount | ||||
|                 .map { u => | ||||
|               needStatusCheck = context.loginAccount.forall { u => | ||||
|                 branchProtection.needStatusCheck(u.userName) | ||||
|                 } | ||||
|                 .getOrElse(true), | ||||
|               }, | ||||
|               hasUpdatePermission = hasDeveloperRole( | ||||
|                 pullreq.requestUserName, | ||||
|                 pullreq.requestRepositoryName, | ||||
|                 context.loginAccount | ||||
|               ) && | ||||
|                 context.loginAccount | ||||
|                   .map { u => | ||||
|                 context.loginAccount.exists { u => | ||||
|                   !getProtectedBranchInfo( | ||||
|                     pullreq.requestUserName, | ||||
|                     pullreq.requestRepositoryName, | ||||
|                     pullreq.requestBranch | ||||
|                   ).needStatusCheck(u.userName) | ||||
|                   } | ||||
|                   .getOrElse(false), | ||||
|                 }, | ||||
|               hasMergePermission = hasMergePermission, | ||||
|               commitIdTo = pullreq.commitIdTo | ||||
|             ) | ||||
| @@ -494,8 +490,7 @@ trait PullRequestsControllerBase extends ControllerBase { | ||||
|               (repository.userName, repository.repositoryName, repository.defaultBranch) | ||||
|             }, | ||||
|             commits.flatten | ||||
|               .map(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false)) | ||||
|               .flatten | ||||
|               .flatMap(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false)) | ||||
|               .toList, | ||||
|             originId, | ||||
|             forkedId, | ||||
|   | ||||
| @@ -685,7 +685,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { | ||||
|         getPathObjectId(git, path, revCommit).map { | ||||
|           objectId => | ||||
|             if (raw) { | ||||
|               // Download (This route is left for backword compatibility) | ||||
|               // Download (This route is left for backward compatibility) | ||||
|               responseRawFile(git, objectId, path, repository) | ||||
|             } else { | ||||
|               val info = EditorConfigUtil.getEditorConfigInfo(git, id, path) | ||||
|   | ||||
| @@ -186,7 +186,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 +200,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( | ||||
| @@ -361,8 +361,8 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase { | ||||
|   }) | ||||
|  | ||||
|   get("/admin/users")(adminOnly { | ||||
|     val includeRemoved = params.get("includeRemoved").map(_.toBoolean).getOrElse(false) | ||||
|     val includeGroups = params.get("includeGroups").map(_.toBoolean).getOrElse(false) | ||||
|     val includeRemoved = params.get("includeRemoved").exists(_.toBoolean) | ||||
|     val includeGroups = params.get("includeGroups").exists(_.toBoolean) | ||||
|     val users = getAllUsers(includeRemoved, includeGroups) | ||||
|     val members = users.collect { | ||||
|       case account if (account.isGroupAccount) => | ||||
|   | ||||
| @@ -47,7 +47,8 @@ trait ApiIssueControllerBase extends ControllerBase { | ||||
|           user = ApiUser(issueUser), | ||||
|           assignee = assignedUser.map(ApiUser(_)), | ||||
|           labels = getIssueLabels(repository.owner, repository.name, issue.issueId) | ||||
|             .map(ApiLabel(_, RepositoryName(repository))) | ||||
|             .map(ApiLabel(_, RepositoryName(repository))), | ||||
|           issue.milestoneId.flatMap { getApiMilestone(repository, _) } | ||||
|         ) | ||||
|     }) | ||||
|   }) | ||||
| @@ -69,7 +70,8 @@ trait ApiIssueControllerBase extends ControllerBase { | ||||
|           RepositoryName(repository), | ||||
|           ApiUser(openedUser), | ||||
|           issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)), | ||||
|           getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository))) | ||||
|           getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository))), | ||||
|           issue.milestoneId.flatMap { getApiMilestone(repository, _) } | ||||
|         ) | ||||
|       ) | ||||
|     }) getOrElse NotFound() | ||||
| @@ -103,7 +105,8 @@ trait ApiIssueControllerBase extends ControllerBase { | ||||
|             ApiUser(loginAccount), | ||||
|             issue.assignedUserName.flatMap(getAccountByUserName(_)).map(ApiUser(_)), | ||||
|             getIssueLabels(repository.owner, repository.name, issue.issueId) | ||||
|               .map(ApiLabel(_, RepositoryName(repository))) | ||||
|               .map(ApiLabel(_, RepositoryName(repository))), | ||||
|             issue.milestoneId.flatMap { getApiMilestone(repository, _) } | ||||
|           ) | ||||
|         ) | ||||
|       }) getOrElse NotFound() | ||||
|   | ||||
| @@ -121,7 +121,7 @@ trait ApiIssueLabelControllerBase extends ControllerBase { | ||||
|    */ | ||||
|   post("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository => | ||||
|     JsonFormat(for { | ||||
|       data <- extractFromJsonBody[Seq[String]]; | ||||
|       data <- extractFromJsonBody[Seq[String]] | ||||
|       issueId <- params("id").toIntOpt | ||||
|     } yield { | ||||
|       data.map { labelName => | ||||
| @@ -160,7 +160,7 @@ trait ApiIssueLabelControllerBase extends ControllerBase { | ||||
|    */ | ||||
|   put("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository => | ||||
|     JsonFormat(for { | ||||
|       data <- extractFromJsonBody[Seq[String]]; | ||||
|       data <- extractFromJsonBody[Seq[String]] | ||||
|       issueId <- params("id").toIntOpt | ||||
|     } yield { | ||||
|       deleteAllIssueLabels(repository.owner, repository.name, issueId, true) | ||||
|   | ||||
| @@ -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 | ||||
|         ) | ||||
|       ) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -86,7 +86,7 @@ trait ApiReleaseControllerBase extends ControllerBase { | ||||
|   /** | ||||
|    * vi. Edit a release | ||||
|    * https://developer.github.com/v3/repos/releases/#edit-a-release | ||||
|    * Incompatiblity info: GitHub API requires :release_id, but GitBucket API requires :tag_name | ||||
|    * Incompatibility info: GitHub API requires :release_id, but GitBucket API requires :tag_name | ||||
|    */ | ||||
|   patch("/api/v3/repos/:owner/:repository/releases/:tag")(writableUsersOnly { repository => | ||||
|     (for { | ||||
| @@ -103,7 +103,7 @@ trait ApiReleaseControllerBase extends ControllerBase { | ||||
|   /** | ||||
|    * vii. Delete a release | ||||
|    * https://developer.github.com/v3/repos/releases/#delete-a-release | ||||
|    * Incompatiblity info: GitHub API requires :release_id, but GitBucket API requires :tag_name | ||||
|    * Incompatibility info: GitHub API requires :release_id, but GitBucket API requires :tag_name | ||||
|    */ | ||||
|   delete("/api/v3/repos/:owner/:repository/releases/:tag")(writableUsersOnly { repository => | ||||
|     val tag = params("tag") | ||||
|   | ||||
| @@ -40,7 +40,7 @@ trait ApiRepositoryCommitControllerBase extends ControllerBase { | ||||
|                   diffs = JGitUtil.getDiffs(git, commitInfo.parents.headOption, commitInfo.id, false, true), | ||||
|                   author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress), | ||||
|                   committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress), | ||||
|                   commentCount = getCommitComment(repository.owner, repository.name, commitInfo.id.toString).size | ||||
|                   commentCount = getCommitComment(repository.owner, repository.name, commitInfo.id).size | ||||
|                 ) | ||||
|             }) | ||||
|         } | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import gitbucket.core.api.{ApiCommit, ApiContents, ApiError, CreateAFile, JsonFo | ||||
| import gitbucket.core.controller.ControllerBase | ||||
| import gitbucket.core.service.{RepositoryCommitFileService, RepositoryService} | ||||
| import gitbucket.core.util.Directory.getRepositoryDir | ||||
| import gitbucket.core.util.JGitUtil.{CommitInfo, FileInfo, getContentFromId, getFileList} | ||||
| import gitbucket.core.util.JGitUtil.{CommitInfo, FileInfo, getContentFromId, getFileList, getSummaryMessage} | ||||
| import gitbucket.core.util._ | ||||
| import gitbucket.core.view.helpers.{isRenderable, renderMarkup} | ||||
| import gitbucket.core.util.Implicits._ | ||||
| @@ -49,19 +49,21 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase { | ||||
|     getContents(repository, multiParams("splat").head, params.getOrElse("ref", repository.repository.defaultBranch)) | ||||
|   }) | ||||
|  | ||||
|   private def getFileInfo(git: Git, revision: String, pathStr: String, ignoreCase: Boolean): Option[FileInfo] = { | ||||
|     val (dirName, fileName) = pathStr.lastIndexOf('/') match { | ||||
|       case -1 => | ||||
|         (".", pathStr) | ||||
|       case n => | ||||
|         (pathStr.take(n), pathStr.drop(n + 1)) | ||||
|     } | ||||
|     if (ignoreCase) { | ||||
|       getFileList(git, revision, dirName, maxFiles = context.settings.repositoryViewer.maxFiles) | ||||
|         .find(_.name.toLowerCase.equals(fileName.toLowerCase)) | ||||
|     } else { | ||||
|       getFileList(git, revision, dirName, maxFiles = context.settings.repositoryViewer.maxFiles) | ||||
|         .find(_.name.equals(fileName)) | ||||
|   private def getFileInfo(git: Git, revision: String, pathStr: String): Option[FileInfo] = { | ||||
|     val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(revision)) | ||||
|     getPathObjectId(git, pathStr, revCommit).map { objectId => | ||||
|       FileInfo( | ||||
|         id = objectId, | ||||
|         isDirectory = false, | ||||
|         name = pathStr.split("/").last, | ||||
|         path = pathStr.split("/").dropRight(1).mkString("/"), | ||||
|         message = getSummaryMessage(revCommit.getFullMessage, revCommit.getShortMessage), | ||||
|         commitId = revCommit.getName, | ||||
|         time = revCommit.getAuthorIdent.getWhen, | ||||
|         author = revCommit.getAuthorIdent.getName, | ||||
|         mailAddress = revCommit.getAuthorIdent.getEmailAddress, | ||||
|         linkUrl = None | ||||
|       ) | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -74,16 +76,17 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase { | ||||
|     Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git => | ||||
|       val fileList = getFileList(git, refStr, path, maxFiles = context.settings.repositoryViewer.maxFiles) | ||||
|       if (fileList.isEmpty) { // file or NotFound | ||||
|         getFileInfo(git, refStr, path, ignoreCase) | ||||
|           .flatMap { f => | ||||
|         val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(refStr)) | ||||
|         getPathObjectId(git, path, revCommit) | ||||
|           .flatMap { objectId => | ||||
|             val largeFile = params.get("large_file").exists(s => s.equals("true")) | ||||
|             val content = getContentFromId(git, f.id, largeFile) | ||||
|             val content = getContentFromId(git, objectId, largeFile) | ||||
|             request.getHeader("Accept") match { | ||||
|               case "application/vnd.github.v3.raw" => { | ||||
|                 contentType = "application/vnd.github.v3.raw" | ||||
|                 content | ||||
|               } | ||||
|               case "application/vnd.github.v3.html" if isRenderable(f.name) => { | ||||
|               case "application/vnd.github.v3.html" if isRenderable(path) => { | ||||
|                 contentType = "application/vnd.github.v3.html" | ||||
|                 content.map { c => | ||||
|                   List( | ||||
| @@ -114,11 +117,12 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase { | ||||
|                 } | ||||
|               } | ||||
|               case _ => | ||||
|                 Some(JsonFormat(ApiContents(f, RepositoryName(repository), content))) | ||||
|                 getFileInfo(git, refStr, path).map { f => | ||||
|                   JsonFormat(ApiContents(f, RepositoryName(repository), content)) | ||||
|                 } | ||||
|             } | ||||
|           } | ||||
|           .getOrElse(NotFound()) | ||||
|  | ||||
|       } else { // directory | ||||
|         JsonFormat(fileList.map { f => | ||||
|           ApiContents(f, RepositoryName(repository), None) | ||||
| @@ -148,9 +152,7 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase { | ||||
|           val path = paths.take(paths.size - 1).toList.mkString("/") | ||||
|           Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { | ||||
|             git => | ||||
|               val fileInfo = getFileInfo(git, commit, path, false) | ||||
|  | ||||
|               fileInfo match { | ||||
|               getFileInfo(git, commit, path) match { | ||||
|                 case Some(f) if !data.sha.contains(f.id.getName) => | ||||
|                   ApiError( | ||||
|                     "The blob SHA is not matched.", | ||||
|   | ||||
| @@ -47,7 +47,7 @@ trait AccountService { | ||||
|           case _ => None | ||||
|         } | ||||
|       case account if (!account.isGroupAccount && account.password == sha1(password)) => Some(account) | ||||
|     } getOrElse None | ||||
|     }.flatten | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|   | ||||
| @@ -39,9 +39,7 @@ trait ActivityService { | ||||
|             if (isPublic == false) { | ||||
|               list += activity | ||||
|             } else { | ||||
|               if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName) | ||||
|                     .map(_.isPrivate) | ||||
|                     .getOrElse(true)) { | ||||
|               if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) { | ||||
|                 list += activity | ||||
|               } | ||||
|             } | ||||
| @@ -61,9 +59,7 @@ trait ActivityService { | ||||
|         var json: String = null | ||||
|         while (list.length < 50 && { json = reader.readLine(); json } != null) { | ||||
|           val activity = read[Activity](json) | ||||
|           if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName) | ||||
|                 .map(_.isPrivate) | ||||
|                 .getOrElse(true)) { | ||||
|           if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) { | ||||
|             list += activity | ||||
|           } | ||||
|         } | ||||
| @@ -83,9 +79,7 @@ trait ActivityService { | ||||
|           val activity = read[Activity](json) | ||||
|           if (owners.contains(activity.userName)) { | ||||
|             list += activity | ||||
|           } else if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName) | ||||
|                        .map(_.isPrivate) | ||||
|                        .getOrElse(true)) { | ||||
|           } else if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) { | ||||
|             list += activity | ||||
|           } | ||||
|         } | ||||
|   | ||||
| @@ -336,13 +336,16 @@ trait IssuesService { | ||||
|     implicit s: Session | ||||
|   ) = | ||||
|     Issues filter { t1 => | ||||
|       (if (repos.size == 1) { | ||||
|       (if (repos.sizeIs == 1) { | ||||
|          t1.byRepository(repos.head._1, repos.head._2) | ||||
|        } else { | ||||
|          ((t1.userName ++ "/" ++ t1.repositoryName) inSetBind (repos.map { case (owner, repo) => s"$owner/$repo" })) | ||||
|        }) && | ||||
|       (t1.closed === (condition.state == "closed").bind) | ||||
|         .&&(t1.milestoneId.? isEmpty, condition.milestone == Some(None)) | ||||
|       (condition.state match { | ||||
|         case "open"   => t1.closed === false | ||||
|         case "closed" => t1.closed === true | ||||
|         case _        => t1.closed === true || t1.closed === false | ||||
|       }).&&(t1.milestoneId.? isEmpty, condition.milestone == Some(None)) | ||||
|         .&&(t1.priorityId.? isEmpty, condition.priority == Some(None)) | ||||
|         .&&(t1.assignedUserName.? isEmpty, condition.assigned == Some(None)) | ||||
|         .&&(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) && | ||||
| @@ -939,7 +942,7 @@ object IssuesService { | ||||
|           case x      => Some(x) | ||||
|         }, | ||||
|         param(request, "mentioned"), | ||||
|         param(request, "state", Seq("open", "closed")).getOrElse("open"), | ||||
|         param(request, "state", Seq("open", "closed", "all")).getOrElse("open"), | ||||
|         param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"), | ||||
|         param(request, "direction", Seq("asc", "desc")).getOrElse("desc"), | ||||
|         param(request, "visibility"), | ||||
| @@ -960,7 +963,7 @@ object IssuesService { | ||||
|           case x      => Some(x) | ||||
|         }, | ||||
|         param(request, "mentioned"), | ||||
|         param(request, "state", Seq("open", "closed")).getOrElse("open"), | ||||
|         param(request, "state", Seq("open", "closed", "all")).getOrElse("open"), | ||||
|         param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"), | ||||
|         param(request, "direction", Seq("asc", "desc")).getOrElse("desc"), | ||||
|         param(request, "visibility"), | ||||
|   | ||||
| @@ -1,9 +1,11 @@ | ||||
| package gitbucket.core.service | ||||
|  | ||||
| import gitbucket.core.api.ApiMilestone | ||||
| import gitbucket.core.model.Milestone | ||||
| import gitbucket.core.model.Profile._ | ||||
| import gitbucket.core.model.Profile.profile.blockingApi._ | ||||
| import gitbucket.core.model.Profile.dateColumnType | ||||
| import gitbucket.core.service.RepositoryService.RepositoryInfo | ||||
|  | ||||
| trait MilestonesService { | ||||
|  | ||||
| @@ -73,4 +75,17 @@ trait MilestonesService { | ||||
|       .sortBy(t => (t.dueDate.asc, t.closedDate.desc, t.milestoneId.desc)) | ||||
|       .list | ||||
|  | ||||
|   def getApiMilestone(repository: RepositoryInfo, milestoneId: Int)(implicit s: Session): Option[ApiMilestone] = { | ||||
|     getMilestonesWithIssueCount(repository.owner, repository.name) | ||||
|       .find(p => p._1.milestoneId == milestoneId) | ||||
|       .map( | ||||
|         milestoneWithIssue => | ||||
|           ApiMilestone( | ||||
|             repository.repository, | ||||
|             milestoneWithIssue._1, | ||||
|             milestoneWithIssue._2, | ||||
|             milestoneWithIssue._3 | ||||
|         ) | ||||
|       ) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -71,7 +71,7 @@ object ProtectedBranchService { | ||||
|       pusher: String, | ||||
|       mergePullRequest: Boolean | ||||
|     )(implicit session: Session): Option[String] = { | ||||
|       if (mergePullRequest == true) { | ||||
|       if (mergePullRequest) { | ||||
|         None | ||||
|       } else { | ||||
|         checkBranchProtection(owner, repository, receivePack, command, pusher) | ||||
| @@ -153,8 +153,8 @@ object ProtectedBranchService { | ||||
|             Some("Cannot force-push to a protected branch") | ||||
|           case ReceiveCommand.Type.UPDATE | ReceiveCommand.Type.UPDATE_NONFASTFORWARD if needStatusCheck(pusher) => | ||||
|             unSuccessedContexts(command.getNewId.name) match { | ||||
|               case s if s.size == 1 => Some(s"""Required status check "${s.toSeq(0)}" is expected""") | ||||
|               case s if s.size >= 1 => Some(s"${s.size} of ${contexts.size} required status checks are expected") | ||||
|               case s if s.sizeIs == 1 => Some(s"""Required status check "${s.toSeq(0)}" is expected""") | ||||
|               case s if s.sizeIs >= 1 => Some(s"${s.size} of ${contexts.size} required status checks are expected") | ||||
|               case _                  => None | ||||
|             } | ||||
|           case ReceiveCommand.Type.DELETE => | ||||
|   | ||||
| @@ -509,10 +509,11 @@ trait PullRequestService { | ||||
|   def getPullRequestComments(userName: String, repositoryName: String, issueId: Int, commits: Seq[CommitInfo])( | ||||
|     implicit s: Session | ||||
|   ): Seq[Comment] = { | ||||
|     (commits | ||||
|       .map(commit => getCommitComments(userName, repositoryName, commit.id, true)) | ||||
|       .flatten ++ getComments(userName, repositoryName, issueId)) | ||||
|       .groupBy { | ||||
|     (commits.flatMap(commit => getCommitComments(userName, repositoryName, commit.id, true)) ++ getComments( | ||||
|       userName, | ||||
|       repositoryName, | ||||
|       issueId | ||||
|     )).groupBy { | ||||
|         case x: IssueComment                        => (Some(x.commentId), None, None, None) | ||||
|         case x: CommitComment if x.fileName.isEmpty => (Some(x.commentId), None, None, None) | ||||
|         case x: CommitComment                       => (None, x.fileName, x.originalOldLine, x.originalNewLine) | ||||
|   | ||||
| @@ -25,7 +25,7 @@ object RepositoryCreationService { | ||||
|   private val Creating = new ConcurrentHashMap[String, Option[String]]() | ||||
|  | ||||
|   def isCreating(owner: String, repository: String): Boolean = { | ||||
|     Option(Creating.get(s"${owner}/${repository}")).map(_.isEmpty).getOrElse(false) | ||||
|     Option(Creating.get(s"${owner}/${repository}")).exists(_.isEmpty) | ||||
|   } | ||||
|  | ||||
|   def startCreation(owner: String, repository: String): Unit = { | ||||
| @@ -40,7 +40,7 @@ object RepositoryCreationService { | ||||
|   } | ||||
|  | ||||
|   def getCreationError(owner: String, repository: String): Option[String] = { | ||||
|     Option(Creating.remove(s"${owner}/${repository}")).getOrElse(None) | ||||
|     Option(Creating.remove(s"${owner}/${repository}")).flatten | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -766,7 +766,8 @@ trait RepositoryService { | ||||
|           JGitUtil.getContentFromId(git, file.id, true).collect { | ||||
|             case bytes if FileUtil.isText(bytes) => StringUtil.convertFromByteArray(bytes) | ||||
|           } | ||||
|         } getOrElse None | ||||
|         } | ||||
|         .flatten | ||||
|     } getOrElse "" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -72,7 +72,7 @@ trait SystemSettingsService { | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     props.setProperty(SkinName, settings.skinName.toString) | ||||
|     props.setProperty(SkinName, settings.skinName) | ||||
|     settings.userDefinedCss.foreach(x => props.setProperty(UserDefinedCss, x)) | ||||
|     props.setProperty(ShowMailAddress, settings.showMailAddress.toString) | ||||
|     props.setProperty(WebHookBlockPrivateAddress, settings.webHook.blockPrivateAddress.toString) | ||||
|   | ||||
| @@ -35,6 +35,7 @@ import org.apache.http.HttpRequest | ||||
| import org.apache.http.HttpResponse | ||||
| import gitbucket.core.model.WebHookContentType | ||||
| import gitbucket.core.service.SystemSettingsService.SystemSettings | ||||
| import gitbucket.core.view.helpers.getApiMilestone | ||||
| import org.apache.http.client.entity.EntityBuilder | ||||
| import org.apache.http.entity.ContentType | ||||
|  | ||||
| @@ -394,7 +395,8 @@ trait WebHookPullRequestService extends WebHookService { | ||||
|             ApiUser(issueUser), | ||||
|             issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)), | ||||
|             getIssueLabels(repository.owner, repository.name, issue.issueId) | ||||
|               .map(ApiLabel(_, RepositoryName(repository))) | ||||
|               .map(ApiLabel(_, RepositoryName(repository))), | ||||
|             getApiMilestone(repository, issue.milestoneId getOrElse (0)) | ||||
|           ), | ||||
|           sender = ApiUser(sender) | ||||
|         ) | ||||
| @@ -576,6 +578,7 @@ trait WebHookIssueCommentService extends WebHookPullRequestService { | ||||
|         commenter <- users.get(issueComment.commentedUserName) | ||||
|         assignedUser = issue.assignedUserName.flatMap(users.get(_)) | ||||
|         labels = getIssueLabels(repository.owner, repository.name, issue.issueId) | ||||
|         milestone = getApiMilestone(repository, issue.milestoneId getOrElse (0)) | ||||
|       } yield { | ||||
|         WebHookIssueCommentPayload( | ||||
|           issue = issue, | ||||
| @@ -586,7 +589,8 @@ trait WebHookIssueCommentService extends WebHookPullRequestService { | ||||
|           repositoryUser = repoOwner, | ||||
|           assignedUser = assignedUser, | ||||
|           sender = sender, | ||||
|           labels = labels | ||||
|           labels = labels, | ||||
|           milestone = milestone | ||||
|         ) | ||||
|       } | ||||
|     } | ||||
| @@ -760,7 +764,8 @@ object WebHookService { | ||||
|       repositoryUser: Account, | ||||
|       assignedUser: Option[Account], | ||||
|       sender: Account, | ||||
|       labels: List[Label] | ||||
|       labels: List[Label], | ||||
|       milestone: Option[ApiMilestone] | ||||
|     ): WebHookIssueCommentPayload = | ||||
|       WebHookIssueCommentPayload( | ||||
|         action = "created", | ||||
| @@ -770,7 +775,8 @@ object WebHookService { | ||||
|           RepositoryName(repository), | ||||
|           ApiUser(issueUser), | ||||
|           assignedUser.map(ApiUser(_)), | ||||
|           labels.map(ApiLabel(_, RepositoryName(repository))) | ||||
|           labels.map(ApiLabel(_, RepositoryName(repository))), | ||||
|           milestone | ||||
|         ), | ||||
|         comment = | ||||
|           ApiComment(comment, RepositoryName(repository), issue.issueId, ApiUser(commentUser), issue.isPullRequest), | ||||
|   | ||||
| @@ -75,13 +75,15 @@ trait WikiService { | ||||
|   def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = { | ||||
|     Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git => | ||||
|       if (!JGitUtil.isEmpty(git)) { | ||||
|         JGitUtil.getFileList(git, "master", ".").find(_.name == pageName + ".md").map { file => | ||||
|         val fileName = pageName + ".md" | ||||
|         JGitUtil.getLatestCommitFromPath(git, fileName, "master").map { latestCommit => | ||||
|           val content = JGitUtil.getContentFromPath(git, latestCommit.getTree, fileName, true) | ||||
|           WikiPageInfo( | ||||
|             file.name, | ||||
|             StringUtil.convertFromByteArray(git.getRepository.open(file.id).getBytes), | ||||
|             file.author, | ||||
|             file.time, | ||||
|             file.commitId | ||||
|             fileName, | ||||
|             StringUtil.convertFromByteArray(content.getOrElse(Array.empty)), | ||||
|             latestCommit.getAuthorIdent.getName, | ||||
|             latestCommit.getAuthorIdent.getWhen, | ||||
|             latestCommit.getName | ||||
|           ) | ||||
|         } | ||||
|       } else None | ||||
| @@ -145,7 +147,7 @@ trait WikiService { | ||||
|           if (!p.getErrors.isEmpty) { | ||||
|             throw new PatchFormatException(p.getErrors()) | ||||
|           } | ||||
|           val revertInfo = (p.getFiles.asScala.map { fh => | ||||
|           val revertInfo = p.getFiles.asScala.flatMap { fh => | ||||
|             fh.getChangeType match { | ||||
|               case DiffEntry.ChangeType.MODIFY => { | ||||
|                 val source = | ||||
| @@ -174,7 +176,7 @@ trait WikiService { | ||||
|               } | ||||
|               case _ => Nil | ||||
|             } | ||||
|           }).flatten | ||||
|           } | ||||
|  | ||||
|           if (revertInfo.nonEmpty) { | ||||
|             val builder = DirCache.newInCore.builder() | ||||
| @@ -255,8 +257,7 @@ trait WikiService { | ||||
|               created = false | ||||
|               updated = JGitUtil | ||||
|                 .getContentFromId(git, tree.getEntryObjectId, true) | ||||
|                 .map(new String(_, "UTF-8") != content) | ||||
|                 .getOrElse(false) | ||||
|                 .exists(new String(_, "UTF-8") != content) | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|   | ||||
| @@ -139,7 +139,7 @@ class GitAuthenticationFilter extends Filter with RepositoryService with Account | ||||
|       case _ => | ||||
|         () => | ||||
|           { | ||||
|             logger.debug(s"Not enough path arguments: ${request.paths}") | ||||
|             logger.debug(s"Not enough path arguments: ${request.paths.mkString(", ")}") | ||||
|             response.sendError(HttpServletResponse.SC_NOT_FOUND) | ||||
|           } | ||||
|     } | ||||
|   | ||||
| @@ -347,9 +347,9 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: | ||||
|             // set PR as merged | ||||
|             val pulls = getPullRequestsByBranch(owner, repository, branchName, Some(false)) | ||||
|             pulls.foreach { pull => | ||||
|               if (commits.find { c => | ||||
|               if (commits.exists { c => | ||||
|                     c.id == pull.commitIdTo | ||||
|                   }.isDefined) { | ||||
|                   }) { | ||||
|                 markMergeAndClosePullRequest(pusher, owner, repository, pull) | ||||
|                 getAccountByUserName(pusher).foreach { pusherAccount => | ||||
|                   callPullRequestWebHook("closed", repositoryInfo, pull.issueId, pusherAccount, settings) | ||||
|   | ||||
| @@ -144,11 +144,9 @@ class DefaultGitUploadPack(owner: String, repoName: String) | ||||
|  | ||||
|   override protected def runTask(authType: AuthType): Unit = { | ||||
|     val execute = Database() withSession { implicit session => | ||||
|       getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")) | ||||
|         .map { repositoryInfo => | ||||
|       getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")).exists { repositoryInfo => | ||||
|         !repositoryInfo.repository.isPrivate || isReadableUser(authType, repositoryInfo) | ||||
|       } | ||||
|         .getOrElse(false) | ||||
|     } | ||||
|  | ||||
|     if (execute) { | ||||
| @@ -169,11 +167,9 @@ class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String, ss | ||||
|  | ||||
|   override protected def runTask(authType: AuthType): Unit = { | ||||
|     val execute = Database() withSession { implicit session => | ||||
|       getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")) | ||||
|         .map { repositoryInfo => | ||||
|       getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")).exists { repositoryInfo => | ||||
|         isWritableUser(authType, repositoryInfo) | ||||
|       } | ||||
|         .getOrElse(false) | ||||
|     } | ||||
|  | ||||
|     if (execute) { | ||||
|   | ||||
| @@ -38,7 +38,7 @@ trait OwnerAuthenticator { self: ControllerBase with RepositoryService with Acco | ||||
|         case Some(x) if (repository.owner == x.userName) => action(repository) | ||||
|         // TODO Repository management is allowed for only group managers? | ||||
|         case Some(x) if (getGroupMembers(repository.owner).exists { m => | ||||
|               m.userName == x.userName && m.isManager == true | ||||
|               m.userName == x.userName && m.isManager | ||||
|             }) => | ||||
|           action(repository) | ||||
|         case Some(x) if (getCollaboratorUserNames(userName, repoName, Seq(Role.ADMIN)).contains(x.userName)) => | ||||
|   | ||||
| @@ -52,7 +52,7 @@ object EditorConfigUtil { | ||||
|     } | ||||
|  | ||||
|     override def getParent: ResourcePath = { | ||||
|       Option(path.getParentPath).map { new JGitResourcePath(repo, revStr, _) }.getOrElse(null) | ||||
|       Option(path.getParentPath).map { new JGitResourcePath(repo, revStr, _) }.orNull | ||||
|     } | ||||
|  | ||||
|     override def openRandomReader(): Resource.RandomReader = { | ||||
| @@ -70,7 +70,7 @@ object EditorConfigUtil { | ||||
|   private class JGitResourcePath(repo: Repository, revStr: String, path: Ec4jPath) extends ResourcePath { | ||||
|  | ||||
|     override def getParent: ResourcePath = { | ||||
|       Option(path.getParentPath).map { new JGitResourcePath(repo, revStr, _) }.getOrElse(null) | ||||
|       Option(path.getParentPath).map { new JGitResourcePath(repo, revStr, _) }.orNull | ||||
|     } | ||||
|  | ||||
|     override def getPath: Ec4jPath = { | ||||
|   | ||||
| @@ -227,7 +227,7 @@ object JDBCUtil { | ||||
|         if (noPreds.isEmpty) { | ||||
|           if (hasPreds.isEmpty) done else sys.error(hasPreds.toString) | ||||
|         } else { | ||||
|           val found = noPreds.map { _._1 } | ||||
|           val found = noPreds.keys | ||||
|           tsort(hasPreds.map { case (k, v) => (k, v -- found) }, done ++ found) | ||||
|         } | ||||
|       } | ||||
|   | ||||
| @@ -37,9 +37,8 @@ object JGitUtil { | ||||
|  | ||||
|   private val logger = LoggerFactory.getLogger(JGitUtil.getClass) | ||||
|  | ||||
|   implicit val objectDatabaseReleasable: Releasable[ObjectDatabase] = new Releasable[ObjectDatabase] { | ||||
|     override def release(resource: ObjectDatabase): Unit = resource.close() | ||||
|   } | ||||
|   implicit val objectDatabaseReleasable: Releasable[ObjectDatabase] = | ||||
|     _.close() | ||||
|  | ||||
|   /** | ||||
|    * The repository data. | ||||
| @@ -383,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) | ||||
| @@ -513,7 +512,7 @@ object JGitUtil { | ||||
|   /** | ||||
|    * Returns the first line of the commit message. | ||||
|    */ | ||||
|   private def getSummaryMessage(fullMessage: String, shortMessage: String): String = { | ||||
|   def getSummaryMessage(fullMessage: String, shortMessage: String): String = { | ||||
|     val i = fullMessage.trim.indexOf('\n') | ||||
|     val firstLine = if (i >= 0) fullMessage.trim.substring(0, i).trim else fullMessage | ||||
|     if (firstLine.length > shortMessage.length) shortMessage else firstLine | ||||
| @@ -659,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 | ||||
|   } | ||||
|  | ||||
| @@ -672,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] = { | ||||
| @@ -1259,7 +1261,7 @@ object JGitUtil { | ||||
|         val blame = blamer.call() | ||||
|         var blameMap = Map[String, JGitUtil.BlameInfo]() | ||||
|         var idLine = List[(String, Int)]() | ||||
|         0.to(blame.getResultContents().size() - 1).map { i => | ||||
|         0.until(blame.getResultContents().size()).foreach { i => | ||||
|           val c = blame.getSourceCommit(i) | ||||
|           if (!blameMap.contains(c.name)) { | ||||
|             blameMap += c.name -> JGitUtil.BlameInfo( | ||||
|   | ||||
| @@ -41,7 +41,7 @@ class Mailer(settings: SystemSettings) { | ||||
|     htmlMsg: Option[String] = None, | ||||
|     loginAccount: Option[Account] = None | ||||
|   ): Option[HtmlEmail] = { | ||||
|     if (settings.notification == true) { | ||||
|     if (settings.notification) { | ||||
|       settings.smtp.map { smtp => | ||||
|         val email = new HtmlEmail | ||||
|         email.setHostName(smtp.host) | ||||
| @@ -51,7 +51,7 @@ class Mailer(settings: SystemSettings) { | ||||
|         } | ||||
|         smtp.ssl.foreach { ssl => | ||||
|           email.setSSLOnConnect(ssl) | ||||
|           if (ssl == true) { | ||||
|           if (ssl) { | ||||
|             email.setSslSmtpPort(smtp.port.get.toString) | ||||
|           } | ||||
|         } | ||||
|   | ||||
| @@ -184,8 +184,7 @@ object StringUtil { | ||||
|     def removeUserName(baseUrl: String): String = baseUrl.replaceFirst("(https?://).+@", "$1") | ||||
|  | ||||
|     gitRepositoryUrl match { | ||||
|       case GitBucketUrlPattern(base, user, repository) | ||||
|           if baseUrl.map(removeUserName(base).startsWith).getOrElse(false) => | ||||
|       case GitBucketUrlPattern(base, user, repository) if baseUrl.exists(removeUserName(base).startsWith) => | ||||
|         s"${removeUserName(base)}/$user/$repository" | ||||
|       case GitHubUrlPattern(_, user, repository)    => s"https://github.com/$user/$repository" | ||||
|       case BitBucketUrlPattern(_, user, repository) => s"https://bitbucket.org/$user/$repository" | ||||
|   | ||||
| @@ -45,11 +45,14 @@ trait AvatarImageProvider { self: RequestCache => | ||||
|  | ||||
|     if (tooltip) { | ||||
|       Html( | ||||
|         s"""<img src="${src}" class="${if (size > 20) { "avatar" } else { "avatar-mini" }}" style="width: ${size}px; height: ${size}px;" data-toggle="tooltip" title="${userName}" alt="@${userName}" />""" | ||||
|         s"""<img src="${src}" class="${if (size > 20) { "avatar" } else { "avatar-mini" }}" style="width: ${size}px; height: ${size}px;" | ||||
|            |     alt="@${StringUtil.escapeHtml(userName)}" | ||||
|            |     data-toggle="tooltip" title="${StringUtil.escapeHtml(userName)}" />""".stripMargin | ||||
|       ) | ||||
|     } else { | ||||
|       Html( | ||||
|         s"""<img src="${src}" class="${if (size > 20) { "avatar" } else { "avatar-mini" }}" style="width: ${size}px; height: ${size}px;" alt="@${userName}" />""" | ||||
|         s"""<img src="${src}" class="${if (size > 20) { "avatar" } else { "avatar-mini" }}" style="width: ${size}px; height: ${size}px;" | ||||
|            |     alt="@${StringUtil.escapeHtml(userName)}" />""".stripMargin | ||||
|       ) | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -336,7 +336,7 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache | ||||
|   )(implicit context: Context): Html = { | ||||
|  | ||||
|     val avatarHtml = avatar(userName, size, tooltip, mailAddress) | ||||
|     val contentHtml = if (label == true) Html(avatarHtml.body + " " + userName) else avatarHtml | ||||
|     val contentHtml = if (label) Html(avatarHtml.body + " " + userName) else avatarHtml | ||||
|  | ||||
|     userWithContent(userName, mailAddress)(contentHtml) | ||||
|   } | ||||
| @@ -446,14 +446,14 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache | ||||
|               } | ||||
|               result.append(c) | ||||
|             } | ||||
|             case '>' if tag == true => { | ||||
|             case '>' if tag => { | ||||
|               tag = false | ||||
|               result.append(c) | ||||
|             } | ||||
|             case _ if tag == false => { | ||||
|               text.append(c) | ||||
|             } | ||||
|             case _ if tag == true => { | ||||
|             case _ if tag => { | ||||
|               result.append(c) | ||||
|             } | ||||
|           } | ||||
|   | ||||
| @@ -1,18 +1,18 @@ | ||||
| @(account: gitbucket.core.model.Account, | ||||
|   personalTokens: List[gitbucket.core.model.AccessToken], | ||||
|   gneratedToken: Option[(gitbucket.core.model.AccessToken, String)])(implicit context: gitbucket.core.controller.Context) | ||||
|   generatedToken: Option[(gitbucket.core.model.AccessToken, String)])(implicit context: gitbucket.core.controller.Context) | ||||
| @gitbucket.core.html.main("Applications"){ | ||||
|   @gitbucket.core.account.html.menu("application", context.loginAccount.get.userName, false){ | ||||
|     <div class="panel panel-default"> | ||||
|       <div class="panel-heading strong">Personal access tokens</div> | ||||
|       <div class="panel-body"> | ||||
|         @if(personalTokens.isEmpty && gneratedToken.isEmpty){ | ||||
|         @if(personalTokens.isEmpty && generatedToken.isEmpty){ | ||||
|           No tokens. | ||||
|         } else { | ||||
|           Tokens you have generated which can be used to access the GitBucket API. | ||||
|           <hr style="margin-top: 10px;"> | ||||
|         } | ||||
|         @gneratedToken.map { case (token, tokenString) => | ||||
|         @generatedToken.map { case (token, tokenString) => | ||||
|           <div class="alert alert-info"> | ||||
|             Make sure to copy your new personal access token now. You won't be able to see it again! | ||||
|           </div> | ||||
|   | ||||
| @@ -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"> | ||||
|     <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> | ||||
|       } | ||||
|         </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> | ||||
|       @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 href="@helpers.url(repository)/wiki/_revert/@from...@to" class="btn">Revert Changes</a> | ||||
|           <a href="@helpers.url(repository)/wiki/_revert/@from...@to" class="btn btn-danger">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,17 +4,14 @@ | ||||
| @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"> | ||||
|     <div 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> | ||||
|     <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> | ||||
|       } | ||||
|   | ||||
| @@ -150,7 +150,7 @@ class ApiIntegrationTest extends AnyFunSuite { | ||||
|           .content("create") | ||||
|           .message("Create content") | ||||
|           .path("README.md") | ||||
|           .commit(); | ||||
|           .commit() | ||||
|  | ||||
|       assert(createResult.getContent.isFile == true) | ||||
|       assert(IOUtils.toString(createResult.getContent.read(), "UTF-8") == "create") | ||||
| @@ -168,7 +168,7 @@ class ApiIntegrationTest extends AnyFunSuite { | ||||
|           .message("Update content") | ||||
|           .path("README.md") | ||||
|           .sha(content1.getSha) | ||||
|           .commit(); | ||||
|           .commit() | ||||
|  | ||||
|       assert(updateResult.getContent.isFile == true) | ||||
|       assert(IOUtils.toString(updateResult.getContent.read(), "UTF-8") == "update") | ||||
|   | ||||
| @@ -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", | ||||
|   | ||||
| @@ -35,7 +35,8 @@ class AvatarImageProviderSpec extends AnyFunSpec { | ||||
|  | ||||
|       assert( | ||||
|         provider.toHtml("user", 32).toString == | ||||
|           """<img src="https://www.gravatar.com/avatar/d41d8cd98f00b204e9800998ecf8427e?s=32&d=retro&r=g" class="avatar" style="width: 32px; height: 32px;" alt="@user" />""" | ||||
|           """<img src="https://www.gravatar.com/avatar/d41d8cd98f00b204e9800998ecf8427e?s=32&d=retro&r=g" class="avatar" style="width: 32px; height: 32px;" | ||||
|             |     alt="@user" />""".stripMargin | ||||
|       ) | ||||
|     } | ||||
|  | ||||
| @@ -47,7 +48,8 @@ class AvatarImageProviderSpec extends AnyFunSpec { | ||||
|  | ||||
|       assert( | ||||
|         provider.toHtml("user", 32).toString == | ||||
|           s"""<img src="/user/_avatar?${date}" class="avatar" style="width: 32px; height: 32px;" alt="@user" />""" | ||||
|           s"""<img src="/user/_avatar?${date}" class="avatar" style="width: 32px; height: 32px;" | ||||
|              |     alt="@user" />""".stripMargin | ||||
|       ) | ||||
|     } | ||||
|  | ||||
| @@ -59,7 +61,8 @@ class AvatarImageProviderSpec extends AnyFunSpec { | ||||
|  | ||||
|       assert( | ||||
|         provider.toHtml("user", 32).toString == | ||||
|           s"""<img src="/user/_avatar?${date}" class="avatar" style="width: 32px; height: 32px;" alt="@user" />""" | ||||
|           s"""<img src="/user/_avatar?${date}" class="avatar" style="width: 32px; height: 32px;" | ||||
|              |     alt="@user" />""".stripMargin | ||||
|       ) | ||||
|     } | ||||
|  | ||||
| @@ -69,7 +72,8 @@ class AvatarImageProviderSpec extends AnyFunSpec { | ||||
|  | ||||
|       assert( | ||||
|         provider.toHtml("user", 20, "hoge@hoge.com").toString == | ||||
|           """<img src="https://www.gravatar.com/avatar/4712f9b0e63f56ad952ad387eaa23b9c?s=20&d=retro&r=g" class="avatar-mini" style="width: 20px; height: 20px;" alt="@user" />""" | ||||
|           """<img src="https://www.gravatar.com/avatar/4712f9b0e63f56ad952ad387eaa23b9c?s=20&d=retro&r=g" class="avatar-mini" style="width: 20px; height: 20px;" | ||||
|             |     alt="@user" />""".stripMargin | ||||
|       ) | ||||
|     } | ||||
|  | ||||
| @@ -79,7 +83,8 @@ class AvatarImageProviderSpec extends AnyFunSpec { | ||||
|  | ||||
|       assert( | ||||
|         provider.toHtml("user", 20).toString == | ||||
|           """<img src="/_unknown/_avatar" class="avatar-mini" style="width: 20px; height: 20px;" alt="@user" />""" | ||||
|           """<img src="/_unknown/_avatar" class="avatar-mini" style="width: 20px; height: 20px;" | ||||
|             |     alt="@user" />""".stripMargin | ||||
|       ) | ||||
|     } | ||||
|  | ||||
| @@ -89,7 +94,8 @@ class AvatarImageProviderSpec extends AnyFunSpec { | ||||
|  | ||||
|       assert( | ||||
|         provider.toHtml("user", 20, "hoge@hoge.com").toString == | ||||
|           """<img src="/_unknown/_avatar" class="avatar-mini" style="width: 20px; height: 20px;" alt="@user" />""" | ||||
|           """<img src="/_unknown/_avatar" class="avatar-mini" style="width: 20px; height: 20px;" | ||||
|             |     alt="@user" />""".stripMargin | ||||
|       ) | ||||
|     } | ||||
|  | ||||
| @@ -99,7 +105,27 @@ class AvatarImageProviderSpec extends AnyFunSpec { | ||||
|  | ||||
|       assert( | ||||
|         provider.toHtml("user", 20, "hoge@hoge.com", true).toString == | ||||
|           """<img src="/_unknown/_avatar" class="avatar-mini" style="width: 20px; height: 20px;" data-toggle="tooltip" title="user" alt="@user" />""" | ||||
|           """<img src="/_unknown/_avatar" class="avatar-mini" style="width: 20px; height: 20px;" | ||||
|             |     alt="@user" | ||||
|             |     data-toggle="tooltip" title="user" />""".stripMargin | ||||
|       ) | ||||
|     } | ||||
|  | ||||
|     it("should escape user name") { | ||||
|       implicit val context = Context(createSystemSettings(false), None, request) | ||||
|       val provider = new AvatarImageProviderImpl(None) | ||||
|  | ||||
|       assert( | ||||
|         provider.toHtml("""<user>"<name>""", 20, "hoge@hoge.com").toString == | ||||
|           """<img src="/_unknown/_avatar" class="avatar-mini" style="width: 20px; height: 20px;" | ||||
|             |     alt="@<user>"<name>" />""".stripMargin | ||||
|       ) | ||||
|  | ||||
|       assert( | ||||
|         provider.toHtml("""<user>"<name>""", 20, "hoge@hoge.com", true).toString == | ||||
|           """<img src="/_unknown/_avatar" class="avatar-mini" style="width: 20px; height: 20px;" | ||||
|             |     alt="@<user>"<name>" | ||||
|             |     data-toggle="tooltip" title="<user>"<name>" />""".stripMargin | ||||
|       ) | ||||
|     } | ||||
|   } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user