mirror of
				https://github.com/gitbucket/gitbucket.git
				synced 2025-10-31 10:36:05 +01:00 
			
		
		
		
	Compare commits
	
		
			26 Commits
		
	
	
		
			optimize-c
			...
			4.37.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | ff8a5f6b77 | ||
|  | ec953df156 | ||
|  | d6a191d95b | ||
|  | aba428bba1 | ||
|  | 6ab37fd596 | ||
|  | 73fc70f55b | ||
|  | aad18b7a50 | ||
|  | cc278be5cd | ||
|  | d0f4f82a0f | ||
|  | 1dcbf386b1 | ||
|  | 414afd285c | ||
|  | 35b645d8b5 | ||
|  | b3cba53866 | ||
|  | a4773bb3ca | ||
|  | 863d8a4af5 | ||
|  | 3fccd7b53c | ||
|  | dd2760eaf7 | ||
|  | 824bafa739 | ||
|  | 60cdaec05f | ||
|  | c204a435b3 | ||
|  | 37accd92d6 | ||
|  | 01fd0ee1f0 | ||
|  | fab1c74473 | ||
|  | 0d8fcfd28d | ||
|  | b91a7c32a6 | ||
|  | e7a6f0930b | 
							
								
								
									
										2
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -12,7 +12,7 @@ jobs: | ||||
|     steps: | ||||
|     - uses: actions/checkout@v2 | ||||
|     - name: Cache | ||||
|       uses: actions/cache@v2.1.6 | ||||
|       uses: actions/cache@v2.1.7 | ||||
|       env: | ||||
|         cache-name: cache-sbt-libs | ||||
|       with: | ||||
|   | ||||
| @@ -4,4 +4,5 @@ updates.includeScala = true | ||||
|  | ||||
| updates.pin = [ | ||||
|   { groupId = "org.eclipse.jetty", version = "9." } | ||||
|   { groupId = "org.eclipse.jgit", version = "5." } | ||||
| ] | ||||
|   | ||||
| @@ -1,6 +1,15 @@ | ||||
| # Changelog | ||||
| All changes to the project will be documented in this file. | ||||
|  | ||||
| ### 4.37.0 - 11 Dec 2021 | ||||
| - Enhance Git Reference APIs | ||||
| - Add milestone data to issue list API | ||||
| - Support "all" in issue list API | ||||
| - Support EDDSA in signed commit verification | ||||
| - Support custom SSH url | ||||
| - Relax max passward length limitation | ||||
| - Relax max webhook url length limitation | ||||
|  | ||||
| ### 4.36.2 - 16 Aug 2021 | ||||
| - Escape user name in avatar image tag | ||||
|  | ||||
|   | ||||
							
								
								
									
										21
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								README.md
									
									
									
									
									
								
							| @@ -61,18 +61,15 @@ Support | ||||
| - If you can't find same question and report, send it to our [Gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue. | ||||
| - The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles. | ||||
|  | ||||
| What's New in 4.36.x | ||||
| What's New in 4.37.x | ||||
| ------------- | ||||
| ### 4.36.2 - 16 Aug 2021 | ||||
| - Escape user name in avatar image tag | ||||
|  | ||||
| ### 4.36.1 - 22 Jul 2021 | ||||
| - Bump gitbucket-gist-plugin to 4.21.0 | ||||
|  | ||||
| ### 4.36.0 - 17 Jul 2021 | ||||
| - Tag selector in the repository viewer | ||||
| - Link issues/pull requests of other repositories | ||||
| - Files and lines can be linked in the diff view | ||||
| - Option to disable XSS protection | ||||
| ### 4.37.0 - 11 Dec 2021 | ||||
| - Enhance Git Reference APIs | ||||
| - Add milestone data to issue list API | ||||
| - Support "all" in issue list API | ||||
| - Support EDDSA in signed commit verification | ||||
| - Support custom SSH url | ||||
| - Relax max passward length limitation | ||||
| - Relax max webhook url length limitation | ||||
|  | ||||
| See the [change log](CHANGELOG.md) for all of the updates. | ||||
|   | ||||
							
								
								
									
										16
									
								
								build.sbt
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								build.sbt
									
									
									
									
									
								
							| @@ -3,7 +3,7 @@ import com.jsuereth.sbtpgp.PgpKeys._ | ||||
|  | ||||
| val Organization = "io.github.gitbucket" | ||||
| val Name = "gitbucket" | ||||
| val GitBucketVersion = "4.36.2" | ||||
| val GitBucketVersion = "4.37.0" | ||||
| val ScalatraVersion = "2.8.2" | ||||
| val JettyVersion = "9.4.44.v20210927" | ||||
| val JgitVersion = "5.13.0.202109080827-r" | ||||
| @@ -42,34 +42,34 @@ libraryDependencies ++= Seq( | ||||
|   "org.apache.commons"              % "commons-email"                % "1.5", | ||||
|   "commons-net"                     % "commons-net"                  % "3.8.0", | ||||
|   "org.apache.httpcomponents"       % "httpclient"                   % "4.5.13", | ||||
|   "org.apache.sshd"                 % "apache-sshd"                  % "2.1.0" exclude ("org.slf4j", "slf4j-jdk14") exclude ("org.apache.sshd", "sshd-mina") exclude ("org.apache.sshd", "sshd-netty"), | ||||
|   "org.apache.sshd"                 % "apache-sshd"                  % "2.8.0" exclude ("org.slf4j", "slf4j-jdk14") exclude ("org.apache.sshd", "sshd-mina") exclude ("org.apache.sshd", "sshd-netty"), | ||||
|   "org.apache.tika"                 % "tika-core"                    % "2.1.0", | ||||
|   "com.github.takezoe"              %% "blocking-slick-32"           % "0.0.12" cross CrossVersion.for3Use2_13, | ||||
|   "com.novell.ldap"                 % "jldap"                        % "2009-10-07", | ||||
|   "com.h2database"                  % "h2"                           % "1.4.199", | ||||
|   "org.mariadb.jdbc"                % "mariadb-java-client"          % "2.7.4", | ||||
|   "org.postgresql"                  % "postgresql"                   % "42.3.1", | ||||
|   "ch.qos.logback"                  % "logback-classic"              % "1.2.6", | ||||
|   "ch.qos.logback"                  % "logback-classic"              % "1.2.7", | ||||
|   "com.zaxxer"                      % "HikariCP"                     % "4.0.3" exclude ("org.slf4j", "slf4j-api"), | ||||
|   "com.typesafe"                    % "config"                       % "1.4.1", | ||||
|   "fr.brouillard.oss.security.xhub" % "xhub4j-core"                  % "1.1.0", | ||||
|   "io.github.java-diff-utils"       % "java-diff-utils"              % "4.11", | ||||
|   "org.cache2k"                     % "cache2k-all"                  % "1.6.0.Final", | ||||
|   "net.coobird"                     % "thumbnailator"                % "0.4.14", | ||||
|   "net.coobird"                     % "thumbnailator"                % "0.4.15", | ||||
|   "com.github.zafarkhaja"           % "java-semver"                  % "0.9.0", | ||||
|   "com.nimbusds"                    % "oauth2-oidc-sdk"              % "9.19", | ||||
|   "com.nimbusds"                    % "oauth2-oidc-sdk"              % "9.20", | ||||
|   "org.eclipse.jetty"               % "jetty-webapp"                 % JettyVersion % "provided", | ||||
|   "javax.servlet"                   % "javax.servlet-api"            % "3.1.0" % "provided", | ||||
|   "junit"                           % "junit"                        % "4.13.2" % "test", | ||||
|   "org.scalatra"                    %% "scalatra-scalatest"          % ScalatraVersion % "test" cross CrossVersion.for3Use2_13, | ||||
|   "org.mockito"                     % "mockito-core"                 % "4.0.0" % "test", | ||||
|   "com.dimafeng"                    %% "testcontainers-scala"        % "0.39.11" % "test", | ||||
|   "org.mockito"                     % "mockito-core"                 % "4.1.0" % "test", | ||||
|   "com.dimafeng"                    %% "testcontainers-scala"        % "0.39.12" % "test", | ||||
|   "org.testcontainers"              % "mysql"                        % "1.16.2" % "test", | ||||
|   "org.testcontainers"              % "postgresql"                   % "1.16.2" % "test", | ||||
|   "net.i2p.crypto"                  % "eddsa"                        % "0.3.0", | ||||
|   "is.tagomor.woothee"              % "woothee-java"                 % "1.11.0", | ||||
|   "org.ec4j.core"                   % "ec4j-core"                    % "0.3.0", | ||||
|   "org.kohsuke"                     % "github-api"                   % "1.135" % "test" | ||||
|   "org.kohsuke"                     % "github-api"                   % "1.301" % "test" | ||||
| ) | ||||
|  | ||||
| libraryDependencies ~= { | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| sbt.version=1.5.5 | ||||
| sbt.version=1.5.6 | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature") | ||||
|  | ||||
| addSbtPlugin("org.scalameta"    % "sbt-scalafmt"       % "2.4.3") | ||||
| addSbtPlugin("org.scalameta"    % "sbt-scalafmt"       % "2.4.5") | ||||
| addSbtPlugin("com.typesafe.sbt" % "sbt-twirl"          % "1.5.1") | ||||
| addSbtPlugin("com.eed3si9n"     % "sbt-assembly"       % "1.1.0") | ||||
| addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra"       % "1.0.4") | ||||
|   | ||||
							
								
								
									
										7
									
								
								src/main/resources/update/gitbucket-core_4.37.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/main/resources/update/gitbucket-core_4.37.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <changeSet> | ||||
|   <dropForeignKeyConstraint constraintName="IDX_WEB_HOOK_EVENT_FK0" baseTableName="WEB_HOOK_EVENT"/> | ||||
|   <modifyDataType columnName="URL" newDataType="varchar(400)" tableName="WEB_HOOK_EVENT"/> | ||||
|   <modifyDataType columnName="URL" newDataType="varchar(400)" tableName="WEB_HOOK"/> | ||||
|   <addForeignKeyConstraint constraintName="IDX_WEB_HOOK_EVENT_FK0" baseTableName="WEB_HOOK_EVENT" baseColumnNames="USER_NAME, REPOSITORY_NAME, URL" referencedTableName="WEB_HOOK" referencedColumnNames="USER_NAME, REPOSITORY_NAME, URL" onDelete="CASCADE" onUpdate="CASCADE"/> | ||||
| </changeSet> | ||||
| @@ -119,5 +119,6 @@ object GitBucketCoreModule | ||||
|       new Version("4.35.3"), | ||||
|       new Version("4.36.0", new LiquibaseMigration("update/gitbucket-core_4.36.xml")), | ||||
|       new Version("4.36.1"), | ||||
|       new Version("4.36.2") | ||||
|       new Version("4.36.2"), | ||||
|       new Version("4.37.0", new LiquibaseMigration("update/gitbucket-core_4.37.xml")), | ||||
|     ) | ||||
|   | ||||
| @@ -1,5 +1,49 @@ | ||||
| package gitbucket.core.api | ||||
|  | ||||
| case class ApiObject(sha: String) | ||||
| import gitbucket.core.util.JGitUtil.TagInfo | ||||
| import gitbucket.core.util.RepositoryName | ||||
| import org.eclipse.jgit.lib.Ref | ||||
|  | ||||
| case class ApiRef(ref: String, `object`: ApiObject) | ||||
| case class ApiRefCommit( | ||||
|   sha: String, | ||||
|   `type`: String, | ||||
|   url: ApiPath | ||||
| ) | ||||
|  | ||||
| case class ApiRef( | ||||
|   ref: String, | ||||
|   node_id: String = "", | ||||
|   url: ApiPath, | ||||
|   `object`: ApiRefCommit, | ||||
| ) | ||||
|  | ||||
| object ApiRef { | ||||
|  | ||||
|   def fromRef( | ||||
|     repositoryName: RepositoryName, | ||||
|     ref: Ref | ||||
|   ): ApiRef = | ||||
|     ApiRef( | ||||
|       ref = ref.getName, | ||||
|       url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/${ref.getName}"), | ||||
|       `object` = ApiRefCommit( | ||||
|         sha = ref.getObjectId.getName, | ||||
|         url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/commits/${ref.getObjectId.getName}"), | ||||
|         `type` = "commit" | ||||
|       ) | ||||
|     ) | ||||
|  | ||||
|   def fromTag( | ||||
|     repositoryName: RepositoryName, | ||||
|     tagInfo: TagInfo | ||||
|   ): ApiRef = | ||||
|     ApiRef( | ||||
|       ref = s"refs/tags/${tagInfo.name}", | ||||
|       url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/refs/tags/${tagInfo.name}"), | ||||
|       `object` = ApiRefCommit( | ||||
|         sha = tagInfo.objectId, | ||||
|         url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/tags/${tagInfo.objectId}"), // TODO This URL is not yet available? | ||||
|         `type` = "tag" | ||||
|       ) | ||||
|     ) | ||||
| } | ||||
|   | ||||
| @@ -1,29 +0,0 @@ | ||||
| package gitbucket.core.api | ||||
|  | ||||
| import gitbucket.core.util.RepositoryName | ||||
|  | ||||
| case class ApiTagCommit( | ||||
|   sha: String, | ||||
|   url: ApiPath | ||||
| ) | ||||
|  | ||||
| case class ApiTag( | ||||
|   name: String, | ||||
|   commit: ApiTagCommit, | ||||
|   zipball_url: ApiPath, | ||||
|   tarball_url: ApiPath | ||||
| ) | ||||
|  | ||||
| object ApiTag { | ||||
|   def apply( | ||||
|     tagName: String, | ||||
|     repositoryName: RepositoryName, | ||||
|     commitId: String | ||||
|   ): ApiTag = | ||||
|     ApiTag( | ||||
|       name = tagName, | ||||
|       commit = ApiTagCommit(sha = commitId, url = ApiPath(s"/${repositoryName.fullName}/commits/${commitId}")), | ||||
|       zipball_url = ApiPath(s"/${repositoryName.fullName}/archive/${tagName}.zip"), | ||||
|       tarball_url = ApiPath(s"/${repositoryName.fullName}/archive/${tagName}.tar.gz") | ||||
|     ) | ||||
| } | ||||
| @@ -138,7 +138,7 @@ trait ReleaseControllerBase extends ControllerBase { | ||||
|  | ||||
|   get("/:owner/:repository/changelog/*...*")(writableUsersOnly { repository => | ||||
|     val Seq(previousTag, currentTag) = multiParams("splat") | ||||
|     val previousTagId = repository.tags.collectFirst { case x if x.name == previousTag => x.id }.getOrElse("") | ||||
|     val previousTagId = repository.tags.collectFirst { case x if x.name == previousTag => x.commitId }.getOrElse("") | ||||
|  | ||||
|     val commitLog = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => | ||||
|       val commits = JGitUtil.getCommitLog(git, previousTagId, currentTag).reverse | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| package gitbucket.core.controller | ||||
|  | ||||
| import java.io.FileInputStream | ||||
|  | ||||
| import gitbucket.core.admin.html | ||||
| import gitbucket.core.plugin.PluginRegistry | ||||
| import gitbucket.core.service.SystemSettingsService._ | ||||
| @@ -50,8 +49,20 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase { | ||||
|     "limitVisibleRepositories" -> trim(label("limitVisibleRepositories", boolean())), | ||||
|     "ssh" -> mapping( | ||||
|       "enabled" -> trim(label("SSH access", boolean())), | ||||
|       "host" -> trim(label("SSH host", optional(text()))), | ||||
|       "port" -> trim(label("SSH port", optional(number()))) | ||||
|       "bindAddress" -> mapping( | ||||
|         "host" -> trim(label("Bind SSH host", optional(text()))), | ||||
|         "port" -> trim(label("Bind SSH port", optional(number()))), | ||||
|       )( | ||||
|         (hostOption, portOption) => | ||||
|           hostOption.map(h => SshAddress(h, portOption.getOrElse(DefaultSshPort), GenericSshUser)) | ||||
|       ), | ||||
|       "publicAddress" -> mapping( | ||||
|         "host" -> trim(label("Public SSH host", optional(text()))), | ||||
|         "port" -> trim(label("Public SSH port", optional(number()))), | ||||
|       )( | ||||
|         (hostOption, portOption) => | ||||
|           hostOption.map(h => SshAddress(h, portOption.getOrElse(PublicSshPort), GenericSshUser)) | ||||
|       ), | ||||
|     )(Ssh.apply), | ||||
|     "useSMTP" -> trim(label("SMTP", boolean())), | ||||
|     "smtp" -> optionalIfNotChecked( | ||||
| @@ -116,8 +127,8 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase { | ||||
|       if (settings.ssh.enabled && settings.baseUrl.isEmpty) { | ||||
|         Some("baseUrl" -> "Base URL is required if SSH access is enabled.") | ||||
|       } else None, | ||||
|       if (settings.ssh.enabled && settings.ssh.sshHost.isEmpty) { | ||||
|         Some("sshHost" -> "SSH host is required if SSH access is enabled.") | ||||
|       if (settings.ssh.enabled && settings.ssh.bindAddress.isEmpty) { | ||||
|         Some("ssh.bindAddress.host" -> "SSH bind host is required if SSH access is enabled.") | ||||
|       } else None | ||||
|     ).flatten | ||||
|   } | ||||
| @@ -308,12 +319,13 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase { | ||||
|   post("/admin/system", form)(adminOnly { form => | ||||
|     saveSystemSettings(form) | ||||
|  | ||||
|     if (form.sshAddress != context.settings.sshAddress) { | ||||
|     if (form.ssh.bindAddress != context.settings.sshBindAddress || form.ssh.publicAddress != context.settings.sshPublicAddress) { | ||||
|       SshServer.stop() | ||||
|       for { | ||||
|         sshAddress <- form.sshAddress | ||||
|         bindAddress <- form.ssh.bindAddress | ||||
|         publicAddress <- form.ssh.publicAddress.orElse(form.ssh.bindAddress) | ||||
|         baseUrl <- form.baseUrl | ||||
|       } SshServer.start(sshAddress, baseUrl) | ||||
|       } SshServer.start(bindAddress, publicAddress, baseUrl) | ||||
|     } | ||||
|  | ||||
|     flash.update("info", "System settings has been updated.") | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| package gitbucket.core.controller.api | ||||
| import gitbucket.core.api.{ApiObject, ApiRef, CreateARef, JsonFormat, UpdateARef} | ||||
| import gitbucket.core.api.{ApiError, ApiRef, CreateARef, JsonFormat, UpdateARef} | ||||
| import gitbucket.core.controller.ControllerBase | ||||
| import gitbucket.core.service.RepositoryService.RepositoryInfo | ||||
| import gitbucket.core.util.Directory.getRepositoryDir | ||||
| import gitbucket.core.util.ReferrerAuthenticator | ||||
| import gitbucket.core.util.Implicits._ | ||||
| import gitbucket.core.util.{ReferrerAuthenticator, RepositoryName, WritableUsersAuthenticator} | ||||
| import org.eclipse.jgit.api.Git | ||||
| import org.eclipse.jgit.lib.ObjectId | ||||
| import org.eclipse.jgit.lib.RefUpdate.Result | ||||
| @@ -14,47 +15,38 @@ import scala.jdk.CollectionConverters._ | ||||
| import scala.util.Using | ||||
|  | ||||
| trait ApiGitReferenceControllerBase extends ControllerBase { | ||||
|   self: ReferrerAuthenticator => | ||||
|   self: ReferrerAuthenticator with WritableUsersAuthenticator => | ||||
|  | ||||
|   private val logger = LoggerFactory.getLogger(classOf[ApiGitReferenceControllerBase]) | ||||
|  | ||||
|   get("/api/v3/repos/:owner/:repository/git/refs")(referrersOnly { repository => | ||||
|     val result = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => | ||||
|       val refs = git | ||||
|         .getRepository() | ||||
|         .getRefDatabase() | ||||
|         .getRefsByPrefix("refs") | ||||
|         .asScala | ||||
|  | ||||
|       refs.map(ApiRef.fromRef(RepositoryName(s"${repository.owner}/${repository.name}"), _)) | ||||
|     } | ||||
|     JsonFormat(result) | ||||
|   }) | ||||
|  | ||||
|   /* | ||||
|    * i. Get a reference | ||||
|    * https://docs.github.com/en/free-pro-team@latest/rest/reference/git#get-a-reference | ||||
|    */ | ||||
|   get("/api/v3/repos/:owner/:repository/git/ref/*")(referrersOnly { repository => | ||||
|     getRef() | ||||
|     val revstr = multiParams("splat").head | ||||
|     getRef(revstr, repository) | ||||
|   }) | ||||
|  | ||||
|   // Some versions of GHE support this path | ||||
|   get("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { repository => | ||||
|     logger.warn("git/refs/ endpoint may not be compatible with GitHub API v3. Consider using git/ref/ endpoint instead") | ||||
|     getRef() | ||||
|   }) | ||||
|  | ||||
|   private def getRef() = { | ||||
|     val revstr = multiParams("splat").head | ||||
|     Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git => | ||||
|       val ref = git.getRepository().findRef(revstr) | ||||
|  | ||||
|       if (ref != null) { | ||||
|         val sha = ref.getObjectId().name() | ||||
|         JsonFormat(ApiRef(revstr, ApiObject(sha))) | ||||
|  | ||||
|       } else { | ||||
|         val refs = git | ||||
|           .getRepository() | ||||
|           .getRefDatabase() | ||||
|           .getRefsByPrefix("refs/") | ||||
|           .asScala | ||||
|  | ||||
|         JsonFormat(refs.map { ref => | ||||
|           val sha = ref.getObjectId().name() | ||||
|           ApiRef(revstr, ApiObject(sha)) | ||||
|         }) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|     getRef(revstr, repository) | ||||
|   }) | ||||
|  | ||||
|   /* | ||||
|    * ii. Get all references | ||||
| @@ -65,17 +57,17 @@ trait ApiGitReferenceControllerBase extends ControllerBase { | ||||
|    * iii. Create a reference | ||||
|    * https://docs.github.com/en/free-pro-team@latest/rest/reference/git#create-a-reference | ||||
|    */ | ||||
|   post("/api/v3/repos/:owner/:repository/git/refs")(referrersOnly { _ => | ||||
|   post("/api/v3/repos/:owner/:repository/git/refs")(referrersOnly { repository => | ||||
|     extractFromJsonBody[CreateARef].map { | ||||
|       data => | ||||
|         Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git => | ||||
|         Using.resource(Git.open(getRepositoryDir(repository.owner, repository.owner))) { git => | ||||
|           val ref = git.getRepository.findRef(data.ref) | ||||
|           if (ref == null) { | ||||
|             val update = git.getRepository.updateRef(data.ref) | ||||
|             update.setNewObjectId(ObjectId.fromString(data.sha)) | ||||
|             val result = update.update() | ||||
|             result match { | ||||
|               case Result.NEW => JsonFormat(ApiRef(update.getName, ApiObject(update.getNewObjectId.getName))) | ||||
|               case Result.NEW => JsonFormat(ApiRef.fromRef(RepositoryName(repository.owner, repository.name), ref)) | ||||
|               case _          => UnprocessableEntity(result.name()) | ||||
|             } | ||||
|           } else { | ||||
| @@ -89,11 +81,11 @@ trait ApiGitReferenceControllerBase extends ControllerBase { | ||||
|    * iv. Update a reference | ||||
|    * https://docs.github.com/en/free-pro-team@latest/rest/reference/git#update-a-reference | ||||
|    */ | ||||
|   patch("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { _ => | ||||
|   patch("/api/v3/repos/:owner/:repository/git/refs/*")(writableUsersOnly { repository => | ||||
|     val refName = multiParams("splat").mkString("/") | ||||
|     extractFromJsonBody[UpdateARef].map { | ||||
|       data => | ||||
|         Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git => | ||||
|         Using.resource(Git.open(getRepositoryDir(repository.owner, repository.owner))) { git => | ||||
|           val ref = git.getRepository.findRef(refName) | ||||
|           if (ref == null) { | ||||
|             UnprocessableEntity("Ref does not exist.") | ||||
| @@ -104,7 +96,7 @@ trait ApiGitReferenceControllerBase extends ControllerBase { | ||||
|             val result = update.update() | ||||
|             result match { | ||||
|               case Result.FORCED | Result.FAST_FORWARD | Result.NO_CHANGE => | ||||
|                 JsonFormat(ApiRef(update.getName, ApiObject(update.getNewObjectId.getName))) | ||||
|                 JsonFormat(ApiRef.fromRef(RepositoryName(repository), update.getRef)) | ||||
|               case _ => UnprocessableEntity(result.name()) | ||||
|             } | ||||
|           } | ||||
| @@ -116,7 +108,7 @@ trait ApiGitReferenceControllerBase extends ControllerBase { | ||||
|    * v. Delete a reference | ||||
|    * https://docs.github.com/en/free-pro-team@latest/rest/reference/git#delete-a-reference | ||||
|    */ | ||||
|   delete("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { _ => | ||||
|   delete("/api/v3/repos/:owner/:repository/git/refs/*")(writableUsersOnly { _ => | ||||
|     val refName = multiParams("splat").mkString("/") | ||||
|     Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git => | ||||
|       val ref = git.getRepository.findRef(refName) | ||||
| @@ -133,4 +125,34 @@ trait ApiGitReferenceControllerBase extends ControllerBase { | ||||
|       } | ||||
|     } | ||||
|   }) | ||||
|  | ||||
|   private def notFound(): ApiError = { | ||||
|     response.setStatus(404) | ||||
|     ApiError("Not Found") | ||||
|   } | ||||
|  | ||||
|   protected def getRef(revstr: String, repository: RepositoryInfo): AnyRef = { | ||||
|     logger.debug(s"getRef: path '${revstr}'") | ||||
|  | ||||
|     val name = RepositoryName(repository) | ||||
|     val result = JsonFormat(revstr match { | ||||
|       case "tags" => repository.tags.map(ApiRef.fromTag(name, _)) | ||||
|       case x if x.startsWith("tags/") => | ||||
|         val tagName = x.substring("tags/".length) | ||||
|         repository.tags.find(_.name == tagName) match { | ||||
|           case Some(tagInfo) => ApiRef.fromTag(name, tagInfo) | ||||
|           case None          => notFound() | ||||
|         } | ||||
|       case other => | ||||
|         Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => | ||||
|           git.getRepository().findRef(other) match { | ||||
|             case null => notFound() | ||||
|             case ref  => ApiRef.fromRef(name, ref) | ||||
|           } | ||||
|         } | ||||
|     }) | ||||
|  | ||||
|     logger.debug(s"json result: $result") | ||||
|     result | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -2,7 +2,6 @@ package gitbucket.core.controller.api | ||||
| import gitbucket.core.api._ | ||||
| import gitbucket.core.controller.ControllerBase | ||||
| import gitbucket.core.service.MilestonesService | ||||
| import gitbucket.core.service.RepositoryService.RepositoryInfo | ||||
| import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator} | ||||
| import gitbucket.core.util.Implicits._ | ||||
| import org.scalatra.NoContent | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import gitbucket.core.api.{ApiCommit, ApiContents, ApiError, CreateAFile, JsonFo | ||||
| import gitbucket.core.controller.ControllerBase | ||||
| import gitbucket.core.service.{RepositoryCommitFileService, RepositoryService} | ||||
| import gitbucket.core.util.Directory.getRepositoryDir | ||||
| import gitbucket.core.util.JGitUtil.{CommitInfo, FileInfo, getContentFromId, getFileList, getSummaryMessage} | ||||
| import gitbucket.core.util.JGitUtil.{CommitInfo, FileInfo, getContentFromId, getFileList} | ||||
| import gitbucket.core.util._ | ||||
| import gitbucket.core.view.helpers.{isRenderable, renderMarkup} | ||||
| import gitbucket.core.util.Implicits._ | ||||
| @@ -49,21 +49,19 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase { | ||||
|     getContents(repository, multiParams("splat").head, params.getOrElse("ref", repository.repository.defaultBranch)) | ||||
|   }) | ||||
|  | ||||
|   private def getFileInfo(git: Git, revision: String, pathStr: String): Option[FileInfo] = { | ||||
|     val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(revision)) | ||||
|     getPathObjectId(git, pathStr, revCommit).map { objectId => | ||||
|       FileInfo( | ||||
|         id = objectId, | ||||
|         isDirectory = false, | ||||
|         name = pathStr.split("/").last, | ||||
|         path = pathStr.split("/").dropRight(1).mkString("/"), | ||||
|         message = getSummaryMessage(revCommit.getFullMessage, revCommit.getShortMessage), | ||||
|         commitId = revCommit.getName, | ||||
|         time = revCommit.getAuthorIdent.getWhen, | ||||
|         author = revCommit.getAuthorIdent.getName, | ||||
|         mailAddress = revCommit.getAuthorIdent.getEmailAddress, | ||||
|         linkUrl = None | ||||
|       ) | ||||
|   private def getFileInfo(git: Git, revision: String, pathStr: String, ignoreCase: Boolean): Option[FileInfo] = { | ||||
|     val (dirName, fileName) = pathStr.lastIndexOf('/') match { | ||||
|       case -1 => | ||||
|         (".", pathStr) | ||||
|       case n => | ||||
|         (pathStr.take(n), pathStr.drop(n + 1)) | ||||
|     } | ||||
|     if (ignoreCase) { | ||||
|       getFileList(git, revision, dirName, maxFiles = context.settings.repositoryViewer.maxFiles) | ||||
|         .find(_.name.toLowerCase.equals(fileName.toLowerCase)) | ||||
|     } else { | ||||
|       getFileList(git, revision, dirName, maxFiles = context.settings.repositoryViewer.maxFiles) | ||||
|         .find(_.name.equals(fileName)) | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -76,17 +74,16 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase { | ||||
|     Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git => | ||||
|       val fileList = getFileList(git, refStr, path, maxFiles = context.settings.repositoryViewer.maxFiles) | ||||
|       if (fileList.isEmpty) { // file or NotFound | ||||
|         val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(refStr)) | ||||
|         getPathObjectId(git, path, revCommit) | ||||
|           .flatMap { objectId => | ||||
|         getFileInfo(git, refStr, path, ignoreCase) | ||||
|           .flatMap { f => | ||||
|             val largeFile = params.get("large_file").exists(s => s.equals("true")) | ||||
|             val content = getContentFromId(git, objectId, largeFile) | ||||
|             val content = getContentFromId(git, f.id, largeFile) | ||||
|             request.getHeader("Accept") match { | ||||
|               case "application/vnd.github.v3.raw" => { | ||||
|                 contentType = "application/vnd.github.v3.raw" | ||||
|                 content | ||||
|               } | ||||
|               case "application/vnd.github.v3.html" if isRenderable(path) => { | ||||
|               case "application/vnd.github.v3.html" if isRenderable(f.name) => { | ||||
|                 contentType = "application/vnd.github.v3.html" | ||||
|                 content.map { c => | ||||
|                   List( | ||||
| @@ -117,12 +114,11 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase { | ||||
|                 } | ||||
|               } | ||||
|               case _ => | ||||
|                 getFileInfo(git, refStr, path).map { f => | ||||
|                   JsonFormat(ApiContents(f, RepositoryName(repository), content)) | ||||
|                 } | ||||
|                 Some(JsonFormat(ApiContents(f, RepositoryName(repository), content))) | ||||
|             } | ||||
|           } | ||||
|           .getOrElse(NotFound()) | ||||
|  | ||||
|       } else { // directory | ||||
|         JsonFormat(fileList.map { f => | ||||
|           ApiContents(f, RepositoryName(repository), None) | ||||
| @@ -152,7 +148,9 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase { | ||||
|           val path = paths.take(paths.size - 1).toList.mkString("/") | ||||
|           Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { | ||||
|             git => | ||||
|               getFileInfo(git, commit, path) match { | ||||
|               val fileInfo = getFileInfo(git, commit, path, false) | ||||
|  | ||||
|               fileInfo match { | ||||
|                 case Some(f) if !data.sha.contains(f.id.getName) => | ||||
|                   ApiError( | ||||
|                     "The blob SHA is not matched.", | ||||
|   | ||||
| @@ -16,6 +16,7 @@ import scala.util.Using | ||||
|  | ||||
| trait ApiRepositoryControllerBase extends ControllerBase { | ||||
|   self: RepositoryService | ||||
|     with ApiGitReferenceControllerBase | ||||
|     with RepositoryCreationService | ||||
|     with AccountService | ||||
|     with OwnerAuthenticator | ||||
| @@ -184,9 +185,11 @@ trait ApiRepositoryControllerBase extends ControllerBase { | ||||
|    * https://docs.github.com/en/rest/reference/repos#list-repository-tags | ||||
|    */ | ||||
|   get("/api/v3/repos/:owner/:repository/tags")(referrersOnly { repository => | ||||
|     JsonFormat( | ||||
|       repository.tags.map(tagInfo => ApiTag(tagInfo.name, RepositoryName(repository), tagInfo.id)) | ||||
|     ) | ||||
|     Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => | ||||
|       JsonFormat( | ||||
|         self.getRef("tags", repository) | ||||
|       ) | ||||
|     } | ||||
|   }) | ||||
|  | ||||
|   /* | ||||
|   | ||||
| @@ -579,7 +579,7 @@ trait PullRequestService { | ||||
|       case (oldGit, newGit) => | ||||
|         if (originRepository.branchList.contains(originId)) { | ||||
|           val forkedId2 = | ||||
|             forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.id }.getOrElse(forkedId) | ||||
|             forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.commitId }.getOrElse(forkedId) | ||||
|  | ||||
|           val originId2 = JGitUtil.getForkedCommitId( | ||||
|             oldGit, | ||||
| @@ -596,9 +596,9 @@ trait PullRequestService { | ||||
|  | ||||
|         } else { | ||||
|           val originId2 = | ||||
|             originRepository.tags.collectFirst { case x if x.name == originId => x.id }.getOrElse(originId) | ||||
|             originRepository.tags.collectFirst { case x if x.name == originId => x.commitId }.getOrElse(originId) | ||||
|           val forkedId2 = | ||||
|             forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.id }.getOrElse(forkedId) | ||||
|             forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.commitId }.getOrElse(forkedId) | ||||
|  | ||||
|           (Option(oldGit.getRepository.resolve(originId2)), Option(newGit.getRepository.resolve(forkedId2))) | ||||
|         } | ||||
|   | ||||
| @@ -12,6 +12,7 @@ import gitbucket.core.util.JGitUtil.FileInfo | ||||
| import org.apache.commons.io.FileUtils | ||||
| import org.eclipse.jgit.api.Git | ||||
| import org.eclipse.jgit.lib.{Repository => _} | ||||
|  | ||||
| import scala.util.Using | ||||
|  | ||||
| trait RepositoryService { | ||||
| @@ -835,12 +836,10 @@ object RepositoryService { | ||||
|  | ||||
|   def httpUrl(owner: String, name: String)(implicit context: Context): String = | ||||
|     s"${context.baseUrl}/git/${owner}/${name}.git" | ||||
|  | ||||
|   def sshUrl(owner: String, name: String)(implicit context: Context): Option[String] = | ||||
|     if (context.settings.ssh.enabled) { | ||||
|       context.settings.sshAddress.map { x => | ||||
|         s"ssh://${x.genericUser}@${x.host}:${x.port}/${owner}/${name}.git" | ||||
|       } | ||||
|     } else None | ||||
|     context.settings.sshUrl(owner, name) | ||||
|  | ||||
|   def openRepoUrl(openUrl: String)(implicit context: Context): String = | ||||
|     s"github-${context.platform}://openRepo/${openUrl}" | ||||
|  | ||||
|   | ||||
| @@ -4,9 +4,10 @@ import javax.servlet.http.HttpServletRequest | ||||
| import com.nimbusds.jose.JWSAlgorithm | ||||
| import com.nimbusds.oauth2.sdk.auth.Secret | ||||
| import com.nimbusds.oauth2.sdk.id.{ClientID, Issuer} | ||||
| import gitbucket.core.service.SystemSettingsService._ | ||||
| import gitbucket.core.service.SystemSettingsService.{getOptionValue, _} | ||||
| import gitbucket.core.util.ConfigUtil._ | ||||
| import gitbucket.core.util.Directory._ | ||||
|  | ||||
| import scala.util.Using | ||||
|  | ||||
| trait SystemSettingsService { | ||||
| @@ -29,8 +30,14 @@ trait SystemSettingsService { | ||||
|     props.setProperty(Notification, settings.notification.toString) | ||||
|     props.setProperty(LimitVisibleRepositories, settings.limitVisibleRepositories.toString) | ||||
|     props.setProperty(SshEnabled, settings.ssh.enabled.toString) | ||||
|     settings.ssh.sshHost.foreach(x => props.setProperty(SshHost, x.trim)) | ||||
|     settings.ssh.sshPort.foreach(x => props.setProperty(SshPort, x.toString)) | ||||
|     settings.ssh.bindAddress.foreach { bindAddress => | ||||
|       props.setProperty(SshBindAddressHost, bindAddress.host.trim()) | ||||
|       props.setProperty(SshBindAddressPort, bindAddress.port.toString) | ||||
|     } | ||||
|     settings.ssh.publicAddress.foreach { publicAddress => | ||||
|       props.setProperty(SshPublicAddressHost, publicAddress.host.trim()) | ||||
|       props.setProperty(SshPublicAddressPort, publicAddress.port.toString) | ||||
|     } | ||||
|     props.setProperty(UseSMTP, settings.useSMTP.toString) | ||||
|     if (settings.useSMTP) { | ||||
|       settings.smtp.foreach { smtp => | ||||
| @@ -95,6 +102,10 @@ trait SystemSettingsService { | ||||
|         props.load(in) | ||||
|       } | ||||
|     } | ||||
|     loadSystemSettings(props) | ||||
|   } | ||||
|  | ||||
|   def loadSystemSettings(props: java.util.Properties): SystemSettings = { | ||||
|     SystemSettings( | ||||
|       getOptionValue[String](props, BaseURL, None).map(x => x.replaceFirst("/\\Z", "")), | ||||
|       getOptionValue(props, Information, None), | ||||
| @@ -112,9 +123,20 @@ trait SystemSettingsService { | ||||
|       getValue(props, Notification, false), | ||||
|       getValue(props, LimitVisibleRepositories, false), | ||||
|       Ssh( | ||||
|         getValue(props, SshEnabled, false), | ||||
|         getOptionValue[String](props, SshHost, None).map(_.trim), | ||||
|         getOptionValue(props, SshPort, Some(DefaultSshPort)) | ||||
|         enabled = getValue(props, SshEnabled, false), | ||||
|         bindAddress = { | ||||
|           // try the new-style configuration first | ||||
|           getOptionValue[String](props, SshBindAddressHost, None) | ||||
|             .map(h => SshAddress(h, getValue(props, SshBindAddressPort, DefaultSshPort), GenericSshUser)) | ||||
|             .orElse( | ||||
|               // otherwise try to get old-style configuration | ||||
|               getOptionValue[String](props, SshHost, None) | ||||
|                 .map(_.trim) | ||||
|                 .map(h => SshAddress(h, getValue(props, SshPort, DefaultSshPort), GenericSshUser)) | ||||
|             ) | ||||
|         }, | ||||
|         publicAddress = getOptionValue[String](props, SshPublicAddressHost, None) | ||||
|           .map(h => SshAddress(h, getValue(props, SshPublicAddressPort, PublicSshPort), GenericSshUser)) | ||||
|       ), | ||||
|       getValue( | ||||
|         props, | ||||
| @@ -182,7 +204,6 @@ trait SystemSettingsService { | ||||
|       ) | ||||
|     ) | ||||
|   } | ||||
|  | ||||
| } | ||||
|  | ||||
| object SystemSettingsService { | ||||
| @@ -214,7 +235,6 @@ object SystemSettingsService { | ||||
|     upload: Upload, | ||||
|     repositoryViewer: RepositoryViewerSettings | ||||
|   ) { | ||||
|  | ||||
|     def baseUrl(request: HttpServletRequest): String = | ||||
|       baseUrl.getOrElse(parseBaseUrl(request)).stripSuffix("/") | ||||
|  | ||||
| @@ -231,11 +251,17 @@ object SystemSettingsService { | ||||
|         .fold(base)(_ + base.dropWhile(_ != ':')) | ||||
|     } | ||||
|  | ||||
|     def sshAddress: Option[SshAddress] = | ||||
|       ssh.sshHost.collect { | ||||
|         case host if ssh.enabled => | ||||
|           SshAddress(host, ssh.sshPort.getOrElse(DefaultSshPort), "git") | ||||
|       } | ||||
|     def sshBindAddress: Option[SshAddress] = | ||||
|       ssh.bindAddress | ||||
|  | ||||
|     def sshPublicAddress: Option[SshAddress] = | ||||
|       ssh.publicAddress.orElse(ssh.bindAddress) | ||||
|  | ||||
|     def sshUrl: Option[String] = | ||||
|       ssh.getUrl | ||||
|  | ||||
|     def sshUrl(owner: String, name: String): Option[String] = | ||||
|       ssh.getUrl(owner: String, name: String) | ||||
|   } | ||||
|  | ||||
|   case class RepositoryOperation( | ||||
| @@ -248,9 +274,35 @@ object SystemSettingsService { | ||||
|  | ||||
|   case class Ssh( | ||||
|     enabled: Boolean, | ||||
|     sshHost: Option[String], | ||||
|     sshPort: Option[Int] | ||||
|   ) | ||||
|     bindAddress: Option[SshAddress], | ||||
|     publicAddress: Option[SshAddress] | ||||
|   ) { | ||||
|  | ||||
|     def getUrl: Option[String] = | ||||
|       if (enabled) { | ||||
|         publicAddress.map(_.getUrl).orElse(bindAddress.map(_.getUrl)) | ||||
|       } else { | ||||
|         None | ||||
|       } | ||||
|  | ||||
|     def getUrl(owner: String, name: String): Option[String] = | ||||
|       if (enabled) { | ||||
|         publicAddress | ||||
|           .map(_.getUrl(owner, name)) | ||||
|           .orElse(bindAddress.map(_.getUrl(owner, name))) | ||||
|       } else { | ||||
|         None | ||||
|       } | ||||
|   } | ||||
|  | ||||
|   object Ssh { | ||||
|     def apply( | ||||
|       enabled: Boolean, | ||||
|       bindAddress: Option[SshAddress], | ||||
|       publicAddress: Option[SshAddress] | ||||
|     ): Ssh = | ||||
|       new Ssh(enabled, bindAddress, publicAddress.orElse(bindAddress)) | ||||
|   } | ||||
|  | ||||
|   case class Ldap( | ||||
|     host: String, | ||||
| @@ -296,7 +348,25 @@ object SystemSettingsService { | ||||
|     password: Option[String] | ||||
|   ) | ||||
|  | ||||
|   case class SshAddress(host: String, port: Int, genericUser: String) | ||||
|   case class SshAddress(host: String, port: Int, genericUser: String) { | ||||
|  | ||||
|     def isDefaultPort: Boolean = | ||||
|       port == PublicSshPort | ||||
|  | ||||
|     def getUrl: String = | ||||
|       if (isDefaultPort) { | ||||
|         s"${genericUser}@${host}" | ||||
|       } else { | ||||
|         s"${genericUser}@${host}:${port}" | ||||
|       } | ||||
|  | ||||
|     def getUrl(owner: String, name: String): String = | ||||
|       if (isDefaultPort) { | ||||
|         s"${genericUser}@${host}:${owner}/${name}.git" | ||||
|       } else { | ||||
|         s"ssh://${genericUser}@${host}:${port}/${owner}/${name}.git" | ||||
|       } | ||||
|   } | ||||
|  | ||||
|   case class WebHook(blockPrivateAddress: Boolean, whitelist: Seq[String]) | ||||
|  | ||||
| @@ -304,6 +374,8 @@ object SystemSettingsService { | ||||
|  | ||||
|   case class RepositoryViewerSettings(maxFiles: Int) | ||||
|  | ||||
|   val GenericSshUser = "git" | ||||
|   val PublicSshPort = 22 | ||||
|   val DefaultSshPort = 29418 | ||||
|   val DefaultSmtpPort = 25 | ||||
|   val DefaultLdapPort = 389 | ||||
| @@ -325,6 +397,10 @@ object SystemSettingsService { | ||||
|   private val SshEnabled = "ssh" | ||||
|   private val SshHost = "ssh.host" | ||||
|   private val SshPort = "ssh.port" | ||||
|   private val SshBindAddressHost = "ssh.bindAddress.host" | ||||
|   private val SshBindAddressPort = "ssh.bindAddress.port" | ||||
|   private val SshPublicAddressHost = "ssh.publicAddress.host" | ||||
|   private val SshPublicAddressPort = "ssh.publicAddress.port" | ||||
|   private val UseSMTP = "useSMTP" | ||||
|   private val SmtpHost = "smtp.host" | ||||
|   private val SmtpPort = "smtp.port" | ||||
|   | ||||
| @@ -3,7 +3,6 @@ package gitbucket.core.servlet | ||||
| import java.io.File | ||||
| import java.util | ||||
| import java.util.Date | ||||
|  | ||||
| import scala.util.Using | ||||
| import gitbucket.core.api | ||||
| import gitbucket.core.api.JsonFormat.Context | ||||
| @@ -209,9 +208,7 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest] | ||||
|  | ||||
|       val settings = loadSystemSettings() | ||||
|       val baseUrl = settings.baseUrl(request) | ||||
|       val sshUrl = settings.sshAddress.map { x => | ||||
|         s"${x.genericUser}@${x.host}:${x.port}" | ||||
|       } | ||||
|       val sshUrl = settings.sshUrl(owner, repository) | ||||
|  | ||||
|       if (!repository.endsWith(".wiki")) { | ||||
|         val hook = new CommitLogHook(owner, repository, pusher, baseUrl, sshUrl) | ||||
|   | ||||
| @@ -5,26 +5,31 @@ import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry} | ||||
| import gitbucket.core.service.{AccountService, DeployKeyService, RepositoryService, SystemSettingsService} | ||||
| import gitbucket.core.servlet.{CommitLogHook, Database} | ||||
| import gitbucket.core.util.Directory | ||||
| import org.apache.sshd.server.{Environment, ExitCallback, SessionAware} | ||||
| import org.apache.sshd.server.{Environment, ExitCallback} | ||||
| import org.apache.sshd.server.command.{Command, CommandFactory} | ||||
| import org.apache.sshd.server.session.ServerSession | ||||
| import org.apache.sshd.server.session.{ServerSession, ServerSessionAware} | ||||
| import org.slf4j.LoggerFactory | ||||
| import java.io.{File, InputStream, OutputStream} | ||||
|  | ||||
| import java.io.{File, InputStream, OutputStream} | ||||
| import org.eclipse.jgit.api.Git | ||||
| import Directory._ | ||||
| import gitbucket.core.service.SystemSettingsService.SshAddress | ||||
| import gitbucket.core.ssh.PublicKeyAuthenticator.AuthType | ||||
| import org.apache.sshd.server.channel.ChannelSession | ||||
| import org.eclipse.jgit.transport.{ReceivePack, UploadPack} | ||||
| import org.apache.sshd.server.shell.UnknownCommand | ||||
| import org.eclipse.jgit.errors.RepositoryNotFoundException | ||||
|  | ||||
| import scala.util.Using | ||||
|  | ||||
| object GitCommand { | ||||
|   val DefaultCommandRegex = """\Agit-(upload|receive)-pack '/([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-\+_.]+).git'\Z""".r | ||||
|   val SimpleCommandRegex = """\Agit-(upload|receive)-pack '/(.+\.git)'\Z""".r | ||||
|   val DefaultCommandRegexPort22 = """\Agit-(upload|receive)-pack '/?([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-\+_.]+).git'\Z""".r | ||||
|   val SimpleCommandRegexPort22 = """\Agit-(upload|receive)-pack '/?(.+\.git)'\Z""".r | ||||
| } | ||||
|  | ||||
| abstract class GitCommand extends Command with SessionAware { | ||||
| abstract class GitCommand extends Command with ServerSessionAware { | ||||
|  | ||||
|   private val logger = LoggerFactory.getLogger(classOf[GitCommand]) | ||||
|  | ||||
| @@ -57,12 +62,12 @@ abstract class GitCommand extends Command with SessionAware { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   final override def start(env: Environment): Unit = { | ||||
|   final override def start(channel: ChannelSession, env: Environment): Unit = { | ||||
|     val thread = new Thread(newTask()) | ||||
|     thread.start() | ||||
|   } | ||||
|  | ||||
|   override def destroy(): Unit = {} | ||||
|   override def destroy(channel: ChannelSession): Unit = {} | ||||
|  | ||||
|   override def setExitCallback(callback: ExitCallback): Unit = { | ||||
|     this.callback = callback | ||||
| @@ -159,7 +164,7 @@ class DefaultGitUploadPack(owner: String, repoName: String) | ||||
|   } | ||||
| } | ||||
|  | ||||
| class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String, sshUrl: Option[String]) | ||||
| class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String, sshAddress: SshAddress) | ||||
|     extends DefaultGitCommand(owner, repoName) | ||||
|     with RepositoryService | ||||
|     with AccountService | ||||
| @@ -177,7 +182,8 @@ class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String, ss | ||||
|         val repository = git.getRepository | ||||
|         val receive = new ReceivePack(repository) | ||||
|         if (!repoName.endsWith(".wiki")) { | ||||
|           val hook = new CommitLogHook(owner, repoName, userName(authType), baseUrl, sshUrl) | ||||
|           val hook = | ||||
|             new CommitLogHook(owner, repoName, userName(authType), baseUrl, Some(sshAddress.getUrl(owner, repoName))) | ||||
|           receive.setPreReceiveHook(hook) | ||||
|           receive.setPostReceiveHook(hook) | ||||
|         } | ||||
| @@ -227,10 +233,10 @@ class PluginGitReceivePack(repoName: String, routing: GitRepositoryRouting) | ||||
|   } | ||||
| } | ||||
|  | ||||
| class GitCommandFactory(baseUrl: String, sshUrl: Option[String]) extends CommandFactory { | ||||
| class GitCommandFactory(baseUrl: String, sshAddress: SshAddress) extends CommandFactory { | ||||
|   private val logger = LoggerFactory.getLogger(classOf[GitCommandFactory]) | ||||
|  | ||||
|   override def createCommand(command: String): Command = { | ||||
|   override def createCommand(channel: ChannelSession, command: String): Command = { | ||||
|     import GitCommand._ | ||||
|     logger.debug(s"command: $command") | ||||
|  | ||||
| @@ -238,19 +244,24 @@ class GitCommandFactory(baseUrl: String, sshUrl: Option[String]) extends Command | ||||
|       case f if f.isDefinedAt(command) => f(command) | ||||
|     } | ||||
|  | ||||
|     pluginCommand match { | ||||
|       case Some(x) => x | ||||
|       case None => | ||||
|         command match { | ||||
|           case SimpleCommandRegex("upload", repoName) if (pluginRepository(repoName)) => | ||||
|             new PluginGitUploadPack(repoName, routing(repoName)) | ||||
|           case SimpleCommandRegex("receive", repoName) if (pluginRepository(repoName)) => | ||||
|             new PluginGitReceivePack(repoName, routing(repoName)) | ||||
|           case DefaultCommandRegex("upload", owner, repoName) => new DefaultGitUploadPack(owner, repoName) | ||||
|           case DefaultCommandRegex("receive", owner, repoName) => | ||||
|             new DefaultGitReceivePack(owner, repoName, baseUrl, sshUrl) | ||||
|           case _ => new UnknownCommand(command) | ||||
|     pluginCommand.getOrElse { | ||||
|       val (simpleRegex, defaultRegex) = | ||||
|         if (sshAddress.isDefaultPort) { | ||||
|           (SimpleCommandRegexPort22, DefaultCommandRegexPort22) | ||||
|         } else { | ||||
|           (SimpleCommandRegex, DefaultCommandRegex) | ||||
|         } | ||||
|       command match { | ||||
|         case simpleRegex("upload", repoName) if pluginRepository(repoName) => | ||||
|           new PluginGitUploadPack(repoName, routing(repoName)) | ||||
|         case simpleRegex("receive", repoName) if pluginRepository(repoName) => | ||||
|           new PluginGitReceivePack(repoName, routing(repoName)) | ||||
|         case defaultRegex("upload", owner, repoName) => | ||||
|           new DefaultGitUploadPack(owner, repoName) | ||||
|         case defaultRegex("receive", owner, repoName) => | ||||
|           new DefaultGitReceivePack(owner, repoName, baseUrl, sshAddress) | ||||
|         case _ => new UnknownCommand(command) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -1,20 +1,23 @@ | ||||
| package gitbucket.core.ssh | ||||
|  | ||||
| import gitbucket.core.service.SystemSettingsService.SshAddress | ||||
| import org.apache.sshd.common.Factory | ||||
| import org.apache.sshd.server.channel.ChannelSession | ||||
| import org.apache.sshd.server.{Environment, ExitCallback} | ||||
| import org.apache.sshd.server.command.Command | ||||
| import java.io.{OutputStream, InputStream} | ||||
| import org.apache.sshd.server.shell.ShellFactory | ||||
|  | ||||
| import java.io.{InputStream, OutputStream} | ||||
| import org.eclipse.jgit.lib.Constants | ||||
|  | ||||
| class NoShell(sshAddress: SshAddress) extends Factory[Command] { | ||||
|   override def create(): Command = new Command() { | ||||
| class NoShell(sshAddress: SshAddress) extends ShellFactory { | ||||
|   override def createShell(channel: ChannelSession): Command = new Command() { | ||||
|     private var in: InputStream = null | ||||
|     private var out: OutputStream = null | ||||
|     private var err: OutputStream = null | ||||
|     private var callback: ExitCallback = null | ||||
|  | ||||
|     override def start(env: Environment): Unit = { | ||||
|     override def start(channel: ChannelSession, env: Environment): Unit = { | ||||
|       val placeholderAddress = sshAddress.getUrl("OWNER", "REPOSITORY_NAME") | ||||
|       val message = | ||||
|         """ | ||||
|           | Welcome to | ||||
| @@ -30,8 +33,8 @@ class NoShell(sshAddress: SshAddress) extends Factory[Command] { | ||||
|           | | ||||
|           | Please use: | ||||
|           | | ||||
|           | git clone ssh://%s@%s:%d/OWNER/REPOSITORY_NAME.git | ||||
|         """.stripMargin.format(sshAddress.genericUser, sshAddress.host, sshAddress.port).replace("\n", "\r\n") + "\r\n" | ||||
|           | git clone %s | ||||
|         """.stripMargin.format(placeholderAddress).replace("\n", "\r\n") + "\r\n" | ||||
|       err.write(Constants.encode(message)) | ||||
|       err.flush() | ||||
|       in.close() | ||||
| @@ -40,7 +43,7 @@ class NoShell(sshAddress: SshAddress) extends Factory[Command] { | ||||
|       callback.onExit(127) | ||||
|     } | ||||
|  | ||||
|     override def destroy(): Unit = {} | ||||
|     override def destroy(channel: ChannelSession): Unit = {} | ||||
|  | ||||
|     override def setInputStream(in: InputStream): Unit = { | ||||
|       this.in = in | ||||
|   | ||||
| @@ -8,13 +8,13 @@ import gitbucket.core.model.Profile.profile.blockingApi._ | ||||
| import gitbucket.core.ssh.PublicKeyAuthenticator.AuthType | ||||
| import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator | ||||
| import org.apache.sshd.server.session.ServerSession | ||||
| import org.apache.sshd.common.AttributeStore | ||||
| import org.apache.sshd.common.AttributeRepository | ||||
| import org.slf4j.LoggerFactory | ||||
|  | ||||
| object PublicKeyAuthenticator { | ||||
|  | ||||
|   // put in the ServerSession here to be read by GitCommand later | ||||
|   private val authTypeSessionKey = new AttributeStore.AttributeKey[AuthType] | ||||
|   private val authTypeSessionKey = new AttributeRepository.AttributeKey[AuthType] | ||||
|  | ||||
|   def putAuthType(serverSession: ServerSession, authType: AuthType): Unit = | ||||
|     serverSession.setAttribute(authTypeSessionKey, authType) | ||||
|   | ||||
| @@ -1,8 +1,7 @@ | ||||
| package gitbucket.core.ssh | ||||
|  | ||||
| import java.util.concurrent.atomic.AtomicBoolean | ||||
| import java.util.concurrent.atomic.AtomicReference | ||||
| import javax.servlet.{ServletContextEvent, ServletContextListener} | ||||
|  | ||||
| import gitbucket.core.service.SystemSettingsService | ||||
| import gitbucket.core.service.SystemSettingsService.SshAddress | ||||
| import gitbucket.core.util.Directory | ||||
| @@ -11,40 +10,48 @@ import org.slf4j.LoggerFactory | ||||
|  | ||||
| object SshServer { | ||||
|   private val logger = LoggerFactory.getLogger(SshServer.getClass) | ||||
|   private val server = org.apache.sshd.server.SshServer.setUpDefaultServer() | ||||
|   private val active = new AtomicBoolean(false) | ||||
|   private val server = new AtomicReference[org.apache.sshd.server.SshServer](null) | ||||
|  | ||||
|   private def configure(sshAddress: SshAddress, baseUrl: String) = { | ||||
|     server.setPort(sshAddress.port) | ||||
|   private def configure( | ||||
|     bindAddress: SshAddress, | ||||
|     publicAddress: SshAddress, | ||||
|     baseUrl: String | ||||
|   ): org.apache.sshd.server.SshServer = { | ||||
|     val server = org.apache.sshd.server.SshServer.setUpDefaultServer() | ||||
|     server.setPort(bindAddress.port) | ||||
|     val provider = new SimpleGeneratorHostKeyProvider( | ||||
|       java.nio.file.Paths.get(s"${Directory.GitBucketHome}/gitbucket.ser") | ||||
|     ) | ||||
|     provider.setAlgorithm("RSA") | ||||
|     provider.setOverwriteAllowed(false) | ||||
|     server.setKeyPairProvider(provider) | ||||
|     server.setPublickeyAuthenticator(new PublicKeyAuthenticator(sshAddress.genericUser)) | ||||
|     server.setPublickeyAuthenticator(new PublicKeyAuthenticator(bindAddress.genericUser)) | ||||
|     server.setCommandFactory( | ||||
|       new GitCommandFactory(baseUrl, Some(s"${sshAddress.genericUser}@${sshAddress.host}:${sshAddress.port}")) | ||||
|       new GitCommandFactory(baseUrl, publicAddress) | ||||
|     ) | ||||
|     server.setShellFactory(new NoShell(sshAddress)) | ||||
|     server.setShellFactory(new NoShell(publicAddress)) | ||||
|     server | ||||
|   } | ||||
|  | ||||
|   def start(sshAddress: SshAddress, baseUrl: String) = { | ||||
|     if (active.compareAndSet(false, true)) { | ||||
|       configure(sshAddress, baseUrl) | ||||
|       server.start() | ||||
|       logger.info(s"Start SSH Server Listen on ${server.getPort}") | ||||
|   def start(bindAddress: SshAddress, publicAddress: SshAddress, baseUrl: String): Unit = { | ||||
|     this.server.synchronized { | ||||
|       val server = configure(bindAddress, publicAddress, baseUrl) | ||||
|       if (this.server.compareAndSet(null, server)) { | ||||
|         server.start() | ||||
|         logger.info(s"Start SSH Server Listen on ${server.getPort}") | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   def stop() = { | ||||
|     if (active.compareAndSet(true, false)) { | ||||
|       server.stop(true) | ||||
|       logger.info("SSH Server is stopped.") | ||||
|   def stop(): Unit = { | ||||
|     this.server.synchronized { | ||||
|       val server = this.server.getAndSet(null) | ||||
|       if (server != null) { | ||||
|         server.stop() | ||||
|         logger.info("SSH Server is stopped.") | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   def isActive = active.get | ||||
| } | ||||
|  | ||||
| /* | ||||
| @@ -59,13 +66,14 @@ class SshServerListener extends ServletContextListener with SystemSettingsServic | ||||
|  | ||||
|   override def contextInitialized(sce: ServletContextEvent): Unit = { | ||||
|     val settings = loadSystemSettings() | ||||
|     if (settings.sshAddress.isDefined && settings.baseUrl.isEmpty) { | ||||
|     if (settings.sshBindAddress.isDefined && settings.baseUrl.isEmpty) { | ||||
|       logger.error("Could not start SshServer because the baseUrl is not configured.") | ||||
|     } | ||||
|     for { | ||||
|       sshAddress <- settings.sshAddress | ||||
|       bindAddress <- settings.sshBindAddress | ||||
|       publicAddress <- settings.sshPublicAddress | ||||
|       baseUrl <- settings.baseUrl | ||||
|     } SshServer.start(sshAddress, baseUrl) | ||||
|     } SshServer.start(bindAddress, publicAddress, baseUrl) | ||||
|   } | ||||
|  | ||||
|   override def contextDestroyed(sce: ServletContextEvent): Unit = { | ||||
|   | ||||
| @@ -22,9 +22,7 @@ object Implicits { | ||||
|   implicit def request2Session(implicit request: HttpServletRequest): JdbcBackend#Session = Database.getSession(request) | ||||
|  | ||||
|   implicit def context2ApiJsonFormatContext(implicit context: Context): JsonFormat.Context = | ||||
|     JsonFormat.Context(context.baseUrl, context.settings.sshAddress.map { x => | ||||
|       s"${x.genericUser}@${x.host}:${x.port}" | ||||
|     }) | ||||
|     JsonFormat.Context(context.baseUrl, context.settings.sshUrl) | ||||
|  | ||||
|   implicit class RichSeq[A](private val seq: Seq[A]) extends AnyVal { | ||||
|  | ||||
|   | ||||
| @@ -228,10 +228,11 @@ object JGitUtil { | ||||
|    * | ||||
|    * @param name the tag name | ||||
|    * @param time the tagged date | ||||
|    * @param id the commit id | ||||
|    * @param commitId the commit id | ||||
|    * @param message the message of the tagged commit | ||||
|    * @param objectId the tag object id | ||||
|    */ | ||||
|   case class TagInfo(name: String, time: Date, id: String, message: String) | ||||
|   case class TagInfo(name: String, time: Date, commitId: String, message: String, objectId: String) | ||||
|  | ||||
|   /** | ||||
|    * The submodule data | ||||
| @@ -347,7 +348,8 @@ object JGitUtil { | ||||
|                     ref.getName.stripPrefix("refs/tags/"), | ||||
|                     revCommit.getCommitterIdent.getWhen, | ||||
|                     revCommit.getName, | ||||
|                     revCommit.getShortMessage | ||||
|                     revCommit.getShortMessage, | ||||
|                     ref.getObjectId.getName | ||||
|                   ) | ||||
|                 ) | ||||
|               } catch { | ||||
| @@ -512,7 +514,7 @@ object JGitUtil { | ||||
|   /** | ||||
|    * Returns the first line of the commit message. | ||||
|    */ | ||||
|   def getSummaryMessage(fullMessage: String, shortMessage: String): String = { | ||||
|   private def getSummaryMessage(fullMessage: String, shortMessage: String): String = { | ||||
|     val i = fullMessage.trim.indexOf('\n') | ||||
|     val firstLine = if (i >= 0) fullMessage.trim.substring(0, i).trim else fullMessage | ||||
|     if (firstLine.length > shortMessage.length) shortMessage else firstLine | ||||
|   | ||||
| @@ -19,22 +19,40 @@ | ||||
|   <label class="checkbox"> | ||||
|     <input type="checkbox" id="sshEnabled" name="ssh.enabled"@if(context.settings.ssh.enabled){ checked}/> | ||||
|     Enable SSH access to git repository | ||||
|     <span class="muted normal">(Both SSH host and Base URL are required if SSH access is enabled)</span> | ||||
|     <span class="muted normal">(Both SSH bind host and Base URL are required if SSH access is enabled)</span> | ||||
|   </label> | ||||
| </fieldset> | ||||
| <div class="ssh"> | ||||
|   <div class="form-group"> | ||||
|     <label class="control-label col-md-2" for="sshHost">SSH host</label> | ||||
|     <div class="col-md-10"> | ||||
|       <input type="text" id="sshHost" name="ssh.host" class="form-control" value="@context.settings.ssh.sshHost"/> | ||||
|       <span id="error-ssh_host" class="error"></span> | ||||
|   <div class="bindAddress"> | ||||
|     <div class="form-group"> | ||||
|       <label class="control-label col-md-2" for="sshBindHost">SSH bind host</label> | ||||
|       <div class="col-md-10"> | ||||
|         <input type="text" id="sshBindHost" name="ssh.bindAddress.host" class="form-control" value="@context.settings.ssh.bindAddress.map(_.host)"/> | ||||
|         <span id="error-ssh_bindAddress_host" class="error"></span> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="form-group"> | ||||
|       <label class="control-label col-md-2" for="sshBindPort">SSH bind port</label> | ||||
|       <div class="col-md-10"> | ||||
|         <input type="text" id="sshBindPort" name="ssh.bindAddress.port" class="form-control" value="@context.settings.ssh.bindAddress.map(_.port)"/> | ||||
|         <span id="error-ssh_bindAddress_port" class="error"></span> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
|   <div class="form-group"> | ||||
|     <label class="control-label col-md-2" for="sshPort">SSH port</label> | ||||
|     <div class="col-md-10"> | ||||
|       <input type="text" id="sshPort" name="ssh.port" class="form-control" value="@context.settings.ssh.sshPort"/> | ||||
|       <span id="error-ssh_port" class="error"></span> | ||||
|   <div class="publicAddress"> | ||||
|     <div class="form-group"> | ||||
|       <label class="control-label col-md-2" for="sshPublicHost">SSH public host</label> | ||||
|       <div class="col-md-10"> | ||||
|         <input type="text" id="sshPublicHost" name="ssh.publicAddress.host" class="form-control" value="@context.settings.ssh.publicAddress.map(_.host)"/> | ||||
|         <span id="error-ssh_publicAddress_host" class="error"></span> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="form-group"> | ||||
|       <label class="control-label col-md-2" for="sshPublicPort">SSH public port</label> | ||||
|       <div class="col-md-10"> | ||||
|         <input type="text" id="sshPublicPort" name="ssh.publicAddress.port" class="form-control" value="@context.settings.ssh.publicAddress.map(_.port)"/> | ||||
|         <span id="error-ssh_publicAddress_port" class="error"></span> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </div> | ||||
|   | ||||
| @@ -40,8 +40,6 @@ | ||||
|     </div> | ||||
|   </div> | ||||
| </div> | ||||
| <link  href="@helpers.assets("/vendors/google-code-prettify/prettify.css")" type="text/css" rel="stylesheet"/> | ||||
| <script src="@helpers.assets("/vendors/google-code-prettify/prettify.js")"></script> | ||||
| <script> | ||||
| $(function(){ | ||||
|   @if(elastic){ | ||||
|   | ||||
| @@ -16,7 +16,7 @@ | ||||
|           <td> | ||||
|             <div class="col-md-2 text-right"> | ||||
|               <a href="@helpers.url(repository)/tree/@helpers.urlEncode(tag.name)" class="strong"><i class="octicon octicon-tag"></i>@tag.name</a><br> | ||||
|               <a href="@helpers.url(repository)/commit/@tag.id" class="monospace muted"><i class="octicon octicon-git-commit"></i>@tag.id.substring(0, 7)</a><br> | ||||
|               <a href="@helpers.url(repository)/commit/@tag.commitId" class="monospace muted"><i class="octicon octicon-git-commit"></i>@tag.commitId.substring(0, 7)</a><br> | ||||
|               <span class="muted">@gitbucket.core.helper.html.datetimeago(tag.time)</span> | ||||
|             </div> | ||||
|             <div class="col-md-10" style="border-left: 1px solid #eee"> | ||||
|   | ||||
| @@ -10,7 +10,7 @@ | ||||
|         @defining(repository.tags.find(_.name == release.tag)){ tag => | ||||
|           @tag.map { tag => | ||||
|             <a href="@helpers.url(repository)/tree/@helpers.urlEncode(tag.name)" class="strong"><i class="octicon octicon-tag"></i>@tag.name</a><br> | ||||
|             <a href="@helpers.url(repository)/commit/@tag.id" class="monospace muted"><i class="octicon octicon-git-commit"></i>@tag.id.substring(0, 7)</a><br> | ||||
|             <a href="@helpers.url(repository)/commit/@tag.commitId" class="monospace muted"><i class="octicon octicon-git-commit"></i>@tag.commitId.substring(0, 7)</a><br> | ||||
|             <span class="muted">@gitbucket.core.helper.html.datetimeago(tag.time)</span> | ||||
|           } | ||||
|         } | ||||
|   | ||||
| @@ -244,7 +244,7 @@ function updateHighlighting() { | ||||
|   const isDark = @{highlighterTheme.contains("dark").toString}; | ||||
|   if (hash.match(/#L\d+(-L\d+)?/)) { | ||||
|     if (isDark) { | ||||
|       $('li.highlight').removeClass('highlight-dark'); | ||||
|       $('li.highlight-dark').removeClass('highlight-dark'); | ||||
|     } else { | ||||
|       $('li.highlight').removeClass('highlight'); | ||||
|     } | ||||
|   | ||||
							
								
								
									
										29
									
								
								src/test/resources/logback-test.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/test/resources/logback-test.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <configuration> | ||||
|   <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | ||||
|     <encoder> | ||||
|       <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> | ||||
|     </encoder> | ||||
|   </appender> | ||||
|  | ||||
|   <!-- | ||||
|   <appender name="FILE" class="ch.qos.logback.core.FileAppender"> | ||||
|     <file>gitbucket.log</file> | ||||
|     <encoder> | ||||
|       <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> | ||||
|     </encoder> | ||||
|   </appender> | ||||
|   --> | ||||
|  | ||||
|   <logger name="gitbucket" level="DEBUG"/> | ||||
|   <root level="WARN"> | ||||
|     <appender-ref ref="STDOUT" /> | ||||
|   </root> | ||||
|  | ||||
|   <!-- | ||||
|   <logger name="service.WebHookService" level="DEBUG" /> | ||||
|   <logger name="servlet" level="DEBUG" /> | ||||
|   <logger name="scala.slick.jdbc.JdbcBackend.statement" level="DEBUG" /> | ||||
|   --> | ||||
|  | ||||
| </configuration> | ||||
| @@ -52,6 +52,8 @@ class TestingGitBucketServer(val port: Int = 19999) extends AutoCloseable { | ||||
|   def client(login: String, password: String): GitHub = | ||||
|     GitHub.connectToEnterprise(s"http://localhost:${port}/api/v3", login, password) | ||||
|  | ||||
|   def getDirectory(): File = dir | ||||
|  | ||||
|   private def addStatisticsHandler(handler: Handler) = { // The graceful shutdown is implemented via the statistics handler. | ||||
|     // See the following: https://bugs.eclipse.org/bugs/show_bug.cgi?id=420142 | ||||
|     val statisticsHandler = new StatisticsHandler | ||||
|   | ||||
| @@ -2,10 +2,13 @@ package gitbucket.core.api | ||||
|  | ||||
| import gitbucket.core.TestingGitBucketServer | ||||
| import org.apache.commons.io.IOUtils | ||||
| import org.eclipse.jgit.api.Git | ||||
| import org.scalatest.funsuite.AnyFunSuite | ||||
|  | ||||
| import scala.util.Using | ||||
| import org.kohsuke.github.{GHCommitState, GitHub} | ||||
| import org.kohsuke.github.GHCommitState | ||||
|  | ||||
| import java.io.File | ||||
|  | ||||
| /** | ||||
|  * Need to run `sbt package` before running this test. | ||||
| @@ -134,6 +137,27 @@ class ApiIntegrationTest extends AnyFunSuite { | ||||
|         assert(statusList.get(1).getState == GHCommitState.FAILURE) | ||||
|         assert(statusList.get(1).getContext == "context") | ||||
|       } | ||||
|  | ||||
|       // get master ref | ||||
|       { | ||||
|         val ref = repo.getRef("heads/master") | ||||
|         assert(ref.getRef == "refs/heads/master") | ||||
|         assert( | ||||
|           ref.getUrl.toString == "http://localhost:19999/api/v3/repos/root/create_status_test/git/refs/heads/master" | ||||
|         ) | ||||
|         assert(ref.getObject.getType == "commit") | ||||
|       } | ||||
|  | ||||
|       // get tag v1.0 | ||||
|       { | ||||
|         Using.resource(Git.open(new File(server.getDirectory(), "repositories/root/create_status_test"))) { git => | ||||
|           git.tag().setName("v1.0").call() | ||||
|         } | ||||
|         val ref = repo.getRef("tags/v1.0") | ||||
|         assert(ref.getRef == "refs/tags/v1.0") | ||||
|         assert(ref.getUrl.toString == "http://localhost:19999/api/v3/repos/root/create_status_test/git/refs/tags/v1.0") | ||||
|         assert(ref.getObject.getType == "tag") | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -83,8 +83,20 @@ object ApiSpecModels { | ||||
|     milestoneCount = 1, | ||||
|     branchList = Seq("master", "develop"), | ||||
|     tags = Seq( | ||||
|       TagInfo(name = "v1.0", time = date("2015-05-05T23:40:27Z"), id = "id1", message = "1.0 released"), | ||||
|       TagInfo(name = "v2.0", time = date("2016-05-05T23:40:27Z"), id = "id2", message = "2.0 released") | ||||
|       TagInfo( | ||||
|         name = "v1.0", | ||||
|         time = date("2015-05-05T23:40:27Z"), | ||||
|         commitId = "id1", | ||||
|         message = "1.0 released", | ||||
|         objectId = "id1" | ||||
|       ), | ||||
|       TagInfo( | ||||
|         name = "v2.0", | ||||
|         time = date("2016-05-05T23:40:27Z"), | ||||
|         commitId = "id2", | ||||
|         message = "2.0 released", | ||||
|         objectId = "id2" | ||||
|       ) | ||||
|     ), | ||||
|     managers = Seq("myboss") | ||||
|   ) | ||||
| @@ -432,9 +444,29 @@ object ApiSpecModels { | ||||
|  | ||||
|   val apiPusher = ApiPusher(account) | ||||
|  | ||||
|   val apiRef = ApiRef( | ||||
|     ref = "refs/heads/featureA", | ||||
|     `object` = ApiObject(sha1) | ||||
|   //have both urls as https, as the expected samples are using https | ||||
|   val gitHubContext = JsonFormat.Context("https://api.github.com", Some("https://api.github.com")) | ||||
|  | ||||
|   val apiRefHeadsMaster = ApiRef( | ||||
|     ref = "refs/heads/master", | ||||
|     url = ApiPath("/repos/gitbucket/gitbucket/git/refs/heads/master"), | ||||
|     node_id = "MDM6UmVmOTM1MDc0NjpyZWZzL2hlYWRzL21hc3Rlcg==", | ||||
|     `object` = ApiRefCommit( | ||||
|       sha = "6b2d124d092402f2c2b7131caada05ead9e7de6d", | ||||
|       `type` = "commit", | ||||
|       url = ApiPath("/repos/gitbucket/gitbucket/git/commits/6b2d124d092402f2c2b7131caada05ead9e7de6d") | ||||
|     ) | ||||
|   ) | ||||
|  | ||||
|   val apiRefTag = ApiRef( | ||||
|     ref = "refs/tags/1.0", | ||||
|     url = ApiPath("/repos/gitbucket/gitbucket/git/refs/tags/1.0"), | ||||
|     node_id = "MDM6UmVmOTM1MDc0NjpyZWZzL3RhZ3MvMS4w", | ||||
|     `object` = ApiRefCommit( | ||||
|       sha = "1f164ecf2f59190afc8d7204a221c739e707df4c", | ||||
|       `type` = "tag", | ||||
|       url = ApiPath("/repos/gitbucket/gitbucket/git/tags/1f164ecf2f59190afc8d7204a221c739e707df4c") | ||||
|     ) | ||||
|   ) | ||||
|  | ||||
|   val assetFileName = "010203040a0b0c0d" | ||||
| @@ -765,8 +797,33 @@ object ApiSpecModels { | ||||
|  | ||||
|   val jsonPusher = """{"name":"octocat","email":"octocat@example.com"}""" | ||||
|  | ||||
|   //I checked all refs in gitbucket repo, and there appears to be only type "commit" and type "tag" | ||||
|   val jsonRef = """{"ref":"refs/heads/featureA","object":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}""" | ||||
|  | ||||
|   val jsonRefHeadsMaster = | ||||
|     """{ | ||||
|       |"ref": "refs/heads/master", | ||||
|       |"node_id": "MDM6UmVmOTM1MDc0NjpyZWZzL2hlYWRzL21hc3Rlcg==", | ||||
|       |"url": "https://api.github.com/repos/gitbucket/gitbucket/git/refs/heads/master", | ||||
|       |"object": { | ||||
|       |"sha": "6b2d124d092402f2c2b7131caada05ead9e7de6d", | ||||
|       |"type": "commit", | ||||
|       |"url": "https://api.github.com/repos/gitbucket/gitbucket/git/commits/6b2d124d092402f2c2b7131caada05ead9e7de6d" | ||||
|       |} | ||||
|       |}""".stripMargin | ||||
|  | ||||
|   val jsonRefTag = | ||||
|     """{ | ||||
|       |"ref": "refs/tags/1.0", | ||||
|       |"node_id": "MDM6UmVmOTM1MDc0NjpyZWZzL3RhZ3MvMS4w", | ||||
|       |"url": "https://api.github.com/repos/gitbucket/gitbucket/git/refs/tags/1.0", | ||||
|       |"object": { | ||||
|       |"sha": "1f164ecf2f59190afc8d7204a221c739e707df4c", | ||||
|       |"type": "tag", | ||||
|       |"url": "https://api.github.com/repos/gitbucket/gitbucket/git/tags/1f164ecf2f59190afc8d7204a221c739e707df4c" | ||||
|       |} | ||||
|       |}""".stripMargin | ||||
|  | ||||
|   val jsonReleaseAsset = | ||||
|     s"""{ | ||||
|       |"name":"release.zip", | ||||
|   | ||||
| @@ -8,6 +8,12 @@ class JsonFormatSpec extends AnyFunSuite { | ||||
|   implicit val format = JsonFormat.jsonFormats | ||||
|  | ||||
|   private def expected(json: String) = json.replaceAll("\n", "") | ||||
|   def normalizeJson(json: String) = { | ||||
|     org.json4s.jackson.parseJson(json) | ||||
|   } | ||||
|   def assertEqualJson(actual: String, expected: String) = { | ||||
|     assert(normalizeJson(actual) == normalizeJson(expected)) | ||||
|   } | ||||
|  | ||||
|   test("apiUser") { | ||||
|     assert(JsonFormat(apiUser) == expected(jsonUser)) | ||||
| @@ -76,8 +82,11 @@ class JsonFormatSpec extends AnyFunSuite { | ||||
|   test("apiPusher") { | ||||
|     assert(JsonFormat(apiPusher) == expected(jsonPusher)) | ||||
|   } | ||||
|   test("apiRef") { | ||||
|     assert(JsonFormat(apiRef) == expected(jsonRef)) | ||||
|   test("apiRefHead") { | ||||
|     assertEqualJson(JsonFormat(apiRefHeadsMaster)(gitHubContext), jsonRefHeadsMaster) | ||||
|   } | ||||
|   test("apiRefTag") { | ||||
|     assertEqualJson(JsonFormat(apiRefTag)(gitHubContext), jsonRefTag) | ||||
|   } | ||||
|   test("apiReleaseAsset") { | ||||
|     assert(JsonFormat(apiReleaseAsset) == expected(jsonReleaseAsset)) | ||||
|   | ||||
| @@ -47,8 +47,8 @@ trait ServiceSpecBase { | ||||
|       limitVisibleRepositories = false, | ||||
|       ssh = Ssh( | ||||
|         enabled = false, | ||||
|         sshHost = None, | ||||
|         sshPort = None | ||||
|         bindAddress = None, | ||||
|         publicAddress = None | ||||
|       ), | ||||
|       useSMTP = false, | ||||
|       smtp = None, | ||||
|   | ||||
| @@ -0,0 +1,113 @@ | ||||
| package gitbucket.core.service | ||||
|  | ||||
| import gitbucket.core.service.SystemSettingsService.SshAddress | ||||
| import org.scalatest.matchers.should.Matchers | ||||
| import org.scalatest.wordspec.AnyWordSpecLike | ||||
|  | ||||
| import java.util.Properties | ||||
|  | ||||
| class SystemSettingsServiceSpec extends AnyWordSpecLike with Matchers { | ||||
|  | ||||
|   "loadSystemSettings" should { | ||||
|     "read old-style ssh configuration" in new SystemSettingsService { | ||||
|       val props = new Properties() | ||||
|       props.setProperty("ssh", "true") | ||||
|       props.setProperty("ssh.host", "127.0.0.1") | ||||
|       props.setProperty("ssh.port", "8022") | ||||
|  | ||||
|       val settings = loadSystemSettings(props) | ||||
|       settings.ssh.enabled shouldBe true | ||||
|       settings.ssh.bindAddress shouldBe Some(SshAddress("127.0.0.1", 8022, "git")) | ||||
|       settings.ssh.publicAddress shouldBe settings.ssh.bindAddress | ||||
|     } | ||||
|     "read new-style ssh configuration" in new SystemSettingsService { | ||||
|       val props = new Properties() | ||||
|       props.setProperty("ssh", "true") | ||||
|       props.setProperty("ssh.bindAddress.host", "127.0.0.1") | ||||
|       props.setProperty("ssh.bindAddress.port", "8022") | ||||
|       props.setProperty("ssh.publicAddress.host", "code.these.solutions") | ||||
|       props.setProperty("ssh.publicAddress.port", "22") | ||||
|  | ||||
|       val settings = loadSystemSettings(props) | ||||
|       settings.ssh.enabled shouldBe true | ||||
|       settings.ssh.bindAddress shouldBe Some(SshAddress("127.0.0.1", 8022, "git")) | ||||
|       settings.ssh.publicAddress shouldBe Some(SshAddress("code.these.solutions", 22, "git")) | ||||
|     } | ||||
|     "default the ssh port if not specified" in new SystemSettingsService { | ||||
|       val props = new Properties() | ||||
|       props.setProperty("ssh", "true") | ||||
|       props.setProperty("ssh.bindAddress.host", "127.0.0.1") | ||||
|       props.setProperty("ssh.publicAddress.host", "code.these.solutions") | ||||
|  | ||||
|       val settings = loadSystemSettings(props) | ||||
|       settings.ssh.enabled shouldBe true | ||||
|       settings.ssh.bindAddress shouldBe Some(SshAddress("127.0.0.1", 29418, "git")) | ||||
|       settings.ssh.publicAddress shouldBe Some(SshAddress("code.these.solutions", 22, "git")) | ||||
|     } | ||||
|     "default the public address if not specified" in new SystemSettingsService { | ||||
|       val props = new Properties() | ||||
|       props.setProperty("ssh", "true") | ||||
|       props.setProperty("ssh.bindAddress.host", "127.0.0.1") | ||||
|       props.setProperty("ssh.bindAddress.port", "8022") | ||||
|  | ||||
|       val settings = loadSystemSettings(props) | ||||
|       settings.ssh.enabled shouldBe true | ||||
|       settings.ssh.bindAddress shouldBe Some(SshAddress("127.0.0.1", 8022, "git")) | ||||
|       settings.ssh.publicAddress shouldBe settings.ssh.bindAddress | ||||
|     } | ||||
|     "return addresses even if ssh is not enabled" in new SystemSettingsService { | ||||
|       val props = new Properties() | ||||
|       props.setProperty("ssh", "false") | ||||
|       props.setProperty("ssh.bindAddress.host", "127.0.0.1") | ||||
|       props.setProperty("ssh.bindAddress.port", "8022") | ||||
|       props.setProperty("ssh.publicAddress.host", "code.these.solutions") | ||||
|       props.setProperty("ssh.publicAddress.port", "22") | ||||
|  | ||||
|       val settings = loadSystemSettings(props) | ||||
|       settings.ssh.enabled shouldBe false | ||||
|       settings.ssh.bindAddress shouldNot be(empty) | ||||
|       settings.ssh.publicAddress shouldNot be(empty) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   "SshAddress" can { | ||||
|     trait MockContext { | ||||
|       val host = "code.these.solutions" | ||||
|       val port = 1337 | ||||
|       val user = "git" | ||||
|       lazy val sshAddress = SshAddress(host, port, user) | ||||
|     } | ||||
|     "isDefaultPort" which { | ||||
|       "returns true if using port 22" in new MockContext { | ||||
|         override val port = 22 | ||||
|         sshAddress.isDefaultPort shouldBe true | ||||
|       } | ||||
|       "returns false if using a different port" in new MockContext { | ||||
|         override val port = 8022 | ||||
|         sshAddress.isDefaultPort shouldBe false | ||||
|       } | ||||
|     } | ||||
|     "getUrl" which { | ||||
|       "returns the port number when not using port 22" in new MockContext { | ||||
|         override val port = 8022 | ||||
|         sshAddress.getUrl shouldBe "git@code.these.solutions:8022" | ||||
|       } | ||||
|       "leaves off the port number when using port 22" in new MockContext { | ||||
|         override val port = 22 | ||||
|         sshAddress.getUrl shouldBe "git@code.these.solutions" | ||||
|       } | ||||
|     } | ||||
|     "getUrl for owner and repo" which { | ||||
|       "returns an ssh-protocol url when not using port 22" in new MockContext { | ||||
|         override val port = 8022 | ||||
|         sshAddress.getUrl("np-hard", "quantum-crypto-cracker") shouldBe | ||||
|           "ssh://git@code.these.solutions:8022/np-hard/quantum-crypto-cracker.git" | ||||
|       } | ||||
|       "returns a bare-protocol url when using port 22" in new MockContext { | ||||
|         override val port = 22 | ||||
|         sshAddress.getUrl("syntactic", "brace-stretcher") shouldBe | ||||
|           "git@code.these.solutions:syntactic/brace-stretcher.git" | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -1,38 +1,102 @@ | ||||
| package gitbucket.core.ssh | ||||
|  | ||||
| import gitbucket.core.service.SystemSettingsService.SshAddress | ||||
| import org.apache.sshd.server.channel.ChannelSession | ||||
| import org.apache.sshd.server.shell.UnknownCommand | ||||
| import org.scalatest.funspec.AnyFunSpec | ||||
| import org.scalatest.matchers.should.Matchers | ||||
| import org.scalatest.wordspec.AnyWordSpec | ||||
|  | ||||
| class GitCommandFactorySpec extends AnyFunSpec { | ||||
| class GitCommandFactorySpec extends AnyWordSpec with Matchers { | ||||
|  | ||||
|   val factory = new GitCommandFactory("http://localhost:8080", None) | ||||
|  | ||||
|   describe("createCommand") { | ||||
|     it("should return GitReceivePack when command is git-receive-pack") { | ||||
|       assert(factory.createCommand("git-receive-pack '/owner/repo.git'").isInstanceOf[DefaultGitReceivePack] == true) | ||||
|       assert( | ||||
|         factory.createCommand("git-receive-pack '/owner/repo.wiki.git'").isInstanceOf[DefaultGitReceivePack] == true | ||||
|       ) | ||||
|     } | ||||
|     it("should return GitUploadPack when command is git-upload-pack") { | ||||
|       assert(factory.createCommand("git-upload-pack '/owner/repo.git'").isInstanceOf[DefaultGitUploadPack] == true) | ||||
|       assert(factory.createCommand("git-upload-pack '/owner/repo.wiki.git'").isInstanceOf[DefaultGitUploadPack] == true) | ||||
|     } | ||||
|     it("should return UnknownCommand when command is not git-(upload|receive)-pack") { | ||||
|       assert(factory.createCommand("git- '/owner/repo.git'").isInstanceOf[UnknownCommand] == true) | ||||
|       assert(factory.createCommand("git-pack '/owner/repo.git'").isInstanceOf[UnknownCommand] == true) | ||||
|       assert(factory.createCommand("git-a-pack '/owner/repo.git'").isInstanceOf[UnknownCommand] == true) | ||||
|       assert(factory.createCommand("git-up-pack '/owner/repo.git'").isInstanceOf[UnknownCommand] == true) | ||||
|       assert(factory.createCommand("\ngit-upload-pack '/owner/repo.git'").isInstanceOf[UnknownCommand] == true) | ||||
|     } | ||||
|     it("should return UnknownCommand when git command has no valid arguments") { | ||||
|       // must be: git-upload-pack '/owner/repository_name.git' | ||||
|       assert(factory.createCommand("git-upload-pack").isInstanceOf[UnknownCommand] == true) | ||||
|       assert(factory.createCommand("git-upload-pack /owner/repo.git").isInstanceOf[UnknownCommand] == true) | ||||
|       assert(factory.createCommand("git-upload-pack 'owner/repo.git'").isInstanceOf[UnknownCommand] == true) | ||||
|       assert(factory.createCommand("git-upload-pack '/ownerrepo.git'").isInstanceOf[UnknownCommand] == true) | ||||
|       assert(factory.createCommand("git-upload-pack '/owner/repo.wiki'").isInstanceOf[UnknownCommand] == true) | ||||
|     } | ||||
|   trait MockContext { | ||||
|     val baseUrl = "https://some.example.tech:8080/code-context" | ||||
|     val sshHost = "localhost" | ||||
|     val sshPort = 2222 | ||||
|     lazy val factory = new GitCommandFactory(baseUrl, SshAddress(sshHost, sshPort, "git")) | ||||
|   } | ||||
|  | ||||
|   "createCommand" when { | ||||
|     val channel = new ChannelSession() | ||||
|     "receiving a git-receive-pack command" should { | ||||
|       "return DefaultGitReceivePack" when { | ||||
|         "the path matches owner/repo" in new MockContext { | ||||
|           assert( | ||||
|             factory.createCommand(channel, "git-receive-pack '/owner/repo.git'").isInstanceOf[DefaultGitReceivePack] | ||||
|           ) | ||||
|           assert( | ||||
|             factory | ||||
|               .createCommand(channel, "git-receive-pack '/owner/repo.wiki.git'") | ||||
|               .isInstanceOf[DefaultGitReceivePack] | ||||
|           ) | ||||
|         } | ||||
|         "the leading slash is left off and running on port 22" in new MockContext { | ||||
|           override val sshPort: Int = 22 | ||||
|           assert( | ||||
|             factory.createCommand(channel, "git-receive-pack 'owner/repo.git'").isInstanceOf[DefaultGitReceivePack] | ||||
|           ) | ||||
|           assert( | ||||
|             factory.createCommand(channel, "git-receive-pack 'owner/repo.wiki.git'").isInstanceOf[DefaultGitReceivePack] | ||||
|           ) | ||||
|         } | ||||
|       } | ||||
|       "return UnknownCommand" when { | ||||
|         "the ssh port is not 22 and the leading slash is missing" in new MockContext { | ||||
|           override val sshPort: Int = 1337 | ||||
|           assert(factory.createCommand(channel, "git-receive-pack 'owner/repo.git'").isInstanceOf[UnknownCommand]) | ||||
|           assert(factory.createCommand(channel, "git-receive-pack 'owner/repo.wiki.git'").isInstanceOf[UnknownCommand]) | ||||
|           assert(factory.createCommand(channel, "git-receive-pack 'oranges.git'").isInstanceOf[UnknownCommand]) | ||||
|           assert(factory.createCommand(channel, "git-receive-pack 'apples.git'").isInstanceOf[UnknownCommand]) | ||||
|         } | ||||
|         "the path is malformed" in new MockContext { | ||||
|           assert( | ||||
|             factory.createCommand(channel, "git-receive-pack '/owner/repo/wrong.git'").isInstanceOf[UnknownCommand] | ||||
|           ) | ||||
|           assert(factory.createCommand(channel, "git-receive-pack '/owner:repo.wiki.git'").isInstanceOf[UnknownCommand]) | ||||
|           assert(factory.createCommand(channel, "git-receive-pack '/oranges'").isInstanceOf[UnknownCommand]) | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     "receiving a git-upload-pack command" should { | ||||
|       "return DefaultGitUploadPack" when { | ||||
|         "the path matches owner/repo" in new MockContext { | ||||
|           assert(factory.createCommand(channel, "git-upload-pack '/owner/repo.git'").isInstanceOf[DefaultGitUploadPack]) | ||||
|           assert( | ||||
|             factory.createCommand(channel, "git-upload-pack '/owner/repo.wiki.git'").isInstanceOf[DefaultGitUploadPack] | ||||
|           ) | ||||
|         } | ||||
|         "the leading slash is left off and running on port 22" in new MockContext { | ||||
|           override val sshPort = 22 | ||||
|           assert(factory.createCommand(channel, "git-upload-pack 'owner/repo.git'").isInstanceOf[DefaultGitUploadPack]) | ||||
|           assert( | ||||
|             factory.createCommand(channel, "git-upload-pack 'owner/repo.wiki.git'").isInstanceOf[DefaultGitUploadPack] | ||||
|           ) | ||||
|         } | ||||
|       } | ||||
|       "return UnknownCommand" when { | ||||
|         "the ssh port is not 22 and the leading slash is missing" in new MockContext { | ||||
|           override val sshPort = 1337 | ||||
|           assert(factory.createCommand(channel, "git-upload-pack 'owner/repo.git'").isInstanceOf[UnknownCommand]) | ||||
|           assert(factory.createCommand(channel, "git-upload-pack 'owner/repo.wiki.git'").isInstanceOf[UnknownCommand]) | ||||
|           assert(factory.createCommand(channel, "git-upload-pack 'oranges.git'").isInstanceOf[UnknownCommand]) | ||||
|           assert(factory.createCommand(channel, "git-upload-pack 'apples.git'").isInstanceOf[UnknownCommand]) | ||||
|         } | ||||
|         "the path is malformed" in new MockContext { | ||||
|           assert(factory.createCommand(channel, "git-upload-pack '/owner/repo'").isInstanceOf[UnknownCommand]) | ||||
|           assert(factory.createCommand(channel, "git-upload-pack '/owner:repo.wiki.git'").isInstanceOf[UnknownCommand]) | ||||
|           assert(factory.createCommand(channel, "git-upload-pack '/oranges'").isInstanceOf[UnknownCommand]) | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     "receiving any command not matching git-(receive|upload)-pack" should { | ||||
|       "return UnknownCommand" in new MockContext { | ||||
|         assert(factory.createCommand(channel, "git-destroy-pack '/owner/repo.git'").isInstanceOf[UnknownCommand]) | ||||
|         assert(factory.createCommand(channel, "git-irrigate-pack '/apples.git'").isInstanceOf[UnknownCommand]) | ||||
|         assert(factory.createCommand(channel, "git-force-push '/stolen/nuke.git'").isInstanceOf[UnknownCommand]) | ||||
|         assert(factory.createCommand(channel, "git-delete '/backups.git'").isInstanceOf[UnknownCommand]) | ||||
|         assert(factory.createCommand(channel, "git-pack '/your/bags.git'").isInstanceOf[UnknownCommand]) | ||||
|         assert(factory.createCommand(channel, "git- '/bananas.git'").isInstanceOf[UnknownCommand]) | ||||
|         assert(factory.createCommand(channel, "99 tickets of bugs on the wall").isInstanceOf[UnknownCommand]) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -166,8 +166,8 @@ class AvatarImageProviderSpec extends AnyFunSpec { | ||||
|       limitVisibleRepositories = false, | ||||
|       ssh = Ssh( | ||||
|         enabled = false, | ||||
|         sshHost = None, | ||||
|         sshPort = None | ||||
|         bindAddress = None, | ||||
|         publicAddress = None | ||||
|       ), | ||||
|       useSMTP = false, | ||||
|       smtp = None, | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| package gitbucket.core.view | ||||
|  | ||||
| import gitbucket.core.util.SyntaxSugars | ||||
| import SyntaxSugars._ | ||||
| import org.scalatest.funspec.AnyFunSpec | ||||
|  | ||||
| class PaginationSpec extends AnyFunSpec { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user