mirror of
				https://github.com/gitbucket/gitbucket.git
				synced 2025-10-31 10:36:05 +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 |     timeout-minutes: 30 | ||||||
|     strategy: |     strategy: | ||||||
|       matrix: |       matrix: | ||||||
|         java: [8, 11] |         java: [8, 11, 17] | ||||||
|     steps: |     steps: | ||||||
|     - uses: actions/checkout@v2 |     - uses: actions/checkout@v2 | ||||||
|     - name: Cache |     - name: Cache | ||||||
|   | |||||||
| @@ -1,9 +1,15 @@ | |||||||
| # Changelog | # Changelog | ||||||
| All changes to the project will be documented in this file. | 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 | ### 4.36.0 - 17 Jul 2021 | ||||||
| - Tag selector in the repository viewer | - 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 | - Files and lines can be linked in the diff view | ||||||
| - Option to disable XSS protection | - Option to disable XSS protection | ||||||
|  |  | ||||||
|   | |||||||
| @@ -63,9 +63,15 @@ Support | |||||||
|  |  | ||||||
| What's New in 4.36.x | 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 | ### 4.36.0 - 17 Jul 2021 | ||||||
| - Tag selector in the repository viewer | - 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 | - Files and lines can be linked in the diff view | ||||||
| - Option to disable XSS protection | - 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 Organization = "io.github.gitbucket" | ||||||
| val Name = "gitbucket" | val Name = "gitbucket" | ||||||
| val GitBucketVersion = "4.36.0" | val GitBucketVersion = "4.36.2" | ||||||
| val ScalatraVersion = "2.7.1" | val ScalatraVersion = "2.8.2" | ||||||
| val JettyVersion = "9.4.43.v20210629" | val JettyVersion = "9.4.44.v20210927" | ||||||
| val JgitVersion = "5.12.0.202106070339-r" | val JgitVersion = "5.13.0.202109080827-r" | ||||||
|  |  | ||||||
| lazy val root = (project in file(".")) | lazy val root = (project in file(".")) | ||||||
|   .enablePlugins(SbtTwirl, ScalatraPlugin) |   .enablePlugins(SbtTwirl, ScalatraPlugin) | ||||||
| @@ -15,7 +15,7 @@ sourcesInBase := false | |||||||
| organization := Organization | organization := Organization | ||||||
| name := Name | name := Name | ||||||
| version := GitBucketVersion | version := GitBucketVersion | ||||||
| scalaVersion := "2.13.6" | scalaVersion := "2.13.7" | ||||||
|  |  | ||||||
| scalafmtOnCompile := true | scalafmtOnCompile := true | ||||||
|  |  | ||||||
| @@ -34,7 +34,7 @@ libraryDependencies ++= Seq( | |||||||
|   "org.scalatra"                    %% "scalatra"                    % ScalatraVersion cross CrossVersion.for3Use2_13, |   "org.scalatra"                    %% "scalatra"                    % ScalatraVersion cross CrossVersion.for3Use2_13, | ||||||
|   "org.scalatra"                    %% "scalatra-json"               % ScalatraVersion cross CrossVersion.for3Use2_13, |   "org.scalatra"                    %% "scalatra-json"               % ScalatraVersion cross CrossVersion.for3Use2_13, | ||||||
|   "org.scalatra"                    %% "scalatra-forms"              % 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", |   "commons-io"                      % "commons-io"                   % "2.11.0", | ||||||
|   "io.github.gitbucket"             % "solidbase"                    % "1.0.3", |   "io.github.gitbucket"             % "solidbase"                    % "1.0.3", | ||||||
|   "io.github.gitbucket"             % "markedj"                      % "1.0.16", |   "io.github.gitbucket"             % "markedj"                      % "1.0.16", | ||||||
| @@ -43,33 +43,33 @@ libraryDependencies ++= Seq( | |||||||
|   "commons-net"                     % "commons-net"                  % "3.8.0", |   "commons-net"                     % "commons-net"                  % "3.8.0", | ||||||
|   "org.apache.httpcomponents"       % "httpclient"                   % "4.5.13", |   "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.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.github.takezoe"              %% "blocking-slick-32"           % "0.0.12" cross CrossVersion.for3Use2_13, | ||||||
|   "com.novell.ldap"                 % "jldap"                        % "2009-10-07", |   "com.novell.ldap"                 % "jldap"                        % "2009-10-07", | ||||||
|   "com.h2database"                  % "h2"                           % "1.4.199", |   "com.h2database"                  % "h2"                           % "1.4.199", | ||||||
|   "org.mariadb.jdbc"                % "mariadb-java-client"          % "2.7.3", |   "org.mariadb.jdbc"                % "mariadb-java-client"          % "2.7.4", | ||||||
|   "org.postgresql"                  % "postgresql"                   % "42.2.23", |   "org.postgresql"                  % "postgresql"                   % "42.3.1", | ||||||
|   "ch.qos.logback"                  % "logback-classic"              % "1.2.3", |   "ch.qos.logback"                  % "logback-classic"              % "1.2.6", | ||||||
|   "com.zaxxer"                      % "HikariCP"                     % "4.0.3", |   "com.zaxxer"                      % "HikariCP"                     % "4.0.3" exclude ("org.slf4j", "slf4j-api"), | ||||||
|   "com.typesafe"                    % "config"                       % "1.4.1", |   "com.typesafe"                    % "config"                       % "1.4.1", | ||||||
|   "fr.brouillard.oss.security.xhub" % "xhub4j-core"                  % "1.1.0", |   "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", |   "org.cache2k"                     % "cache2k-all"                  % "1.6.0.Final", | ||||||
|   "net.coobird"                     % "thumbnailator"                % "0.4.14", |   "net.coobird"                     % "thumbnailator"                % "0.4.14", | ||||||
|   "com.github.zafarkhaja"           % "java-semver"                  % "0.9.0", |   "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", |   "org.eclipse.jetty"               % "jetty-webapp"                 % JettyVersion % "provided", | ||||||
|   "javax.servlet"                   % "javax.servlet-api"            % "3.1.0" % "provided", |   "javax.servlet"                   % "javax.servlet-api"            % "3.1.0" % "provided", | ||||||
|   "junit"                           % "junit"                        % "4.13.2" % "test", |   "junit"                           % "junit"                        % "4.13.2" % "test", | ||||||
|   "org.scalatra"                    %% "scalatra-scalatest"          % ScalatraVersion % "test" cross CrossVersion.for3Use2_13, |   "org.scalatra"                    %% "scalatra-scalatest"          % ScalatraVersion % "test" cross CrossVersion.for3Use2_13, | ||||||
|   "org.mockito"                     % "mockito-core"                 % "3.11.2" % "test", |   "org.mockito"                     % "mockito-core"                 % "4.0.0" % "test", | ||||||
|   "com.dimafeng"                    %% "testcontainers-scala"        % "0.39.5" % "test", |   "com.dimafeng"                    %% "testcontainers-scala"        % "0.39.11" % "test", | ||||||
|   "org.testcontainers"              % "mysql"                        % "1.15.3" % "test", |   "org.testcontainers"              % "mysql"                        % "1.16.2" % "test", | ||||||
|   "org.testcontainers"              % "postgresql"                   % "1.15.3" % "test", |   "org.testcontainers"              % "postgresql"                   % "1.16.2" % "test", | ||||||
|   "net.i2p.crypto"                  % "eddsa"                        % "0.3.0", |   "net.i2p.crypto"                  % "eddsa"                        % "0.3.0", | ||||||
|   "is.tagomor.woothee"              % "woothee-java"                 % "1.11.0", |   "is.tagomor.woothee"              % "woothee-java"                 % "1.11.0", | ||||||
|   "org.ec4j.core"                   % "ec4j-core"                    % "0.3.0", |   "org.ec4j.core"                   % "ec4j-core"                    % "0.3.0", | ||||||
|   "org.kohsuke"                     % "github-api"                   % "1.131" % "test" |   "org.kohsuke"                     % "github-api"                   % "1.135" % "test" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| libraryDependencies ~= { | libraryDependencies ~= { | ||||||
| @@ -200,7 +200,7 @@ executableKey := { | |||||||
|   manifest.getMainAttributes put (AttrName.MANIFEST_VERSION, "1.0") |   manifest.getMainAttributes put (AttrName.MANIFEST_VERSION, "1.0") | ||||||
|   manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher") |   manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher") | ||||||
|   val outputFile = workDir / warName |   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 |   // generate checksums | ||||||
|   Seq( |   Seq( | ||||||
|   | |||||||
| @@ -2,9 +2,10 @@ scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature") | |||||||
|  |  | ||||||
| addSbtPlugin("org.scalameta"    % "sbt-scalafmt"       % "2.4.3") | addSbtPlugin("org.scalameta"    % "sbt-scalafmt"       % "2.4.3") | ||||||
| addSbtPlugin("com.typesafe.sbt" % "sbt-twirl"          % "1.5.1") | 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("org.scalatra.sbt" % "sbt-scalatra"       % "1.0.4") | ||||||
| addSbtPlugin("com.github.sbt"   % "sbt-pgp"            % "2.1.2") | addSbtPlugin("com.github.sbt"   % "sbt-pgp"            % "2.1.2") | ||||||
| addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0") | 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.9.2") | ||||||
| addSbtPlugin("org.scoverage"    % "sbt-scoverage"        % "1.8.2") |  | ||||||
|  | addDependencyTreePlugin | ||||||
|   | |||||||
| @@ -1,51 +1,100 @@ | |||||||
| import org.eclipse.jetty.server.ConnectionFactory; | import org.eclipse.jetty.http.HttpVersion; | ||||||
| import org.eclipse.jetty.server.Connector; |  | ||||||
| import org.eclipse.jetty.server.Handler; | import org.eclipse.jetty.server.Handler; | ||||||
|  | import org.eclipse.jetty.server.HttpConfiguration; | ||||||
| import org.eclipse.jetty.server.HttpConnectionFactory; | import org.eclipse.jetty.server.HttpConnectionFactory; | ||||||
|  | import org.eclipse.jetty.server.SecureRequestCustomizer; | ||||||
| import org.eclipse.jetty.server.Server; | 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.handler.StatisticsHandler; | ||||||
| import org.eclipse.jetty.server.session.DefaultSessionCache; | import org.eclipse.jetty.server.session.DefaultSessionCache; | ||||||
| import org.eclipse.jetty.server.session.FileSessionDataStore; | import org.eclipse.jetty.server.session.FileSessionDataStore; | ||||||
| import org.eclipse.jetty.server.session.SessionCache; | import org.eclipse.jetty.server.session.SessionCache; | ||||||
| import org.eclipse.jetty.server.session.SessionHandler; | import org.eclipse.jetty.server.session.SessionHandler; | ||||||
|  | import org.eclipse.jetty.util.ssl.SslContextFactory; | ||||||
| import org.eclipse.jetty.webapp.WebAppContext; | import org.eclipse.jetty.webapp.WebAppContext; | ||||||
|  |  | ||||||
| import java.io.File; | import java.io.File; | ||||||
|  | import java.net.InetAddress; | ||||||
| import java.net.URL; | import java.net.URL; | ||||||
| import java.net.InetSocketAddress; |  | ||||||
| import java.security.ProtectionDomain; | 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 { | 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 { |     public static void main(String[] args) throws Exception { | ||||||
|         System.setProperty("java.awt.headless", "true"); |         System.setProperty("java.awt.headless", "true"); | ||||||
|  |  | ||||||
|         String host = null; |         String connectors = getEnvironmentVariable("gitbucket.connectors"); | ||||||
|         String port = null; |         String host = getEnvironmentVariable("gitbucket.host"); | ||||||
|         InetSocketAddress address = null; |         String port = getEnvironmentVariable("gitbucket.port"); | ||||||
|         String contextPath = "/"; |         String securePort = getEnvironmentVariable("gitbucket.securePort"); | ||||||
|         String tmpDirPath=""; |         String keyStorePath = getEnvironmentVariable("gitbucket.keyStorePath"); | ||||||
|         boolean forceHttps = false; |         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; |         boolean saveSessions = false; | ||||||
|  |  | ||||||
|         host = getEnvironmentVariable("gitbucket.host"); |  | ||||||
|         port = getEnvironmentVariable("gitbucket.port"); |  | ||||||
|         contextPath = getEnvironmentVariable("gitbucket.prefix"); |  | ||||||
|         tmpDirPath = getEnvironmentVariable("gitbucket.tempDir"); |  | ||||||
|  |  | ||||||
|         for(String arg: args) { |         for(String arg: args) { | ||||||
|             if(arg.equals("--save_sessions")) { |             if(arg.equals("--save_sessions")) { | ||||||
|                 saveSessions = true; |                 saveSessions = true; | ||||||
|             } |             } | ||||||
|             if(arg.startsWith("--") && arg.contains("=")) { |             if(arg.startsWith("--") && arg.contains("=")) { | ||||||
|                 String[] dim = arg.split("="); |                 String[] dim = arg.split("=", 2); | ||||||
|                 if(dim.length >= 2) { |                 if(dim.length == 2) { | ||||||
|                     switch (dim[0]) { |                     switch (dim[0]) { | ||||||
|  |                         case "--connectors": | ||||||
|  |                             connectors = dim[1]; | ||||||
|  |                             break; | ||||||
|                         case "--host": |                         case "--host": | ||||||
|                             host = dim[1]; |                             host = dim[1]; | ||||||
|                             break; |                             break; | ||||||
|                         case "--port": |                         case "--port": | ||||||
|                             port = dim[1]; |                             port = dim[1]; | ||||||
|                             break; |                             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": |                         case "--prefix": | ||||||
|                             contextPath = dim[1]; |                             contextPath = dim[1]; | ||||||
|                             break; |                             break; | ||||||
| @@ -67,38 +116,69 @@ public class JettyLauncher { | |||||||
|             contextPath = "/" + contextPath; |             contextPath = "/" + contextPath; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if(host != null) { |         final String hostName = InetAddress.getByName(fallback(host, Defaults.HOST)).getHostName(); | ||||||
|             address = new InetSocketAddress(host, getPort(port)); |  | ||||||
|         } else { |         final Server server = new Server(); | ||||||
|             address = new InetSocketAddress(getPort(port)); |  | ||||||
|  |         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(); |             connectorInstances.add(connector); | ||||||
| //        if(host != null) { |         } | ||||||
| //            connector.setHost(host); |  | ||||||
| //        } |  | ||||||
| //        connector.setMaxIdleTime(1000 * 60 * 60); |  | ||||||
| //        connector.setSoLingerTime(-1); |  | ||||||
| //        connector.setPort(port); |  | ||||||
| //        server.addConnector(connector); |  | ||||||
|  |  | ||||||
|         // Disabling Server header |         if (connectorsSet.contains(Connectors.HTTPS)) { | ||||||
|         for (Connector connector : server.getConnectors()) { |             final SslContextFactory sslContextFactory = new SslContextFactory.Server(); | ||||||
|             for (ConnectionFactory factory : connector.getConnectionFactories()) { |  | ||||||
|                 if (factory instanceof HttpConnectionFactory) { |             sslContextFactory.setKeyStorePath(requireNonNull(keyStorePath, | ||||||
|                     ((HttpConnectionFactory) factory).getHttpConfiguration().setSendServerVersion(false); |                 "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(); |         WebAppContext context = new WebAppContext(); | ||||||
|  |  | ||||||
|         if(saveSessions) { |         if(saveSessions) { | ||||||
|             File sessDir = new File(getGitBucketHome(), "sessions"); |             File sessDir = new File(getGitBucketHome(), "sessions"); | ||||||
|             if(!sessDir.exists()){ |             if(!sessDir.exists()){ | ||||||
|                 sessDir.mkdirs(); |                 mkdir(sessDir); | ||||||
|             } |             } | ||||||
|             SessionHandler sessions = context.getSessionHandler(); |             SessionHandler sessions = context.getSessionHandler(); | ||||||
|             SessionCache cache = new DefaultSessionCache(sessions); |             SessionCache cache = new DefaultSessionCache(sessions); | ||||||
| @@ -112,7 +192,7 @@ public class JettyLauncher { | |||||||
|         if(tmpDirPath == null || tmpDirPath.equals("")){ |         if(tmpDirPath == null || tmpDirPath.equals("")){ | ||||||
|             tmpDir = new File(getGitBucketHome(), "tmp"); |             tmpDir = new File(getGitBucketHome(), "tmp"); | ||||||
|             if(!tmpDir.exists()){ |             if(!tmpDir.exists()){ | ||||||
|                 tmpDir.mkdirs(); |                 mkdir(tmpDir); | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             tmpDir = new File(tmpDirPath); |             tmpDir = new File(tmpDirPath); | ||||||
| @@ -136,13 +216,16 @@ public class JettyLauncher { | |||||||
|         context.setDescriptor(location.toExternalForm() + "/WEB-INF/web.xml"); |         context.setDescriptor(location.toExternalForm() + "/WEB-INF/web.xml"); | ||||||
|         context.setServer(server); |         context.setServer(server); | ||||||
|         context.setWar(location.toExternalForm()); |         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.setStopAtShutdown(true); | ||||||
|         server.setStopTimeout(7_000); |         server.setStopTimeout(7_000); | ||||||
|         server.start(); |         server.start(); | ||||||
| @@ -170,11 +253,28 @@ public class JettyLauncher { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static int getPort(String port){ |     private static <T, R> T fallback(R value, T defaultValue, Function<R, T> converter) { | ||||||
|         if(port == null) { |         return value == null ? defaultValue : converter.apply(value); | ||||||
|             return 8080; |     } | ||||||
|         } else { |  | ||||||
|             return Integer.parseInt(port); |     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) |     public static String apply(String source, String patch, FileHeader fh) | ||||||
|             throws IOException, PatchApplyException { |             throws IOException, PatchApplyException { | ||||||
|         RawText rt = new RawText(source.getBytes("UTF-8")); |         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++) |         for (int i = 0; i < rt.size(); i++) | ||||||
|             oldLines.add(rt.getString(i)); |             oldLines.add(rt.getString(i)); | ||||||
|         List<String> newLines = new ArrayList<String>(oldLines); |         List<String> newLines = new ArrayList<>(oldLines); | ||||||
|         for (HunkHeader hh : fh.getHunks()) { |         for (HunkHeader hh : fh.getHunks()) { | ||||||
|             ByteArrayOutputStream out = new ByteArrayOutputStream(); |             ByteArrayOutputStream out = new ByteArrayOutputStream(); | ||||||
|             out.write(patch.getBytes("UTF-8"), hh.getStartOffset(), hh.getEndOffset() - hh.getStartOffset()); |             out.write(patch.getBytes("UTF-8"), hh.getStartOffset(), hh.getEndOffset() - hh.getStartOffset()); | ||||||
|             RawText hrt = new RawText(out.toByteArray()); |             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++) |             for (int i = 0; i < hrt.size(); i++) | ||||||
|                 hunkLines.add(hrt.getString(i)); |                 hunkLines.add(hrt.getString(i)); | ||||||
|             int pos = 0; |             int pos = 0; | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| notifications:1.10.0 | notifications:1.10.0 | ||||||
| gist:4.20.0 | gist:4.21.0 | ||||||
| emoji:4.6.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.io.FileOutputStream | ||||||
| import java.nio.charset.StandardCharsets | import java.nio.charset.StandardCharsets | ||||||
| import java.sql.Connection | import java.sql.Connection | ||||||
| import java.util |  | ||||||
| import java.util.UUID | import java.util.UUID | ||||||
|  |  | ||||||
| import gitbucket.core.model.Activity | import gitbucket.core.model.Activity | ||||||
| @@ -84,7 +83,7 @@ object GitBucketCoreModule | |||||||
|       new Version( |       new Version( | ||||||
|         "4.34.0", |         "4.34.0", | ||||||
|         new Migration() { |         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) |             implicit val formats: Formats = Serialization.formats(NoTypeHints) | ||||||
|             import JDBCUtil._ |             import JDBCUtil._ | ||||||
|  |  | ||||||
| @@ -118,5 +117,7 @@ object GitBucketCoreModule | |||||||
|       new Version("4.35.1"), |       new Version("4.35.1"), | ||||||
|       new Version("4.35.2"), |       new Version("4.35.2"), | ||||||
|       new Version("4.35.3"), |       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, |   state: String, | ||||||
|   created_at: Date, |   created_at: Date, | ||||||
|   updated_at: Date, |   updated_at: Date, | ||||||
|   body: String |   body: String, | ||||||
|  |   milestone: Option[ApiMilestone] | ||||||
| )(repositoryName: RepositoryName, isPullRequest: Boolean) { | )(repositoryName: RepositoryName, isPullRequest: Boolean) { | ||||||
|   val id = 0 // dummy id |   val id = 0 // dummy id | ||||||
|   val assignees = List(assignee).flatten |   val assignees = List(assignee).flatten | ||||||
| @@ -43,7 +44,8 @@ object ApiIssue { | |||||||
|     repositoryName: RepositoryName, |     repositoryName: RepositoryName, | ||||||
|     user: ApiUser, |     user: ApiUser, | ||||||
|     assignee: Option[ApiUser], |     assignee: Option[ApiUser], | ||||||
|     labels: List[ApiLabel] |     labels: List[ApiLabel], | ||||||
|  |     milestone: Option[ApiMilestone] | ||||||
|   ): ApiIssue = |   ): ApiIssue = | ||||||
|     ApiIssue( |     ApiIssue( | ||||||
|       number = issue.issueId, |       number = issue.issueId, | ||||||
| @@ -51,6 +53,7 @@ object ApiIssue { | |||||||
|       user = user, |       user = user, | ||||||
|       assignee = assignee, |       assignee = assignee, | ||||||
|       labels = labels, |       labels = labels, | ||||||
|  |       milestone = milestone, | ||||||
|       state = if (issue.closed) { "closed" } else { "open" }, |       state = if (issue.closed) { "closed" } else { "open" }, | ||||||
|       body = issue.content.getOrElse(""), |       body = issue.content.getOrElse(""), | ||||||
|       created_at = issue.registeredDate, |       created_at = issue.registeredDate, | ||||||
|   | |||||||
| @@ -86,7 +86,7 @@ trait AccountControllerBase extends AccountManagementControllerBase { | |||||||
|  |  | ||||||
|   val newForm = mapping( |   val newForm = mapping( | ||||||
|     "userName" -> trim(label("User name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))), |     "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)))), |     "fullName" -> trim(label("Full Name", text(required, maxlength(100)))), | ||||||
|     "mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))), |     "mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))), | ||||||
|     "extraMailAddresses" -> list( |     "extraMailAddresses" -> list( | ||||||
| @@ -98,7 +98,7 @@ trait AccountControllerBase extends AccountManagementControllerBase { | |||||||
|   )(AccountNewForm.apply) |   )(AccountNewForm.apply) | ||||||
|  |  | ||||||
|   val editForm = mapping( |   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)))), |     "fullName" -> trim(label("Full Name", text(required, maxlength(100)))), | ||||||
|     "mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))), |     "mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))), | ||||||
|     "extraMailAddresses" -> list( |     "extraMailAddresses" -> list( | ||||||
| @@ -434,7 +434,7 @@ trait AccountControllerBase extends AccountManagementControllerBase { | |||||||
|  |  | ||||||
|   post("/:userName/_personalToken", personalTokenForm)(oneselfOnly { form => |   post("/:userName/_personalToken", personalTokenForm)(oneselfOnly { form => | ||||||
|     val userName = params("userName") |     val userName = params("userName") | ||||||
|     getAccountByUserName(userName).map { x => |     getAccountByUserName(userName).foreach { x => | ||||||
|       val (tokenId, token) = generateAccessToken(userName, form.note) |       val (tokenId, token) = generateAccessToken(userName, form.note) | ||||||
|       flash.update("generatedToken", (tokenId, token)) |       flash.update("generatedToken", (tokenId, token)) | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -32,6 +32,7 @@ trait PreProcessControllerBase extends ControllerBase { | |||||||
|   get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) { |   get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) { | ||||||
|     if (!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") && |     if (!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") && | ||||||
|         !context.currentPath.startsWith("/register") && !context.currentPath.endsWith("/info/refs") && |         !context.currentPath.startsWith("/register") && !context.currentPath.endsWith("/info/refs") && | ||||||
|  |         !context.currentPath.startsWith("/plugin-assets") && | ||||||
|         !PluginRegistry().getAnonymousAccessiblePaths().exists { path => |         !PluginRegistry().getAnonymousAccessiblePaths().exists { path => | ||||||
|           context.currentPath.startsWith(path) |           context.currentPath.startsWith(path) | ||||||
|         }) { |         }) { | ||||||
|   | |||||||
| @@ -242,25 +242,21 @@ trait PullRequestsControllerBase extends ControllerBase { | |||||||
|               branchIsOutOfDate = JGitUtil.getShaByRef(repository.owner, repository.name, pullreq.branch) != Some( |               branchIsOutOfDate = JGitUtil.getShaByRef(repository.owner, repository.name, pullreq.branch) != Some( | ||||||
|                 pullreq.commitIdFrom |                 pullreq.commitIdFrom | ||||||
|               ), |               ), | ||||||
|               needStatusCheck = context.loginAccount |               needStatusCheck = context.loginAccount.forall { u => | ||||||
|                 .map { u => |  | ||||||
|                 branchProtection.needStatusCheck(u.userName) |                 branchProtection.needStatusCheck(u.userName) | ||||||
|                 } |               }, | ||||||
|                 .getOrElse(true), |  | ||||||
|               hasUpdatePermission = hasDeveloperRole( |               hasUpdatePermission = hasDeveloperRole( | ||||||
|                 pullreq.requestUserName, |                 pullreq.requestUserName, | ||||||
|                 pullreq.requestRepositoryName, |                 pullreq.requestRepositoryName, | ||||||
|                 context.loginAccount |                 context.loginAccount | ||||||
|               ) && |               ) && | ||||||
|                 context.loginAccount |                 context.loginAccount.exists { u => | ||||||
|                   .map { u => |  | ||||||
|                   !getProtectedBranchInfo( |                   !getProtectedBranchInfo( | ||||||
|                     pullreq.requestUserName, |                     pullreq.requestUserName, | ||||||
|                     pullreq.requestRepositoryName, |                     pullreq.requestRepositoryName, | ||||||
|                     pullreq.requestBranch |                     pullreq.requestBranch | ||||||
|                   ).needStatusCheck(u.userName) |                   ).needStatusCheck(u.userName) | ||||||
|                   } |                 }, | ||||||
|                   .getOrElse(false), |  | ||||||
|               hasMergePermission = hasMergePermission, |               hasMergePermission = hasMergePermission, | ||||||
|               commitIdTo = pullreq.commitIdTo |               commitIdTo = pullreq.commitIdTo | ||||||
|             ) |             ) | ||||||
| @@ -494,8 +490,7 @@ trait PullRequestsControllerBase extends ControllerBase { | |||||||
|               (repository.userName, repository.repositoryName, repository.defaultBranch) |               (repository.userName, repository.repositoryName, repository.defaultBranch) | ||||||
|             }, |             }, | ||||||
|             commits.flatten |             commits.flatten | ||||||
|               .map(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false)) |               .flatMap(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false)) | ||||||
|               .flatten |  | ||||||
|               .toList, |               .toList, | ||||||
|             originId, |             originId, | ||||||
|             forkedId, |             forkedId, | ||||||
|   | |||||||
| @@ -685,7 +685,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { | |||||||
|         getPathObjectId(git, path, revCommit).map { |         getPathObjectId(git, path, revCommit).map { | ||||||
|           objectId => |           objectId => | ||||||
|             if (raw) { |             if (raw) { | ||||||
|               // Download (This route is left for backword compatibility) |               // Download (This route is left for backward compatibility) | ||||||
|               responseRawFile(git, objectId, path, repository) |               responseRawFile(git, objectId, path, repository) | ||||||
|             } else { |             } else { | ||||||
|               val info = EditorConfigUtil.getEditorConfigInfo(git, id, path) |               val info = EditorConfigUtil.getEditorConfigInfo(git, id, path) | ||||||
|   | |||||||
| @@ -186,7 +186,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase { | |||||||
|  |  | ||||||
|   val newUserForm = mapping( |   val newUserForm = mapping( | ||||||
|     "userName" -> trim(label("Username", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))), |     "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)))), |     "fullName" -> trim(label("Full Name", text(required, maxlength(100)))), | ||||||
|     "mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))), |     "mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))), | ||||||
|     "extraMailAddresses" -> list( |     "extraMailAddresses" -> list( | ||||||
| @@ -200,7 +200,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase { | |||||||
|  |  | ||||||
|   val editUserForm = mapping( |   val editUserForm = mapping( | ||||||
|     "userName" -> trim(label("Username", text(required, maxlength(100), identifier))), |     "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)))), |     "fullName" -> trim(label("Full Name", text(required, maxlength(100)))), | ||||||
|     "mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))), |     "mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))), | ||||||
|     "extraMailAddresses" -> list( |     "extraMailAddresses" -> list( | ||||||
| @@ -361,8 +361,8 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase { | |||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   get("/admin/users")(adminOnly { |   get("/admin/users")(adminOnly { | ||||||
|     val includeRemoved = params.get("includeRemoved").map(_.toBoolean).getOrElse(false) |     val includeRemoved = params.get("includeRemoved").exists(_.toBoolean) | ||||||
|     val includeGroups = params.get("includeGroups").map(_.toBoolean).getOrElse(false) |     val includeGroups = params.get("includeGroups").exists(_.toBoolean) | ||||||
|     val users = getAllUsers(includeRemoved, includeGroups) |     val users = getAllUsers(includeRemoved, includeGroups) | ||||||
|     val members = users.collect { |     val members = users.collect { | ||||||
|       case account if (account.isGroupAccount) => |       case account if (account.isGroupAccount) => | ||||||
|   | |||||||
| @@ -47,7 +47,8 @@ trait ApiIssueControllerBase extends ControllerBase { | |||||||
|           user = ApiUser(issueUser), |           user = ApiUser(issueUser), | ||||||
|           assignee = assignedUser.map(ApiUser(_)), |           assignee = assignedUser.map(ApiUser(_)), | ||||||
|           labels = getIssueLabels(repository.owner, repository.name, issue.issueId) |           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), |           RepositoryName(repository), | ||||||
|           ApiUser(openedUser), |           ApiUser(openedUser), | ||||||
|           issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)), |           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() |     }) getOrElse NotFound() | ||||||
| @@ -103,7 +105,8 @@ trait ApiIssueControllerBase extends ControllerBase { | |||||||
|             ApiUser(loginAccount), |             ApiUser(loginAccount), | ||||||
|             issue.assignedUserName.flatMap(getAccountByUserName(_)).map(ApiUser(_)), |             issue.assignedUserName.flatMap(getAccountByUserName(_)).map(ApiUser(_)), | ||||||
|             getIssueLabels(repository.owner, repository.name, issue.issueId) |             getIssueLabels(repository.owner, repository.name, issue.issueId) | ||||||
|               .map(ApiLabel(_, RepositoryName(repository))) |               .map(ApiLabel(_, RepositoryName(repository))), | ||||||
|  |             issue.milestoneId.flatMap { getApiMilestone(repository, _) } | ||||||
|           ) |           ) | ||||||
|         ) |         ) | ||||||
|       }) getOrElse NotFound() |       }) getOrElse NotFound() | ||||||
|   | |||||||
| @@ -121,7 +121,7 @@ trait ApiIssueLabelControllerBase extends ControllerBase { | |||||||
|    */ |    */ | ||||||
|   post("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository => |   post("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository => | ||||||
|     JsonFormat(for { |     JsonFormat(for { | ||||||
|       data <- extractFromJsonBody[Seq[String]]; |       data <- extractFromJsonBody[Seq[String]] | ||||||
|       issueId <- params("id").toIntOpt |       issueId <- params("id").toIntOpt | ||||||
|     } yield { |     } yield { | ||||||
|       data.map { labelName => |       data.map { labelName => | ||||||
| @@ -160,7 +160,7 @@ trait ApiIssueLabelControllerBase extends ControllerBase { | |||||||
|    */ |    */ | ||||||
|   put("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository => |   put("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository => | ||||||
|     JsonFormat(for { |     JsonFormat(for { | ||||||
|       data <- extractFromJsonBody[Seq[String]]; |       data <- extractFromJsonBody[Seq[String]] | ||||||
|       issueId <- params("id").toIntOpt |       issueId <- params("id").toIntOpt | ||||||
|     } yield { |     } yield { | ||||||
|       deleteAllIssueLabels(repository.owner, repository.name, issueId, true) |       deleteAllIssueLabels(repository.owner, repository.name, issueId, true) | ||||||
|   | |||||||
| @@ -102,17 +102,4 @@ trait ApiIssueMilestoneControllerBase extends ControllerBase { | |||||||
|     NoContent() |     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 |    * vi. Edit a release | ||||||
|    * https://developer.github.com/v3/repos/releases/#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 => |   patch("/api/v3/repos/:owner/:repository/releases/:tag")(writableUsersOnly { repository => | ||||||
|     (for { |     (for { | ||||||
| @@ -103,7 +103,7 @@ trait ApiReleaseControllerBase extends ControllerBase { | |||||||
|   /** |   /** | ||||||
|    * vii. Delete a release |    * vii. Delete a release | ||||||
|    * https://developer.github.com/v3/repos/releases/#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 => |   delete("/api/v3/repos/:owner/:repository/releases/:tag")(writableUsersOnly { repository => | ||||||
|     val tag = params("tag") |     val tag = params("tag") | ||||||
|   | |||||||
| @@ -40,7 +40,7 @@ trait ApiRepositoryCommitControllerBase extends ControllerBase { | |||||||
|                   diffs = JGitUtil.getDiffs(git, commitInfo.parents.headOption, commitInfo.id, false, true), |                   diffs = JGitUtil.getDiffs(git, commitInfo.parents.headOption, commitInfo.id, false, true), | ||||||
|                   author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress), |                   author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress), | ||||||
|                   committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress), |                   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.controller.ControllerBase | ||||||
| import gitbucket.core.service.{RepositoryCommitFileService, RepositoryService} | import gitbucket.core.service.{RepositoryCommitFileService, RepositoryService} | ||||||
| import gitbucket.core.util.Directory.getRepositoryDir | 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.util._ | ||||||
| import gitbucket.core.view.helpers.{isRenderable, renderMarkup} | import gitbucket.core.view.helpers.{isRenderable, renderMarkup} | ||||||
| import gitbucket.core.util.Implicits._ | import gitbucket.core.util.Implicits._ | ||||||
| @@ -49,19 +49,21 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase { | |||||||
|     getContents(repository, multiParams("splat").head, params.getOrElse("ref", repository.repository.defaultBranch)) |     getContents(repository, multiParams("splat").head, params.getOrElse("ref", repository.repository.defaultBranch)) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   private def getFileInfo(git: Git, revision: String, pathStr: String, ignoreCase: Boolean): Option[FileInfo] = { |   private def getFileInfo(git: Git, revision: String, pathStr: String): Option[FileInfo] = { | ||||||
|     val (dirName, fileName) = pathStr.lastIndexOf('/') match { |     val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(revision)) | ||||||
|       case -1 => |     getPathObjectId(git, pathStr, revCommit).map { objectId => | ||||||
|         (".", pathStr) |       FileInfo( | ||||||
|       case n => |         id = objectId, | ||||||
|         (pathStr.take(n), pathStr.drop(n + 1)) |         isDirectory = false, | ||||||
|     } |         name = pathStr.split("/").last, | ||||||
|     if (ignoreCase) { |         path = pathStr.split("/").dropRight(1).mkString("/"), | ||||||
|       getFileList(git, revision, dirName, maxFiles = context.settings.repositoryViewer.maxFiles) |         message = getSummaryMessage(revCommit.getFullMessage, revCommit.getShortMessage), | ||||||
|         .find(_.name.toLowerCase.equals(fileName.toLowerCase)) |         commitId = revCommit.getName, | ||||||
|     } else { |         time = revCommit.getAuthorIdent.getWhen, | ||||||
|       getFileList(git, revision, dirName, maxFiles = context.settings.repositoryViewer.maxFiles) |         author = revCommit.getAuthorIdent.getName, | ||||||
|         .find(_.name.equals(fileName)) |         mailAddress = revCommit.getAuthorIdent.getEmailAddress, | ||||||
|  |         linkUrl = None | ||||||
|  |       ) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -74,16 +76,17 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase { | |||||||
|     Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git => |     Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git => | ||||||
|       val fileList = getFileList(git, refStr, path, maxFiles = context.settings.repositoryViewer.maxFiles) |       val fileList = getFileList(git, refStr, path, maxFiles = context.settings.repositoryViewer.maxFiles) | ||||||
|       if (fileList.isEmpty) { // file or NotFound |       if (fileList.isEmpty) { // file or NotFound | ||||||
|         getFileInfo(git, refStr, path, ignoreCase) |         val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(refStr)) | ||||||
|           .flatMap { f => |         getPathObjectId(git, path, revCommit) | ||||||
|  |           .flatMap { objectId => | ||||||
|             val largeFile = params.get("large_file").exists(s => s.equals("true")) |             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 { |             request.getHeader("Accept") match { | ||||||
|               case "application/vnd.github.v3.raw" => { |               case "application/vnd.github.v3.raw" => { | ||||||
|                 contentType = "application/vnd.github.v3.raw" |                 contentType = "application/vnd.github.v3.raw" | ||||||
|                 content |                 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" |                 contentType = "application/vnd.github.v3.html" | ||||||
|                 content.map { c => |                 content.map { c => | ||||||
|                   List( |                   List( | ||||||
| @@ -114,11 +117,12 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase { | |||||||
|                 } |                 } | ||||||
|               } |               } | ||||||
|               case _ => |               case _ => | ||||||
|                 Some(JsonFormat(ApiContents(f, RepositoryName(repository), content))) |                 getFileInfo(git, refStr, path).map { f => | ||||||
|  |                   JsonFormat(ApiContents(f, RepositoryName(repository), content)) | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|           .getOrElse(NotFound()) |           .getOrElse(NotFound()) | ||||||
|  |  | ||||||
|       } else { // directory |       } else { // directory | ||||||
|         JsonFormat(fileList.map { f => |         JsonFormat(fileList.map { f => | ||||||
|           ApiContents(f, RepositoryName(repository), None) |           ApiContents(f, RepositoryName(repository), None) | ||||||
| @@ -148,9 +152,7 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase { | |||||||
|           val path = paths.take(paths.size - 1).toList.mkString("/") |           val path = paths.take(paths.size - 1).toList.mkString("/") | ||||||
|           Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { |           Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { | ||||||
|             git => |             git => | ||||||
|               val fileInfo = getFileInfo(git, commit, path, false) |               getFileInfo(git, commit, path) match { | ||||||
|  |  | ||||||
|               fileInfo match { |  | ||||||
|                 case Some(f) if !data.sha.contains(f.id.getName) => |                 case Some(f) if !data.sha.contains(f.id.getName) => | ||||||
|                   ApiError( |                   ApiError( | ||||||
|                     "The blob SHA is not matched.", |                     "The blob SHA is not matched.", | ||||||
|   | |||||||
| @@ -47,7 +47,7 @@ trait AccountService { | |||||||
|           case _ => None |           case _ => None | ||||||
|         } |         } | ||||||
|       case account if (!account.isGroupAccount && account.password == sha1(password)) => Some(account) |       case account if (!account.isGroupAccount && account.password == sha1(password)) => Some(account) | ||||||
|     } getOrElse None |     }.flatten | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|   | |||||||
| @@ -39,9 +39,7 @@ trait ActivityService { | |||||||
|             if (isPublic == false) { |             if (isPublic == false) { | ||||||
|               list += activity |               list += activity | ||||||
|             } else { |             } else { | ||||||
|               if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName) |               if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) { | ||||||
|                     .map(_.isPrivate) |  | ||||||
|                     .getOrElse(true)) { |  | ||||||
|                 list += activity |                 list += activity | ||||||
|               } |               } | ||||||
|             } |             } | ||||||
| @@ -61,9 +59,7 @@ trait ActivityService { | |||||||
|         var json: String = null |         var json: String = null | ||||||
|         while (list.length < 50 && { json = reader.readLine(); json } != null) { |         while (list.length < 50 && { json = reader.readLine(); json } != null) { | ||||||
|           val activity = read[Activity](json) |           val activity = read[Activity](json) | ||||||
|           if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName) |           if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) { | ||||||
|                 .map(_.isPrivate) |  | ||||||
|                 .getOrElse(true)) { |  | ||||||
|             list += activity |             list += activity | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
| @@ -83,9 +79,7 @@ trait ActivityService { | |||||||
|           val activity = read[Activity](json) |           val activity = read[Activity](json) | ||||||
|           if (owners.contains(activity.userName)) { |           if (owners.contains(activity.userName)) { | ||||||
|             list += activity |             list += activity | ||||||
|           } else if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName) |           } else if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) { | ||||||
|                        .map(_.isPrivate) |  | ||||||
|                        .getOrElse(true)) { |  | ||||||
|             list += activity |             list += activity | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -336,13 +336,16 @@ trait IssuesService { | |||||||
|     implicit s: Session |     implicit s: Session | ||||||
|   ) = |   ) = | ||||||
|     Issues filter { t1 => |     Issues filter { t1 => | ||||||
|       (if (repos.size == 1) { |       (if (repos.sizeIs == 1) { | ||||||
|          t1.byRepository(repos.head._1, repos.head._2) |          t1.byRepository(repos.head._1, repos.head._2) | ||||||
|        } else { |        } else { | ||||||
|          ((t1.userName ++ "/" ++ t1.repositoryName) inSetBind (repos.map { case (owner, repo) => s"$owner/$repo" })) |          ((t1.userName ++ "/" ++ t1.repositoryName) inSetBind (repos.map { case (owner, repo) => s"$owner/$repo" })) | ||||||
|        }) && |        }) && | ||||||
|       (t1.closed === (condition.state == "closed").bind) |       (condition.state match { | ||||||
|         .&&(t1.milestoneId.? isEmpty, condition.milestone == Some(None)) |         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.priorityId.? isEmpty, condition.priority == Some(None)) | ||||||
|         .&&(t1.assignedUserName.? isEmpty, condition.assigned == Some(None)) |         .&&(t1.assignedUserName.? isEmpty, condition.assigned == Some(None)) | ||||||
|         .&&(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) && |         .&&(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) && | ||||||
| @@ -939,7 +942,7 @@ object IssuesService { | |||||||
|           case x      => Some(x) |           case x      => Some(x) | ||||||
|         }, |         }, | ||||||
|         param(request, "mentioned"), |         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, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"), | ||||||
|         param(request, "direction", Seq("asc", "desc")).getOrElse("desc"), |         param(request, "direction", Seq("asc", "desc")).getOrElse("desc"), | ||||||
|         param(request, "visibility"), |         param(request, "visibility"), | ||||||
| @@ -960,7 +963,7 @@ object IssuesService { | |||||||
|           case x      => Some(x) |           case x      => Some(x) | ||||||
|         }, |         }, | ||||||
|         param(request, "mentioned"), |         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, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"), | ||||||
|         param(request, "direction", Seq("asc", "desc")).getOrElse("desc"), |         param(request, "direction", Seq("asc", "desc")).getOrElse("desc"), | ||||||
|         param(request, "visibility"), |         param(request, "visibility"), | ||||||
|   | |||||||
| @@ -1,9 +1,11 @@ | |||||||
| package gitbucket.core.service | package gitbucket.core.service | ||||||
|  |  | ||||||
|  | import gitbucket.core.api.ApiMilestone | ||||||
| import gitbucket.core.model.Milestone | import gitbucket.core.model.Milestone | ||||||
| import gitbucket.core.model.Profile._ | import gitbucket.core.model.Profile._ | ||||||
| import gitbucket.core.model.Profile.profile.blockingApi._ | import gitbucket.core.model.Profile.profile.blockingApi._ | ||||||
| import gitbucket.core.model.Profile.dateColumnType | import gitbucket.core.model.Profile.dateColumnType | ||||||
|  | import gitbucket.core.service.RepositoryService.RepositoryInfo | ||||||
|  |  | ||||||
| trait MilestonesService { | trait MilestonesService { | ||||||
|  |  | ||||||
| @@ -73,4 +75,17 @@ trait MilestonesService { | |||||||
|       .sortBy(t => (t.dueDate.asc, t.closedDate.desc, t.milestoneId.desc)) |       .sortBy(t => (t.dueDate.asc, t.closedDate.desc, t.milestoneId.desc)) | ||||||
|       .list |       .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, |       pusher: String, | ||||||
|       mergePullRequest: Boolean |       mergePullRequest: Boolean | ||||||
|     )(implicit session: Session): Option[String] = { |     )(implicit session: Session): Option[String] = { | ||||||
|       if (mergePullRequest == true) { |       if (mergePullRequest) { | ||||||
|         None |         None | ||||||
|       } else { |       } else { | ||||||
|         checkBranchProtection(owner, repository, receivePack, command, pusher) |         checkBranchProtection(owner, repository, receivePack, command, pusher) | ||||||
| @@ -153,8 +153,8 @@ object ProtectedBranchService { | |||||||
|             Some("Cannot force-push to a protected branch") |             Some("Cannot force-push to a protected branch") | ||||||
|           case ReceiveCommand.Type.UPDATE | ReceiveCommand.Type.UPDATE_NONFASTFORWARD if needStatusCheck(pusher) => |           case ReceiveCommand.Type.UPDATE | ReceiveCommand.Type.UPDATE_NONFASTFORWARD if needStatusCheck(pusher) => | ||||||
|             unSuccessedContexts(command.getNewId.name) match { |             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.sizeIs == 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"${s.size} of ${contexts.size} required status checks are expected") | ||||||
|               case _                  => None |               case _                  => None | ||||||
|             } |             } | ||||||
|           case ReceiveCommand.Type.DELETE => |           case ReceiveCommand.Type.DELETE => | ||||||
|   | |||||||
| @@ -509,10 +509,11 @@ trait PullRequestService { | |||||||
|   def getPullRequestComments(userName: String, repositoryName: String, issueId: Int, commits: Seq[CommitInfo])( |   def getPullRequestComments(userName: String, repositoryName: String, issueId: Int, commits: Seq[CommitInfo])( | ||||||
|     implicit s: Session |     implicit s: Session | ||||||
|   ): Seq[Comment] = { |   ): Seq[Comment] = { | ||||||
|     (commits |     (commits.flatMap(commit => getCommitComments(userName, repositoryName, commit.id, true)) ++ getComments( | ||||||
|       .map(commit => getCommitComments(userName, repositoryName, commit.id, true)) |       userName, | ||||||
|       .flatten ++ getComments(userName, repositoryName, issueId)) |       repositoryName, | ||||||
|       .groupBy { |       issueId | ||||||
|  |     )).groupBy { | ||||||
|         case x: IssueComment                        => (Some(x.commentId), None, None, None) |         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 if x.fileName.isEmpty => (Some(x.commentId), None, None, None) | ||||||
|         case x: CommitComment                       => (None, x.fileName, x.originalOldLine, x.originalNewLine) |         case x: CommitComment                       => (None, x.fileName, x.originalOldLine, x.originalNewLine) | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ object RepositoryCreationService { | |||||||
|   private val Creating = new ConcurrentHashMap[String, Option[String]]() |   private val Creating = new ConcurrentHashMap[String, Option[String]]() | ||||||
|  |  | ||||||
|   def isCreating(owner: String, repository: String): Boolean = { |   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 = { |   def startCreation(owner: String, repository: String): Unit = { | ||||||
| @@ -40,7 +40,7 @@ object RepositoryCreationService { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   def getCreationError(owner: String, repository: String): Option[String] = { |   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 { |           JGitUtil.getContentFromId(git, file.id, true).collect { | ||||||
|             case bytes if FileUtil.isText(bytes) => StringUtil.convertFromByteArray(bytes) |             case bytes if FileUtil.isText(bytes) => StringUtil.convertFromByteArray(bytes) | ||||||
|           } |           } | ||||||
|         } getOrElse None |         } | ||||||
|  |         .flatten | ||||||
|     } getOrElse "" |     } 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)) |     settings.userDefinedCss.foreach(x => props.setProperty(UserDefinedCss, x)) | ||||||
|     props.setProperty(ShowMailAddress, settings.showMailAddress.toString) |     props.setProperty(ShowMailAddress, settings.showMailAddress.toString) | ||||||
|     props.setProperty(WebHookBlockPrivateAddress, settings.webHook.blockPrivateAddress.toString) |     props.setProperty(WebHookBlockPrivateAddress, settings.webHook.blockPrivateAddress.toString) | ||||||
|   | |||||||
| @@ -35,6 +35,7 @@ import org.apache.http.HttpRequest | |||||||
| import org.apache.http.HttpResponse | import org.apache.http.HttpResponse | ||||||
| import gitbucket.core.model.WebHookContentType | import gitbucket.core.model.WebHookContentType | ||||||
| import gitbucket.core.service.SystemSettingsService.SystemSettings | import gitbucket.core.service.SystemSettingsService.SystemSettings | ||||||
|  | import gitbucket.core.view.helpers.getApiMilestone | ||||||
| import org.apache.http.client.entity.EntityBuilder | import org.apache.http.client.entity.EntityBuilder | ||||||
| import org.apache.http.entity.ContentType | import org.apache.http.entity.ContentType | ||||||
|  |  | ||||||
| @@ -394,7 +395,8 @@ trait WebHookPullRequestService extends WebHookService { | |||||||
|             ApiUser(issueUser), |             ApiUser(issueUser), | ||||||
|             issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)), |             issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)), | ||||||
|             getIssueLabels(repository.owner, repository.name, issue.issueId) |             getIssueLabels(repository.owner, repository.name, issue.issueId) | ||||||
|               .map(ApiLabel(_, RepositoryName(repository))) |               .map(ApiLabel(_, RepositoryName(repository))), | ||||||
|  |             getApiMilestone(repository, issue.milestoneId getOrElse (0)) | ||||||
|           ), |           ), | ||||||
|           sender = ApiUser(sender) |           sender = ApiUser(sender) | ||||||
|         ) |         ) | ||||||
| @@ -576,6 +578,7 @@ trait WebHookIssueCommentService extends WebHookPullRequestService { | |||||||
|         commenter <- users.get(issueComment.commentedUserName) |         commenter <- users.get(issueComment.commentedUserName) | ||||||
|         assignedUser = issue.assignedUserName.flatMap(users.get(_)) |         assignedUser = issue.assignedUserName.flatMap(users.get(_)) | ||||||
|         labels = getIssueLabels(repository.owner, repository.name, issue.issueId) |         labels = getIssueLabels(repository.owner, repository.name, issue.issueId) | ||||||
|  |         milestone = getApiMilestone(repository, issue.milestoneId getOrElse (0)) | ||||||
|       } yield { |       } yield { | ||||||
|         WebHookIssueCommentPayload( |         WebHookIssueCommentPayload( | ||||||
|           issue = issue, |           issue = issue, | ||||||
| @@ -586,7 +589,8 @@ trait WebHookIssueCommentService extends WebHookPullRequestService { | |||||||
|           repositoryUser = repoOwner, |           repositoryUser = repoOwner, | ||||||
|           assignedUser = assignedUser, |           assignedUser = assignedUser, | ||||||
|           sender = sender, |           sender = sender, | ||||||
|           labels = labels |           labels = labels, | ||||||
|  |           milestone = milestone | ||||||
|         ) |         ) | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| @@ -760,7 +764,8 @@ object WebHookService { | |||||||
|       repositoryUser: Account, |       repositoryUser: Account, | ||||||
|       assignedUser: Option[Account], |       assignedUser: Option[Account], | ||||||
|       sender: Account, |       sender: Account, | ||||||
|       labels: List[Label] |       labels: List[Label], | ||||||
|  |       milestone: Option[ApiMilestone] | ||||||
|     ): WebHookIssueCommentPayload = |     ): WebHookIssueCommentPayload = | ||||||
|       WebHookIssueCommentPayload( |       WebHookIssueCommentPayload( | ||||||
|         action = "created", |         action = "created", | ||||||
| @@ -770,7 +775,8 @@ object WebHookService { | |||||||
|           RepositoryName(repository), |           RepositoryName(repository), | ||||||
|           ApiUser(issueUser), |           ApiUser(issueUser), | ||||||
|           assignedUser.map(ApiUser(_)), |           assignedUser.map(ApiUser(_)), | ||||||
|           labels.map(ApiLabel(_, RepositoryName(repository))) |           labels.map(ApiLabel(_, RepositoryName(repository))), | ||||||
|  |           milestone | ||||||
|         ), |         ), | ||||||
|         comment = |         comment = | ||||||
|           ApiComment(comment, RepositoryName(repository), issue.issueId, ApiUser(commentUser), issue.isPullRequest), |           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] = { |   def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = { | ||||||
|     Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git => |     Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git => | ||||||
|       if (!JGitUtil.isEmpty(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( |           WikiPageInfo( | ||||||
|             file.name, |             fileName, | ||||||
|             StringUtil.convertFromByteArray(git.getRepository.open(file.id).getBytes), |             StringUtil.convertFromByteArray(content.getOrElse(Array.empty)), | ||||||
|             file.author, |             latestCommit.getAuthorIdent.getName, | ||||||
|             file.time, |             latestCommit.getAuthorIdent.getWhen, | ||||||
|             file.commitId |             latestCommit.getName | ||||||
|           ) |           ) | ||||||
|         } |         } | ||||||
|       } else None |       } else None | ||||||
| @@ -145,7 +147,7 @@ trait WikiService { | |||||||
|           if (!p.getErrors.isEmpty) { |           if (!p.getErrors.isEmpty) { | ||||||
|             throw new PatchFormatException(p.getErrors()) |             throw new PatchFormatException(p.getErrors()) | ||||||
|           } |           } | ||||||
|           val revertInfo = (p.getFiles.asScala.map { fh => |           val revertInfo = p.getFiles.asScala.flatMap { fh => | ||||||
|             fh.getChangeType match { |             fh.getChangeType match { | ||||||
|               case DiffEntry.ChangeType.MODIFY => { |               case DiffEntry.ChangeType.MODIFY => { | ||||||
|                 val source = |                 val source = | ||||||
| @@ -174,7 +176,7 @@ trait WikiService { | |||||||
|               } |               } | ||||||
|               case _ => Nil |               case _ => Nil | ||||||
|             } |             } | ||||||
|           }).flatten |           } | ||||||
|  |  | ||||||
|           if (revertInfo.nonEmpty) { |           if (revertInfo.nonEmpty) { | ||||||
|             val builder = DirCache.newInCore.builder() |             val builder = DirCache.newInCore.builder() | ||||||
| @@ -255,8 +257,7 @@ trait WikiService { | |||||||
|               created = false |               created = false | ||||||
|               updated = JGitUtil |               updated = JGitUtil | ||||||
|                 .getContentFromId(git, tree.getEntryObjectId, true) |                 .getContentFromId(git, tree.getEntryObjectId, true) | ||||||
|                 .map(new String(_, "UTF-8") != content) |                 .exists(new String(_, "UTF-8") != content) | ||||||
|                 .getOrElse(false) |  | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -139,7 +139,7 @@ class GitAuthenticationFilter extends Filter with RepositoryService with Account | |||||||
|       case _ => |       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) |             response.sendError(HttpServletResponse.SC_NOT_FOUND) | ||||||
|           } |           } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -347,9 +347,9 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: | |||||||
|             // set PR as merged |             // set PR as merged | ||||||
|             val pulls = getPullRequestsByBranch(owner, repository, branchName, Some(false)) |             val pulls = getPullRequestsByBranch(owner, repository, branchName, Some(false)) | ||||||
|             pulls.foreach { pull => |             pulls.foreach { pull => | ||||||
|               if (commits.find { c => |               if (commits.exists { c => | ||||||
|                     c.id == pull.commitIdTo |                     c.id == pull.commitIdTo | ||||||
|                   }.isDefined) { |                   }) { | ||||||
|                 markMergeAndClosePullRequest(pusher, owner, repository, pull) |                 markMergeAndClosePullRequest(pusher, owner, repository, pull) | ||||||
|                 getAccountByUserName(pusher).foreach { pusherAccount => |                 getAccountByUserName(pusher).foreach { pusherAccount => | ||||||
|                   callPullRequestWebHook("closed", repositoryInfo, pull.issueId, pusherAccount, settings) |                   callPullRequestWebHook("closed", repositoryInfo, pull.issueId, pusherAccount, settings) | ||||||
|   | |||||||
| @@ -144,11 +144,9 @@ class DefaultGitUploadPack(owner: String, repoName: String) | |||||||
|  |  | ||||||
|   override protected def runTask(authType: AuthType): Unit = { |   override protected def runTask(authType: AuthType): Unit = { | ||||||
|     val execute = Database() withSession { implicit session => |     val execute = Database() withSession { implicit session => | ||||||
|       getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")) |       getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")).exists { repositoryInfo => | ||||||
|         .map { repositoryInfo => |  | ||||||
|         !repositoryInfo.repository.isPrivate || isReadableUser(authType, repositoryInfo) |         !repositoryInfo.repository.isPrivate || isReadableUser(authType, repositoryInfo) | ||||||
|       } |       } | ||||||
|         .getOrElse(false) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (execute) { |     if (execute) { | ||||||
| @@ -169,11 +167,9 @@ class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String, ss | |||||||
|  |  | ||||||
|   override protected def runTask(authType: AuthType): Unit = { |   override protected def runTask(authType: AuthType): Unit = { | ||||||
|     val execute = Database() withSession { implicit session => |     val execute = Database() withSession { implicit session => | ||||||
|       getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")) |       getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")).exists { repositoryInfo => | ||||||
|         .map { repositoryInfo => |  | ||||||
|         isWritableUser(authType, repositoryInfo) |         isWritableUser(authType, repositoryInfo) | ||||||
|       } |       } | ||||||
|         .getOrElse(false) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (execute) { |     if (execute) { | ||||||
|   | |||||||
| @@ -38,7 +38,7 @@ trait OwnerAuthenticator { self: ControllerBase with RepositoryService with Acco | |||||||
|         case Some(x) if (repository.owner == x.userName) => action(repository) |         case Some(x) if (repository.owner == x.userName) => action(repository) | ||||||
|         // TODO Repository management is allowed for only group managers? |         // TODO Repository management is allowed for only group managers? | ||||||
|         case Some(x) if (getGroupMembers(repository.owner).exists { m => |         case Some(x) if (getGroupMembers(repository.owner).exists { m => | ||||||
|               m.userName == x.userName && m.isManager == true |               m.userName == x.userName && m.isManager | ||||||
|             }) => |             }) => | ||||||
|           action(repository) |           action(repository) | ||||||
|         case Some(x) if (getCollaboratorUserNames(userName, repoName, Seq(Role.ADMIN)).contains(x.userName)) => |         case Some(x) if (getCollaboratorUserNames(userName, repoName, Seq(Role.ADMIN)).contains(x.userName)) => | ||||||
|   | |||||||
| @@ -52,7 +52,7 @@ object EditorConfigUtil { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     override def getParent: 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 openRandomReader(): Resource.RandomReader = { |     override def openRandomReader(): Resource.RandomReader = { | ||||||
| @@ -70,7 +70,7 @@ object EditorConfigUtil { | |||||||
|   private class JGitResourcePath(repo: Repository, revStr: String, path: Ec4jPath) extends ResourcePath { |   private class JGitResourcePath(repo: Repository, revStr: String, path: Ec4jPath) extends ResourcePath { | ||||||
|  |  | ||||||
|     override def getParent: 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 = { |     override def getPath: Ec4jPath = { | ||||||
|   | |||||||
| @@ -227,7 +227,7 @@ object JDBCUtil { | |||||||
|         if (noPreds.isEmpty) { |         if (noPreds.isEmpty) { | ||||||
|           if (hasPreds.isEmpty) done else sys.error(hasPreds.toString) |           if (hasPreds.isEmpty) done else sys.error(hasPreds.toString) | ||||||
|         } else { |         } else { | ||||||
|           val found = noPreds.map { _._1 } |           val found = noPreds.keys | ||||||
|           tsort(hasPreds.map { case (k, v) => (k, v -- found) }, done ++ found) |           tsort(hasPreds.map { case (k, v) => (k, v -- found) }, done ++ found) | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|   | |||||||
| @@ -37,9 +37,8 @@ object JGitUtil { | |||||||
|  |  | ||||||
|   private val logger = LoggerFactory.getLogger(JGitUtil.getClass) |   private val logger = LoggerFactory.getLogger(JGitUtil.getClass) | ||||||
|  |  | ||||||
|   implicit val objectDatabaseReleasable: Releasable[ObjectDatabase] = new Releasable[ObjectDatabase] { |   implicit val objectDatabaseReleasable: Releasable[ObjectDatabase] = | ||||||
|     override def release(resource: ObjectDatabase): Unit = resource.close() |     _.close() | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * The repository data. |    * The repository data. | ||||||
| @@ -383,7 +382,7 @@ object JGitUtil { | |||||||
|     path: String = ".", |     path: String = ".", | ||||||
|     baseUrl: Option[String] = None, |     baseUrl: Option[String] = None, | ||||||
|     commitCount: Int = 0, |     commitCount: Int = 0, | ||||||
|     maxFiles: Int = 100 |     maxFiles: Int = 5 | ||||||
|   ): List[FileInfo] = { |   ): List[FileInfo] = { | ||||||
|     Using.resource(new RevWalk(git.getRepository)) { revWalk => |     Using.resource(new RevWalk(git.getRepository)) { revWalk => | ||||||
|       val objectId = git.getRepository.resolve(revision) |       val objectId = git.getRepository.resolve(revision) | ||||||
| @@ -513,7 +512,7 @@ object JGitUtil { | |||||||
|   /** |   /** | ||||||
|    * Returns the first line of the commit message. |    * 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 i = fullMessage.trim.indexOf('\n') | ||||||
|     val firstLine = if (i >= 0) fullMessage.trim.substring(0, i).trim else fullMessage |     val firstLine = if (i >= 0) fullMessage.trim.substring(0, i).trim else fullMessage | ||||||
|     if (firstLine.length > shortMessage.length) shortMessage else firstLine |     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] = { |   def getLatestCommitFromPaths(git: Git, paths: List[String], revision: String): Map[String, RevCommit] = { | ||||||
|     val start = getRevCommitFromId(git, git.getRepository.resolve(revision)) |     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 |       val commit = git.log.add(start).addPath(path).setMaxCount(1).call.iterator.next | ||||||
|       (path, commit) |       if (commit == null) { | ||||||
|  |         None | ||||||
|  |       } else { | ||||||
|  |         Some((path, commit)) | ||||||
|  |       } | ||||||
|     }.toMap |     }.toMap | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -672,11 +675,10 @@ object JGitUtil { | |||||||
|     df.setDiffComparator(RawTextComparator.DEFAULT) |     df.setDiffComparator(RawTextComparator.DEFAULT) | ||||||
|     df.setDetectRenames(true) |     df.setDetectRenames(true) | ||||||
|     getDiffEntries(git, from, to) |     getDiffEntries(git, from, to) | ||||||
|       .map { entry => |       .foreach { entry => | ||||||
|         df.format(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] = { |   private def getDiffEntries(git: Git, from: Option[String], to: String): Seq[DiffEntry] = { | ||||||
| @@ -1259,7 +1261,7 @@ object JGitUtil { | |||||||
|         val blame = blamer.call() |         val blame = blamer.call() | ||||||
|         var blameMap = Map[String, JGitUtil.BlameInfo]() |         var blameMap = Map[String, JGitUtil.BlameInfo]() | ||||||
|         var idLine = List[(String, Int)]() |         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) |           val c = blame.getSourceCommit(i) | ||||||
|           if (!blameMap.contains(c.name)) { |           if (!blameMap.contains(c.name)) { | ||||||
|             blameMap += c.name -> JGitUtil.BlameInfo( |             blameMap += c.name -> JGitUtil.BlameInfo( | ||||||
|   | |||||||
| @@ -41,7 +41,7 @@ class Mailer(settings: SystemSettings) { | |||||||
|     htmlMsg: Option[String] = None, |     htmlMsg: Option[String] = None, | ||||||
|     loginAccount: Option[Account] = None |     loginAccount: Option[Account] = None | ||||||
|   ): Option[HtmlEmail] = { |   ): Option[HtmlEmail] = { | ||||||
|     if (settings.notification == true) { |     if (settings.notification) { | ||||||
|       settings.smtp.map { smtp => |       settings.smtp.map { smtp => | ||||||
|         val email = new HtmlEmail |         val email = new HtmlEmail | ||||||
|         email.setHostName(smtp.host) |         email.setHostName(smtp.host) | ||||||
| @@ -51,7 +51,7 @@ class Mailer(settings: SystemSettings) { | |||||||
|         } |         } | ||||||
|         smtp.ssl.foreach { ssl => |         smtp.ssl.foreach { ssl => | ||||||
|           email.setSSLOnConnect(ssl) |           email.setSSLOnConnect(ssl) | ||||||
|           if (ssl == true) { |           if (ssl) { | ||||||
|             email.setSslSmtpPort(smtp.port.get.toString) |             email.setSslSmtpPort(smtp.port.get.toString) | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -184,8 +184,7 @@ object StringUtil { | |||||||
|     def removeUserName(baseUrl: String): String = baseUrl.replaceFirst("(https?://).+@", "$1") |     def removeUserName(baseUrl: String): String = baseUrl.replaceFirst("(https?://).+@", "$1") | ||||||
|  |  | ||||||
|     gitRepositoryUrl match { |     gitRepositoryUrl match { | ||||||
|       case GitBucketUrlPattern(base, user, repository) |       case GitBucketUrlPattern(base, user, repository) if baseUrl.exists(removeUserName(base).startsWith) => | ||||||
|           if baseUrl.map(removeUserName(base).startsWith).getOrElse(false) => |  | ||||||
|         s"${removeUserName(base)}/$user/$repository" |         s"${removeUserName(base)}/$user/$repository" | ||||||
|       case GitHubUrlPattern(_, user, repository)    => s"https://github.com/$user/$repository" |       case GitHubUrlPattern(_, user, repository)    => s"https://github.com/$user/$repository" | ||||||
|       case BitBucketUrlPattern(_, user, repository) => s"https://bitbucket.org/$user/$repository" |       case BitBucketUrlPattern(_, user, repository) => s"https://bitbucket.org/$user/$repository" | ||||||
|   | |||||||
| @@ -45,11 +45,14 @@ trait AvatarImageProvider { self: RequestCache => | |||||||
|  |  | ||||||
|     if (tooltip) { |     if (tooltip) { | ||||||
|       Html( |       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 { |     } else { | ||||||
|       Html( |       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 = { |   )(implicit context: Context): Html = { | ||||||
|  |  | ||||||
|     val avatarHtml = avatar(userName, size, tooltip, mailAddress) |     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) |     userWithContent(userName, mailAddress)(contentHtml) | ||||||
|   } |   } | ||||||
| @@ -446,14 +446,14 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache | |||||||
|               } |               } | ||||||
|               result.append(c) |               result.append(c) | ||||||
|             } |             } | ||||||
|             case '>' if tag == true => { |             case '>' if tag => { | ||||||
|               tag = false |               tag = false | ||||||
|               result.append(c) |               result.append(c) | ||||||
|             } |             } | ||||||
|             case _ if tag == false => { |             case _ if tag == false => { | ||||||
|               text.append(c) |               text.append(c) | ||||||
|             } |             } | ||||||
|             case _ if tag == true => { |             case _ if tag => { | ||||||
|               result.append(c) |               result.append(c) | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|   | |||||||
| @@ -1,18 +1,18 @@ | |||||||
| @(account: gitbucket.core.model.Account, | @(account: gitbucket.core.model.Account, | ||||||
|   personalTokens: List[gitbucket.core.model.AccessToken], |   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.html.main("Applications"){ | ||||||
|   @gitbucket.core.account.html.menu("application", context.loginAccount.get.userName, false){ |   @gitbucket.core.account.html.menu("application", context.loginAccount.get.userName, false){ | ||||||
|     <div class="panel panel-default"> |     <div class="panel panel-default"> | ||||||
|       <div class="panel-heading strong">Personal access tokens</div> |       <div class="panel-heading strong">Personal access tokens</div> | ||||||
|       <div class="panel-body"> |       <div class="panel-body"> | ||||||
|         @if(personalTokens.isEmpty && gneratedToken.isEmpty){ |         @if(personalTokens.isEmpty && generatedToken.isEmpty){ | ||||||
|           No tokens. |           No tokens. | ||||||
|         } else { |         } else { | ||||||
|           Tokens you have generated which can be used to access the GitBucket API. |           Tokens you have generated which can be used to access the GitBucket API. | ||||||
|           <hr style="margin-top: 10px;"> |           <hr style="margin-top: 10px;"> | ||||||
|         } |         } | ||||||
|         @gneratedToken.map { case (token, tokenString) => |         @generatedToken.map { case (token, tokenString) => | ||||||
|           <div class="alert alert-info"> |           <div class="alert alert-info"> | ||||||
|             Make sure to copy your new personal access token now. You won't be able to see it again! |             Make sure to copy your new personal access token now. You won't be able to see it again! | ||||||
|           </div> |           </div> | ||||||
|   | |||||||
| @@ -9,32 +9,22 @@ | |||||||
| @gitbucket.core.html.main(s"Compare Revisions - ${repository.owner}/${repository.name}", Some(repository)){ | @gitbucket.core.html.main(s"Compare Revisions - ${repository.owner}/${repository.name}", Some(repository)){ | ||||||
|   @gitbucket.core.helper.html.information(info) |   @gitbucket.core.helper.html.information(info) | ||||||
|   @gitbucket.core.html.menu("wiki", repository){ |   @gitbucket.core.html.menu("wiki", repository){ | ||||||
|     <ul class="nav nav-tabs fill-width"> |     <div class="pull-right"> | ||||||
|       <li> |  | ||||||
|         <h1 class="wiki-title"><span class="muted">Compare Revisions</span></h1> |  | ||||||
|       </li> |  | ||||||
|       <li class="pull-right"> |  | ||||||
|         <div class="btn-group"> |  | ||||||
|       @if(pageName.isDefined){ |       @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)">View Page</a> | ||||||
|         <a class="btn btn-small btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_history">Back to Page History</a> |         <a class="btn btn-small btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_history">Back to Page History</a> | ||||||
|       } else { |       } else { | ||||||
|         <a class="btn btn-small btn-default" href="@helpers.url(repository)/wiki/_history">Back to Wiki History</a> |         <a class="btn btn-small btn-default" href="@helpers.url(repository)/wiki/_history">Back to Wiki History</a> | ||||||
|       } |       } | ||||||
|         </div> |       @if(isEditable) { | ||||||
|       </li> |         @if(pageName.isDefined) { | ||||||
|     </ul> |           <a href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_revert/@from...@to" class="btn btn-danger">Revert Changes</a> | ||||||
|     <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 { |         } 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> |     </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> |         <a class="btn btn-danger" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_delete" id="delete">Delete Page</a> | ||||||
|       } |       } | ||||||
|     </div> |     </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"> |     <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> |       <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"/> |       <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> |       </div> | ||||||
|     } |     } | ||||||
|     <h1 class="wiki-title"> |     <h1 class="body-title"> | ||||||
|     @if(pageName.isEmpty){ |     @if(pageName.isEmpty){ | ||||||
|       <span class="muted">History</span> |       <span class="muted">History</span> | ||||||
|     } else { |     } else { | ||||||
|   | |||||||
| @@ -4,17 +4,14 @@ | |||||||
| @import gitbucket.core.view.helpers | @import gitbucket.core.view.helpers | ||||||
| @gitbucket.core.html.main(s"Pages - ${repository.owner}/${repository.name}", Some(repository)){ | @gitbucket.core.html.main(s"Pages - ${repository.owner}/${repository.name}", Some(repository)){ | ||||||
|   @gitbucket.core.html.menu("wiki", repository){ |   @gitbucket.core.html.menu("wiki", repository){ | ||||||
|     <ul class="nav nav-tabs fill-width"> |     <div class="pull-right"> | ||||||
|       <li> |  | ||||||
|         <h1 class="wiki-title"><span class="muted">Pages</span></h1> |  | ||||||
|       </li> |  | ||||||
|       <li class="pull-right"> |  | ||||||
|       @if(isEditable){ |       @if(isEditable){ | ||||||
|         <a class="btn btn-default" href="@helpers.url(repository)/wiki/_new">New Page</a> |         <a class="btn btn-default" href="@helpers.url(repository)/wiki/_new">New Page</a> | ||||||
|       } |       } | ||||||
|       </li> |     </div> | ||||||
|     </ul> |     <h1 class="body-title"><span class="muted">Pages</span></h1> | ||||||
|     <ul class="pull-left"> |     <hr> | ||||||
|  |     <ul> | ||||||
|       @pages.map { page => |       @pages.map { page => | ||||||
|         <li><a href="@helpers.url(repository)/wiki/@helpers.urlEncode(page)">@page</a></li> |         <li><a href="@helpers.url(repository)/wiki/@helpers.urlEncode(page)">@page</a></li> | ||||||
|       } |       } | ||||||
|   | |||||||
| @@ -150,7 +150,7 @@ class ApiIntegrationTest extends AnyFunSuite { | |||||||
|           .content("create") |           .content("create") | ||||||
|           .message("Create content") |           .message("Create content") | ||||||
|           .path("README.md") |           .path("README.md") | ||||||
|           .commit(); |           .commit() | ||||||
|  |  | ||||||
|       assert(createResult.getContent.isFile == true) |       assert(createResult.getContent.isFile == true) | ||||||
|       assert(IOUtils.toString(createResult.getContent.read(), "UTF-8") == "create") |       assert(IOUtils.toString(createResult.getContent.read(), "UTF-8") == "create") | ||||||
| @@ -168,7 +168,7 @@ class ApiIntegrationTest extends AnyFunSuite { | |||||||
|           .message("Update content") |           .message("Update content") | ||||||
|           .path("README.md") |           .path("README.md") | ||||||
|           .sha(content1.getSha) |           .sha(content1.getSha) | ||||||
|           .commit(); |           .commit() | ||||||
|  |  | ||||||
|       assert(updateResult.getContent.isFile == true) |       assert(updateResult.getContent.isFile == true) | ||||||
|       assert(IOUtils.toString(updateResult.getContent.read(), "UTF-8") == "update") |       assert(IOUtils.toString(updateResult.getContent.read(), "UTF-8") == "update") | ||||||
|   | |||||||
| @@ -177,6 +177,16 @@ object ApiSpecModels { | |||||||
|     updatedDate = date1 |     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 |   // APIs | ||||||
|  |  | ||||||
|   val apiUser = ApiUser(account) |   val apiUser = ApiUser(account) | ||||||
| @@ -193,12 +203,20 @@ object ApiSpecModels { | |||||||
|     repositoryName = repo1Name |     repositoryName = repo1Name | ||||||
|   ) |   ) | ||||||
|  |  | ||||||
|  |   val apiMilestone = ApiMilestone( | ||||||
|  |     repository = repository, | ||||||
|  |     milestone = milestone, | ||||||
|  |     open_issue_count = 1, | ||||||
|  |     closed_issue_count = 1 | ||||||
|  |   ) | ||||||
|  |  | ||||||
|   val apiIssue = ApiIssue( |   val apiIssue = ApiIssue( | ||||||
|     issue = issue, |     issue = issue, | ||||||
|     repositoryName = repo1Name, |     repositoryName = repo1Name, | ||||||
|     user = apiUser, |     user = apiUser, | ||||||
|     assignee = Some(apiUser), |     assignee = Some(apiUser), | ||||||
|     labels = List(apiLabel) |     labels = List(apiLabel), | ||||||
|  |     milestone = Some(apiMilestone) | ||||||
|   ) |   ) | ||||||
|  |  | ||||||
|   val apiNotAssignedIssue = ApiIssue( |   val apiNotAssignedIssue = ApiIssue( | ||||||
| @@ -206,7 +224,8 @@ object ApiSpecModels { | |||||||
|     repositoryName = repo1Name, |     repositoryName = repo1Name, | ||||||
|     user = apiUser, |     user = apiUser, | ||||||
|     assignee = None, |     assignee = None, | ||||||
|     labels = List(apiLabel) |     labels = List(apiLabel), | ||||||
|  |     milestone = Some(apiMilestone) | ||||||
|   ) |   ) | ||||||
|  |  | ||||||
|   val apiIssuePR = ApiIssue( |   val apiIssuePR = ApiIssue( | ||||||
| @@ -214,7 +233,8 @@ object ApiSpecModels { | |||||||
|     repositoryName = repo1Name, |     repositoryName = repo1Name, | ||||||
|     user = apiUser, |     user = apiUser, | ||||||
|     assignee = Some(apiUser), |     assignee = Some(apiUser), | ||||||
|     labels = List(apiLabel) |     labels = List(apiLabel), | ||||||
|  |     milestone = Some(apiMilestone) | ||||||
|   ) |   ) | ||||||
|  |  | ||||||
|   val apiComment = ApiComment( |   val apiComment = ApiComment( | ||||||
| @@ -471,6 +491,19 @@ object ApiSpecModels { | |||||||
|   val jsonLabel = |   val jsonLabel = | ||||||
|     """{"name":"bug","color":"f29513","url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/labels/bug"}""" |     """{"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"""{ |   val jsonIssue = s"""{ | ||||||
|        |"number":1347, |        |"number":1347, | ||||||
|        |"title":"Found a bug", |        |"title":"Found a bug", | ||||||
| @@ -481,6 +514,7 @@ object ApiSpecModels { | |||||||
|        |"created_at":"2011-04-14T16:00:49Z", |        |"created_at":"2011-04-14T16:00:49Z", | ||||||
|        |"updated_at":"2011-04-14T16:00:49Z", |        |"updated_at":"2011-04-14T16:00:49Z", | ||||||
|        |"body":"I'm having a problem with this.", |        |"body":"I'm having a problem with this.", | ||||||
|  |        |"milestone":$jsonMilestone, | ||||||
|        |"id":0, |        |"id":0, | ||||||
|        |"assignees":[$jsonUser], |        |"assignees":[$jsonUser], | ||||||
|        |"comments_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/issues/1347/comments", |        |"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", |        |"created_at":"2011-04-14T16:00:49Z", | ||||||
|        |"updated_at":"2011-04-14T16:00:49Z", |        |"updated_at":"2011-04-14T16:00:49Z", | ||||||
|        |"body":"I'm having a problem with this.", |        |"body":"I'm having a problem with this.", | ||||||
|  |        |"milestone":$jsonMilestone, | ||||||
|        |"id":0, |        |"id":0, | ||||||
|        |"assignees":[], |        |"assignees":[], | ||||||
|        |"comments_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/issues/1347/comments", |        |"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", |        |"created_at":"2011-04-14T16:00:49Z", | ||||||
|        |"updated_at":"2011-04-14T16:00:49Z", |        |"updated_at":"2011-04-14T16:00:49Z", | ||||||
|        |"body":"Please pull these awesome changes", |        |"body":"Please pull these awesome changes", | ||||||
|  |        |"milestone":$jsonMilestone, | ||||||
|        |"id":0, |        |"id":0, | ||||||
|        |"assignees":[$jsonUser], |        |"assignees":[$jsonUser], | ||||||
|        |"comments_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/issues/1347/comments", |        |"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, |       repositoryUser = account, | ||||||
|       assignedUser = Some(account), |       assignedUser = Some(account), | ||||||
|       sender = account, |       sender = account, | ||||||
|       labels = List(label) |       labels = List(label), | ||||||
|  |       milestone = Some(apiMilestone) | ||||||
|     ) |     ) | ||||||
|     val expected = s"""{ |     val expected = s"""{ | ||||||
|         |"action":"created", |         |"action":"created", | ||||||
|   | |||||||
| @@ -35,7 +35,8 @@ class AvatarImageProviderSpec extends AnyFunSpec { | |||||||
|  |  | ||||||
|       assert( |       assert( | ||||||
|         provider.toHtml("user", 32).toString == |         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( |       assert( | ||||||
|         provider.toHtml("user", 32).toString == |         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( |       assert( | ||||||
|         provider.toHtml("user", 32).toString == |         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( |       assert( | ||||||
|         provider.toHtml("user", 20, "hoge@hoge.com").toString == |         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( |       assert( | ||||||
|         provider.toHtml("user", 20).toString == |         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( |       assert( | ||||||
|         provider.toHtml("user", 20, "hoge@hoge.com").toString == |         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( |       assert( | ||||||
|         provider.toHtml("user", 20, "hoge@hoge.com", true).toString == |         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