mirror of
				https://github.com/gitbucket/gitbucket.git
				synced 2025-10-31 02:25:59 +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: |     steps: | ||||||
|     - uses: actions/checkout@v2 |     - uses: actions/checkout@v2 | ||||||
|     - name: Cache |     - name: Cache | ||||||
|       uses: actions/cache@v2.1.6 |       uses: actions/cache@v2.1.7 | ||||||
|       env: |       env: | ||||||
|         cache-name: cache-sbt-libs |         cache-name: cache-sbt-libs | ||||||
|       with: |       with: | ||||||
|   | |||||||
| @@ -4,4 +4,5 @@ updates.includeScala = true | |||||||
|  |  | ||||||
| updates.pin = [ | updates.pin = [ | ||||||
|   { groupId = "org.eclipse.jetty", version = "9." } |   { groupId = "org.eclipse.jetty", version = "9." } | ||||||
|  |   { groupId = "org.eclipse.jgit", version = "5." } | ||||||
| ] | ] | ||||||
|   | |||||||
| @@ -1,6 +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.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 | ### 4.36.2 - 16 Aug 2021 | ||||||
| - Escape user name in avatar image tag | - 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. | - 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. | - 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 | ### 4.37.0 - 11 Dec 2021 | ||||||
| - Escape user name in avatar image tag | - Enhance Git Reference APIs | ||||||
|  | - Add milestone data to issue list API | ||||||
| ### 4.36.1 - 22 Jul 2021 | - Support "all" in issue list API | ||||||
| - Bump gitbucket-gist-plugin to 4.21.0 | - Support EDDSA in signed commit verification | ||||||
|  | - Support custom SSH url | ||||||
| ### 4.36.0 - 17 Jul 2021 | - Relax max passward length limitation | ||||||
| - Tag selector in the repository viewer | - Relax max webhook url length limitation | ||||||
| - Link issues/pull requests of other repositories |  | ||||||
| - Files and lines can be linked in the diff view |  | ||||||
| - Option to disable XSS protection |  | ||||||
|  |  | ||||||
| See the [change log](CHANGELOG.md) for all of the updates. | 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 Organization = "io.github.gitbucket" | ||||||
| val Name = "gitbucket" | val Name = "gitbucket" | ||||||
| val GitBucketVersion = "4.36.2" | val GitBucketVersion = "4.37.0" | ||||||
| val ScalatraVersion = "2.8.2" | val ScalatraVersion = "2.8.2" | ||||||
| val JettyVersion = "9.4.44.v20210927" | val JettyVersion = "9.4.44.v20210927" | ||||||
| val JgitVersion = "5.13.0.202109080827-r" | val JgitVersion = "5.13.0.202109080827-r" | ||||||
| @@ -42,34 +42,34 @@ libraryDependencies ++= Seq( | |||||||
|   "org.apache.commons"              % "commons-email"                % "1.5", |   "org.apache.commons"              % "commons-email"                % "1.5", | ||||||
|   "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.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", |   "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.4", |   "org.mariadb.jdbc"                % "mariadb-java-client"          % "2.7.4", | ||||||
|   "org.postgresql"                  % "postgresql"                   % "42.3.1", |   "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.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.11", |   "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.15", | ||||||
|   "com.github.zafarkhaja"           % "java-semver"                  % "0.9.0", |   "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", |   "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"                 % "4.0.0" % "test", |   "org.mockito"                     % "mockito-core"                 % "4.1.0" % "test", | ||||||
|   "com.dimafeng"                    %% "testcontainers-scala"        % "0.39.11" % "test", |   "com.dimafeng"                    %% "testcontainers-scala"        % "0.39.12" % "test", | ||||||
|   "org.testcontainers"              % "mysql"                        % "1.16.2" % "test", |   "org.testcontainers"              % "mysql"                        % "1.16.2" % "test", | ||||||
|   "org.testcontainers"              % "postgresql"                   % "1.16.2" % "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.135" % "test" |   "org.kohsuke"                     % "github-api"                   % "1.301" % "test" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| libraryDependencies ~= { | libraryDependencies ~= { | ||||||
|   | |||||||
| @@ -1 +1 @@ | |||||||
| sbt.version=1.5.5 | sbt.version=1.5.6 | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature") | 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.typesafe.sbt" % "sbt-twirl"          % "1.5.1") | ||||||
| addSbtPlugin("com.eed3si9n"     % "sbt-assembly"       % "1.1.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") | ||||||
|   | |||||||
							
								
								
									
										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.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.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 | 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 => |   get("/:owner/:repository/changelog/*...*")(writableUsersOnly { repository => | ||||||
|     val Seq(previousTag, currentTag) = multiParams("splat") |     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 commitLog = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => | ||||||
|       val commits = JGitUtil.getCommitLog(git, previousTagId, currentTag).reverse |       val commits = JGitUtil.getCommitLog(git, previousTagId, currentTag).reverse | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| package gitbucket.core.controller | package gitbucket.core.controller | ||||||
|  |  | ||||||
| import java.io.FileInputStream | import java.io.FileInputStream | ||||||
|  |  | ||||||
| import gitbucket.core.admin.html | import gitbucket.core.admin.html | ||||||
| import gitbucket.core.plugin.PluginRegistry | import gitbucket.core.plugin.PluginRegistry | ||||||
| import gitbucket.core.service.SystemSettingsService._ | import gitbucket.core.service.SystemSettingsService._ | ||||||
| @@ -50,8 +49,20 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase { | |||||||
|     "limitVisibleRepositories" -> trim(label("limitVisibleRepositories", boolean())), |     "limitVisibleRepositories" -> trim(label("limitVisibleRepositories", boolean())), | ||||||
|     "ssh" -> mapping( |     "ssh" -> mapping( | ||||||
|       "enabled" -> trim(label("SSH access", boolean())), |       "enabled" -> trim(label("SSH access", boolean())), | ||||||
|       "host" -> trim(label("SSH host", optional(text()))), |       "bindAddress" -> mapping( | ||||||
|       "port" -> trim(label("SSH port", optional(number()))) |         "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), |     )(Ssh.apply), | ||||||
|     "useSMTP" -> trim(label("SMTP", boolean())), |     "useSMTP" -> trim(label("SMTP", boolean())), | ||||||
|     "smtp" -> optionalIfNotChecked( |     "smtp" -> optionalIfNotChecked( | ||||||
| @@ -116,8 +127,8 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase { | |||||||
|       if (settings.ssh.enabled && settings.baseUrl.isEmpty) { |       if (settings.ssh.enabled && settings.baseUrl.isEmpty) { | ||||||
|         Some("baseUrl" -> "Base URL is required if SSH access is enabled.") |         Some("baseUrl" -> "Base URL is required if SSH access is enabled.") | ||||||
|       } else None, |       } else None, | ||||||
|       if (settings.ssh.enabled && settings.ssh.sshHost.isEmpty) { |       if (settings.ssh.enabled && settings.ssh.bindAddress.isEmpty) { | ||||||
|         Some("sshHost" -> "SSH host is required if SSH access is enabled.") |         Some("ssh.bindAddress.host" -> "SSH bind host is required if SSH access is enabled.") | ||||||
|       } else None |       } else None | ||||||
|     ).flatten |     ).flatten | ||||||
|   } |   } | ||||||
| @@ -308,12 +319,13 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase { | |||||||
|   post("/admin/system", form)(adminOnly { form => |   post("/admin/system", form)(adminOnly { form => | ||||||
|     saveSystemSettings(form) |     saveSystemSettings(form) | ||||||
|  |  | ||||||
|     if (form.sshAddress != context.settings.sshAddress) { |     if (form.ssh.bindAddress != context.settings.sshBindAddress || form.ssh.publicAddress != context.settings.sshPublicAddress) { | ||||||
|       SshServer.stop() |       SshServer.stop() | ||||||
|       for { |       for { | ||||||
|         sshAddress <- form.sshAddress |         bindAddress <- form.ssh.bindAddress | ||||||
|  |         publicAddress <- form.ssh.publicAddress.orElse(form.ssh.bindAddress) | ||||||
|         baseUrl <- form.baseUrl |         baseUrl <- form.baseUrl | ||||||
|       } SshServer.start(sshAddress, baseUrl) |       } SshServer.start(bindAddress, publicAddress, baseUrl) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     flash.update("info", "System settings has been updated.") |     flash.update("info", "System settings has been updated.") | ||||||
|   | |||||||
| @@ -1,9 +1,10 @@ | |||||||
| package gitbucket.core.controller.api | 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.controller.ControllerBase | ||||||
|  | import gitbucket.core.service.RepositoryService.RepositoryInfo | ||||||
| import gitbucket.core.util.Directory.getRepositoryDir | import gitbucket.core.util.Directory.getRepositoryDir | ||||||
| import gitbucket.core.util.ReferrerAuthenticator |  | ||||||
| import gitbucket.core.util.Implicits._ | import gitbucket.core.util.Implicits._ | ||||||
|  | import gitbucket.core.util.{ReferrerAuthenticator, RepositoryName, WritableUsersAuthenticator} | ||||||
| import org.eclipse.jgit.api.Git | import org.eclipse.jgit.api.Git | ||||||
| import org.eclipse.jgit.lib.ObjectId | import org.eclipse.jgit.lib.ObjectId | ||||||
| import org.eclipse.jgit.lib.RefUpdate.Result | import org.eclipse.jgit.lib.RefUpdate.Result | ||||||
| @@ -14,47 +15,38 @@ import scala.jdk.CollectionConverters._ | |||||||
| import scala.util.Using | import scala.util.Using | ||||||
|  |  | ||||||
| trait ApiGitReferenceControllerBase extends ControllerBase { | trait ApiGitReferenceControllerBase extends ControllerBase { | ||||||
|   self: ReferrerAuthenticator => |   self: ReferrerAuthenticator with WritableUsersAuthenticator => | ||||||
|  |  | ||||||
|   private val logger = LoggerFactory.getLogger(classOf[ApiGitReferenceControllerBase]) |   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 |    * i. Get a reference | ||||||
|    * https://docs.github.com/en/free-pro-team@latest/rest/reference/git#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 => |   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 |   // Some versions of GHE support this path | ||||||
|   get("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { repository => |   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") |     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 |     val revstr = multiParams("splat").head | ||||||
|     Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git => |     getRef(revstr, repository) | ||||||
|       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)) |  | ||||||
|         }) |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /* |   /* | ||||||
|    * ii. Get all references |    * ii. Get all references | ||||||
| @@ -65,17 +57,17 @@ trait ApiGitReferenceControllerBase extends ControllerBase { | |||||||
|    * iii. Create a reference |    * iii. Create a reference | ||||||
|    * https://docs.github.com/en/free-pro-team@latest/rest/reference/git#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 { |     extractFromJsonBody[CreateARef].map { | ||||||
|       data => |       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) |           val ref = git.getRepository.findRef(data.ref) | ||||||
|           if (ref == null) { |           if (ref == null) { | ||||||
|             val update = git.getRepository.updateRef(data.ref) |             val update = git.getRepository.updateRef(data.ref) | ||||||
|             update.setNewObjectId(ObjectId.fromString(data.sha)) |             update.setNewObjectId(ObjectId.fromString(data.sha)) | ||||||
|             val result = update.update() |             val result = update.update() | ||||||
|             result match { |             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()) |               case _          => UnprocessableEntity(result.name()) | ||||||
|             } |             } | ||||||
|           } else { |           } else { | ||||||
| @@ -89,11 +81,11 @@ trait ApiGitReferenceControllerBase extends ControllerBase { | |||||||
|    * iv. Update a reference |    * iv. Update a reference | ||||||
|    * https://docs.github.com/en/free-pro-team@latest/rest/reference/git#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("/") |     val refName = multiParams("splat").mkString("/") | ||||||
|     extractFromJsonBody[UpdateARef].map { |     extractFromJsonBody[UpdateARef].map { | ||||||
|       data => |       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) |           val ref = git.getRepository.findRef(refName) | ||||||
|           if (ref == null) { |           if (ref == null) { | ||||||
|             UnprocessableEntity("Ref does not exist.") |             UnprocessableEntity("Ref does not exist.") | ||||||
| @@ -104,7 +96,7 @@ trait ApiGitReferenceControllerBase extends ControllerBase { | |||||||
|             val result = update.update() |             val result = update.update() | ||||||
|             result match { |             result match { | ||||||
|               case Result.FORCED | Result.FAST_FORWARD | Result.NO_CHANGE => |               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()) |               case _ => UnprocessableEntity(result.name()) | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
| @@ -116,7 +108,7 @@ trait ApiGitReferenceControllerBase extends ControllerBase { | |||||||
|    * v. Delete a reference |    * v. Delete a reference | ||||||
|    * https://docs.github.com/en/free-pro-team@latest/rest/reference/git#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("/") |     val refName = multiParams("splat").mkString("/") | ||||||
|     Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git => |     Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git => | ||||||
|       val ref = git.getRepository.findRef(refName) |       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.api._ | ||||||
| import gitbucket.core.controller.ControllerBase | import gitbucket.core.controller.ControllerBase | ||||||
| import gitbucket.core.service.MilestonesService | import gitbucket.core.service.MilestonesService | ||||||
| import gitbucket.core.service.RepositoryService.RepositoryInfo |  | ||||||
| import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator} | import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator} | ||||||
| import gitbucket.core.util.Implicits._ | import gitbucket.core.util.Implicits._ | ||||||
| import org.scalatra.NoContent | 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.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, getSummaryMessage} | import gitbucket.core.util.JGitUtil.{CommitInfo, FileInfo, getContentFromId, getFileList} | ||||||
| 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,21 +49,19 @@ 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): Option[FileInfo] = { |   private def getFileInfo(git: Git, revision: String, pathStr: String, ignoreCase: Boolean): Option[FileInfo] = { | ||||||
|     val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(revision)) |     val (dirName, fileName) = pathStr.lastIndexOf('/') match { | ||||||
|     getPathObjectId(git, pathStr, revCommit).map { objectId => |       case -1 => | ||||||
|       FileInfo( |         (".", pathStr) | ||||||
|         id = objectId, |       case n => | ||||||
|         isDirectory = false, |         (pathStr.take(n), pathStr.drop(n + 1)) | ||||||
|         name = pathStr.split("/").last, |     } | ||||||
|         path = pathStr.split("/").dropRight(1).mkString("/"), |     if (ignoreCase) { | ||||||
|         message = getSummaryMessage(revCommit.getFullMessage, revCommit.getShortMessage), |       getFileList(git, revision, dirName, maxFiles = context.settings.repositoryViewer.maxFiles) | ||||||
|         commitId = revCommit.getName, |         .find(_.name.toLowerCase.equals(fileName.toLowerCase)) | ||||||
|         time = revCommit.getAuthorIdent.getWhen, |     } else { | ||||||
|         author = revCommit.getAuthorIdent.getName, |       getFileList(git, revision, dirName, maxFiles = context.settings.repositoryViewer.maxFiles) | ||||||
|         mailAddress = revCommit.getAuthorIdent.getEmailAddress, |         .find(_.name.equals(fileName)) | ||||||
|         linkUrl = None |  | ||||||
|       ) |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -76,17 +74,16 @@ 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 | ||||||
|         val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(refStr)) |         getFileInfo(git, refStr, path, ignoreCase) | ||||||
|         getPathObjectId(git, path, revCommit) |           .flatMap { f => | ||||||
|           .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, objectId, largeFile) |             val content = getContentFromId(git, f.id, 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(path) => { |               case "application/vnd.github.v3.html" if isRenderable(f.name) => { | ||||||
|                 contentType = "application/vnd.github.v3.html" |                 contentType = "application/vnd.github.v3.html" | ||||||
|                 content.map { c => |                 content.map { c => | ||||||
|                   List( |                   List( | ||||||
| @@ -117,12 +114,11 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase { | |||||||
|                 } |                 } | ||||||
|               } |               } | ||||||
|               case _ => |               case _ => | ||||||
|                 getFileInfo(git, refStr, path).map { f => |                 Some(JsonFormat(ApiContents(f, RepositoryName(repository), content))) | ||||||
|                   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) | ||||||
| @@ -152,7 +148,9 @@ 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 => | ||||||
|               getFileInfo(git, commit, path) match { |               val fileInfo = getFileInfo(git, commit, path, false) | ||||||
|  |  | ||||||
|  |               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.", | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ import scala.util.Using | |||||||
|  |  | ||||||
| trait ApiRepositoryControllerBase extends ControllerBase { | trait ApiRepositoryControllerBase extends ControllerBase { | ||||||
|   self: RepositoryService |   self: RepositoryService | ||||||
|  |     with ApiGitReferenceControllerBase | ||||||
|     with RepositoryCreationService |     with RepositoryCreationService | ||||||
|     with AccountService |     with AccountService | ||||||
|     with OwnerAuthenticator |     with OwnerAuthenticator | ||||||
| @@ -184,9 +185,11 @@ trait ApiRepositoryControllerBase extends ControllerBase { | |||||||
|    * https://docs.github.com/en/rest/reference/repos#list-repository-tags |    * https://docs.github.com/en/rest/reference/repos#list-repository-tags | ||||||
|    */ |    */ | ||||||
|   get("/api/v3/repos/:owner/:repository/tags")(referrersOnly { repository => |   get("/api/v3/repos/:owner/:repository/tags")(referrersOnly { repository => | ||||||
|     JsonFormat( |     Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => | ||||||
|       repository.tags.map(tagInfo => ApiTag(tagInfo.name, RepositoryName(repository), tagInfo.id)) |       JsonFormat( | ||||||
|     ) |         self.getRef("tags", repository) | ||||||
|  |       ) | ||||||
|  |     } | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   /* |   /* | ||||||
|   | |||||||
| @@ -579,7 +579,7 @@ trait PullRequestService { | |||||||
|       case (oldGit, newGit) => |       case (oldGit, newGit) => | ||||||
|         if (originRepository.branchList.contains(originId)) { |         if (originRepository.branchList.contains(originId)) { | ||||||
|           val forkedId2 = |           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( |           val originId2 = JGitUtil.getForkedCommitId( | ||||||
|             oldGit, |             oldGit, | ||||||
| @@ -596,9 +596,9 @@ trait PullRequestService { | |||||||
|  |  | ||||||
|         } else { |         } else { | ||||||
|           val originId2 = |           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 = |           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))) |           (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.apache.commons.io.FileUtils | ||||||
| import org.eclipse.jgit.api.Git | import org.eclipse.jgit.api.Git | ||||||
| import org.eclipse.jgit.lib.{Repository => _} | import org.eclipse.jgit.lib.{Repository => _} | ||||||
|  |  | ||||||
| import scala.util.Using | import scala.util.Using | ||||||
|  |  | ||||||
| trait RepositoryService { | trait RepositoryService { | ||||||
| @@ -835,12 +836,10 @@ object RepositoryService { | |||||||
|  |  | ||||||
|   def httpUrl(owner: String, name: String)(implicit context: Context): String = |   def httpUrl(owner: String, name: String)(implicit context: Context): String = | ||||||
|     s"${context.baseUrl}/git/${owner}/${name}.git" |     s"${context.baseUrl}/git/${owner}/${name}.git" | ||||||
|  |  | ||||||
|   def sshUrl(owner: String, name: String)(implicit context: Context): Option[String] = |   def sshUrl(owner: String, name: String)(implicit context: Context): Option[String] = | ||||||
|     if (context.settings.ssh.enabled) { |     context.settings.sshUrl(owner, name) | ||||||
|       context.settings.sshAddress.map { x => |  | ||||||
|         s"ssh://${x.genericUser}@${x.host}:${x.port}/${owner}/${name}.git" |  | ||||||
|       } |  | ||||||
|     } else None |  | ||||||
|   def openRepoUrl(openUrl: String)(implicit context: Context): String = |   def openRepoUrl(openUrl: String)(implicit context: Context): String = | ||||||
|     s"github-${context.platform}://openRepo/${openUrl}" |     s"github-${context.platform}://openRepo/${openUrl}" | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,9 +4,10 @@ import javax.servlet.http.HttpServletRequest | |||||||
| import com.nimbusds.jose.JWSAlgorithm | import com.nimbusds.jose.JWSAlgorithm | ||||||
| import com.nimbusds.oauth2.sdk.auth.Secret | import com.nimbusds.oauth2.sdk.auth.Secret | ||||||
| import com.nimbusds.oauth2.sdk.id.{ClientID, Issuer} | 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.ConfigUtil._ | ||||||
| import gitbucket.core.util.Directory._ | import gitbucket.core.util.Directory._ | ||||||
|  |  | ||||||
| import scala.util.Using | import scala.util.Using | ||||||
|  |  | ||||||
| trait SystemSettingsService { | trait SystemSettingsService { | ||||||
| @@ -29,8 +30,14 @@ trait SystemSettingsService { | |||||||
|     props.setProperty(Notification, settings.notification.toString) |     props.setProperty(Notification, settings.notification.toString) | ||||||
|     props.setProperty(LimitVisibleRepositories, settings.limitVisibleRepositories.toString) |     props.setProperty(LimitVisibleRepositories, settings.limitVisibleRepositories.toString) | ||||||
|     props.setProperty(SshEnabled, settings.ssh.enabled.toString) |     props.setProperty(SshEnabled, settings.ssh.enabled.toString) | ||||||
|     settings.ssh.sshHost.foreach(x => props.setProperty(SshHost, x.trim)) |     settings.ssh.bindAddress.foreach { bindAddress => | ||||||
|     settings.ssh.sshPort.foreach(x => props.setProperty(SshPort, x.toString)) |       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) |     props.setProperty(UseSMTP, settings.useSMTP.toString) | ||||||
|     if (settings.useSMTP) { |     if (settings.useSMTP) { | ||||||
|       settings.smtp.foreach { smtp => |       settings.smtp.foreach { smtp => | ||||||
| @@ -95,6 +102,10 @@ trait SystemSettingsService { | |||||||
|         props.load(in) |         props.load(in) | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |     loadSystemSettings(props) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   def loadSystemSettings(props: java.util.Properties): SystemSettings = { | ||||||
|     SystemSettings( |     SystemSettings( | ||||||
|       getOptionValue[String](props, BaseURL, None).map(x => x.replaceFirst("/\\Z", "")), |       getOptionValue[String](props, BaseURL, None).map(x => x.replaceFirst("/\\Z", "")), | ||||||
|       getOptionValue(props, Information, None), |       getOptionValue(props, Information, None), | ||||||
| @@ -112,9 +123,20 @@ trait SystemSettingsService { | |||||||
|       getValue(props, Notification, false), |       getValue(props, Notification, false), | ||||||
|       getValue(props, LimitVisibleRepositories, false), |       getValue(props, LimitVisibleRepositories, false), | ||||||
|       Ssh( |       Ssh( | ||||||
|         getValue(props, SshEnabled, false), |         enabled = getValue(props, SshEnabled, false), | ||||||
|         getOptionValue[String](props, SshHost, None).map(_.trim), |         bindAddress = { | ||||||
|         getOptionValue(props, SshPort, Some(DefaultSshPort)) |           // 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( |       getValue( | ||||||
|         props, |         props, | ||||||
| @@ -182,7 +204,6 @@ trait SystemSettingsService { | |||||||
|       ) |       ) | ||||||
|     ) |     ) | ||||||
|   } |   } | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| object SystemSettingsService { | object SystemSettingsService { | ||||||
| @@ -214,7 +235,6 @@ object SystemSettingsService { | |||||||
|     upload: Upload, |     upload: Upload, | ||||||
|     repositoryViewer: RepositoryViewerSettings |     repositoryViewer: RepositoryViewerSettings | ||||||
|   ) { |   ) { | ||||||
|  |  | ||||||
|     def baseUrl(request: HttpServletRequest): String = |     def baseUrl(request: HttpServletRequest): String = | ||||||
|       baseUrl.getOrElse(parseBaseUrl(request)).stripSuffix("/") |       baseUrl.getOrElse(parseBaseUrl(request)).stripSuffix("/") | ||||||
|  |  | ||||||
| @@ -231,11 +251,17 @@ object SystemSettingsService { | |||||||
|         .fold(base)(_ + base.dropWhile(_ != ':')) |         .fold(base)(_ + base.dropWhile(_ != ':')) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     def sshAddress: Option[SshAddress] = |     def sshBindAddress: Option[SshAddress] = | ||||||
|       ssh.sshHost.collect { |       ssh.bindAddress | ||||||
|         case host if ssh.enabled => |  | ||||||
|           SshAddress(host, ssh.sshPort.getOrElse(DefaultSshPort), "git") |     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( |   case class RepositoryOperation( | ||||||
| @@ -248,9 +274,35 @@ object SystemSettingsService { | |||||||
|  |  | ||||||
|   case class Ssh( |   case class Ssh( | ||||||
|     enabled: Boolean, |     enabled: Boolean, | ||||||
|     sshHost: Option[String], |     bindAddress: Option[SshAddress], | ||||||
|     sshPort: Option[Int] |     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( |   case class Ldap( | ||||||
|     host: String, |     host: String, | ||||||
| @@ -296,7 +348,25 @@ object SystemSettingsService { | |||||||
|     password: Option[String] |     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]) |   case class WebHook(blockPrivateAddress: Boolean, whitelist: Seq[String]) | ||||||
|  |  | ||||||
| @@ -304,6 +374,8 @@ object SystemSettingsService { | |||||||
|  |  | ||||||
|   case class RepositoryViewerSettings(maxFiles: Int) |   case class RepositoryViewerSettings(maxFiles: Int) | ||||||
|  |  | ||||||
|  |   val GenericSshUser = "git" | ||||||
|  |   val PublicSshPort = 22 | ||||||
|   val DefaultSshPort = 29418 |   val DefaultSshPort = 29418 | ||||||
|   val DefaultSmtpPort = 25 |   val DefaultSmtpPort = 25 | ||||||
|   val DefaultLdapPort = 389 |   val DefaultLdapPort = 389 | ||||||
| @@ -325,6 +397,10 @@ object SystemSettingsService { | |||||||
|   private val SshEnabled = "ssh" |   private val SshEnabled = "ssh" | ||||||
|   private val SshHost = "ssh.host" |   private val SshHost = "ssh.host" | ||||||
|   private val SshPort = "ssh.port" |   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 UseSMTP = "useSMTP" | ||||||
|   private val SmtpHost = "smtp.host" |   private val SmtpHost = "smtp.host" | ||||||
|   private val SmtpPort = "smtp.port" |   private val SmtpPort = "smtp.port" | ||||||
|   | |||||||
| @@ -3,7 +3,6 @@ package gitbucket.core.servlet | |||||||
| import java.io.File | import java.io.File | ||||||
| import java.util | import java.util | ||||||
| import java.util.Date | import java.util.Date | ||||||
|  |  | ||||||
| import scala.util.Using | import scala.util.Using | ||||||
| import gitbucket.core.api | import gitbucket.core.api | ||||||
| import gitbucket.core.api.JsonFormat.Context | import gitbucket.core.api.JsonFormat.Context | ||||||
| @@ -209,9 +208,7 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest] | |||||||
|  |  | ||||||
|       val settings = loadSystemSettings() |       val settings = loadSystemSettings() | ||||||
|       val baseUrl = settings.baseUrl(request) |       val baseUrl = settings.baseUrl(request) | ||||||
|       val sshUrl = settings.sshAddress.map { x => |       val sshUrl = settings.sshUrl(owner, repository) | ||||||
|         s"${x.genericUser}@${x.host}:${x.port}" |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       if (!repository.endsWith(".wiki")) { |       if (!repository.endsWith(".wiki")) { | ||||||
|         val hook = new CommitLogHook(owner, repository, pusher, baseUrl, sshUrl) |         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.service.{AccountService, DeployKeyService, RepositoryService, SystemSettingsService} | ||||||
| import gitbucket.core.servlet.{CommitLogHook, Database} | import gitbucket.core.servlet.{CommitLogHook, Database} | ||||||
| import gitbucket.core.util.Directory | 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.command.{Command, CommandFactory} | ||||||
| import org.apache.sshd.server.session.ServerSession | import org.apache.sshd.server.session.{ServerSession, ServerSessionAware} | ||||||
| import org.slf4j.LoggerFactory | import org.slf4j.LoggerFactory | ||||||
| import java.io.{File, InputStream, OutputStream} |  | ||||||
|  |  | ||||||
|  | import java.io.{File, InputStream, OutputStream} | ||||||
| import org.eclipse.jgit.api.Git | import org.eclipse.jgit.api.Git | ||||||
| import Directory._ | import Directory._ | ||||||
|  | import gitbucket.core.service.SystemSettingsService.SshAddress | ||||||
| import gitbucket.core.ssh.PublicKeyAuthenticator.AuthType | import gitbucket.core.ssh.PublicKeyAuthenticator.AuthType | ||||||
|  | import org.apache.sshd.server.channel.ChannelSession | ||||||
| import org.eclipse.jgit.transport.{ReceivePack, UploadPack} | import org.eclipse.jgit.transport.{ReceivePack, UploadPack} | ||||||
| import org.apache.sshd.server.shell.UnknownCommand | import org.apache.sshd.server.shell.UnknownCommand | ||||||
| import org.eclipse.jgit.errors.RepositoryNotFoundException | import org.eclipse.jgit.errors.RepositoryNotFoundException | ||||||
|  |  | ||||||
| import scala.util.Using | import scala.util.Using | ||||||
|  |  | ||||||
| object GitCommand { | object GitCommand { | ||||||
|   val DefaultCommandRegex = """\Agit-(upload|receive)-pack '/([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-\+_.]+).git'\Z""".r |   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 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]) |   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()) |     val thread = new Thread(newTask()) | ||||||
|     thread.start() |     thread.start() | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   override def destroy(): Unit = {} |   override def destroy(channel: ChannelSession): Unit = {} | ||||||
|  |  | ||||||
|   override def setExitCallback(callback: ExitCallback): Unit = { |   override def setExitCallback(callback: ExitCallback): Unit = { | ||||||
|     this.callback = callback |     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) |     extends DefaultGitCommand(owner, repoName) | ||||||
|     with RepositoryService |     with RepositoryService | ||||||
|     with AccountService |     with AccountService | ||||||
| @@ -177,7 +182,8 @@ class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String, ss | |||||||
|         val repository = git.getRepository |         val repository = git.getRepository | ||||||
|         val receive = new ReceivePack(repository) |         val receive = new ReceivePack(repository) | ||||||
|         if (!repoName.endsWith(".wiki")) { |         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.setPreReceiveHook(hook) | ||||||
|           receive.setPostReceiveHook(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]) |   private val logger = LoggerFactory.getLogger(classOf[GitCommandFactory]) | ||||||
|  |  | ||||||
|   override def createCommand(command: String): Command = { |   override def createCommand(channel: ChannelSession, command: String): Command = { | ||||||
|     import GitCommand._ |     import GitCommand._ | ||||||
|     logger.debug(s"command: $command") |     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) |       case f if f.isDefinedAt(command) => f(command) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pluginCommand match { |     pluginCommand.getOrElse { | ||||||
|       case Some(x) => x |       val (simpleRegex, defaultRegex) = | ||||||
|       case None => |         if (sshAddress.isDefaultPort) { | ||||||
|         command match { |           (SimpleCommandRegexPort22, DefaultCommandRegexPort22) | ||||||
|           case SimpleCommandRegex("upload", repoName) if (pluginRepository(repoName)) => |         } else { | ||||||
|             new PluginGitUploadPack(repoName, routing(repoName)) |           (SimpleCommandRegex, DefaultCommandRegex) | ||||||
|           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) |  | ||||||
|         } |         } | ||||||
|  |       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 | package gitbucket.core.ssh | ||||||
|  |  | ||||||
| import gitbucket.core.service.SystemSettingsService.SshAddress | 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.{Environment, ExitCallback} | ||||||
| import org.apache.sshd.server.command.Command | 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 | import org.eclipse.jgit.lib.Constants | ||||||
|  |  | ||||||
| class NoShell(sshAddress: SshAddress) extends Factory[Command] { | class NoShell(sshAddress: SshAddress) extends ShellFactory { | ||||||
|   override def create(): Command = new Command() { |   override def createShell(channel: ChannelSession): Command = new Command() { | ||||||
|     private var in: InputStream = null |     private var in: InputStream = null | ||||||
|     private var out: OutputStream = null |     private var out: OutputStream = null | ||||||
|     private var err: OutputStream = null |     private var err: OutputStream = null | ||||||
|     private var callback: ExitCallback = 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 = |       val message = | ||||||
|         """ |         """ | ||||||
|           | Welcome to |           | Welcome to | ||||||
| @@ -30,8 +33,8 @@ class NoShell(sshAddress: SshAddress) extends Factory[Command] { | |||||||
|           | |           | | ||||||
|           | Please use: |           | Please use: | ||||||
|           | |           | | ||||||
|           | git clone ssh://%s@%s:%d/OWNER/REPOSITORY_NAME.git |           | git clone %s | ||||||
|         """.stripMargin.format(sshAddress.genericUser, sshAddress.host, sshAddress.port).replace("\n", "\r\n") + "\r\n" |         """.stripMargin.format(placeholderAddress).replace("\n", "\r\n") + "\r\n" | ||||||
|       err.write(Constants.encode(message)) |       err.write(Constants.encode(message)) | ||||||
|       err.flush() |       err.flush() | ||||||
|       in.close() |       in.close() | ||||||
| @@ -40,7 +43,7 @@ class NoShell(sshAddress: SshAddress) extends Factory[Command] { | |||||||
|       callback.onExit(127) |       callback.onExit(127) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override def destroy(): Unit = {} |     override def destroy(channel: ChannelSession): Unit = {} | ||||||
|  |  | ||||||
|     override def setInputStream(in: InputStream): Unit = { |     override def setInputStream(in: InputStream): Unit = { | ||||||
|       this.in = in |       this.in = in | ||||||
|   | |||||||
| @@ -8,13 +8,13 @@ import gitbucket.core.model.Profile.profile.blockingApi._ | |||||||
| import gitbucket.core.ssh.PublicKeyAuthenticator.AuthType | import gitbucket.core.ssh.PublicKeyAuthenticator.AuthType | ||||||
| import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator | import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator | ||||||
| import org.apache.sshd.server.session.ServerSession | import org.apache.sshd.server.session.ServerSession | ||||||
| import org.apache.sshd.common.AttributeStore | import org.apache.sshd.common.AttributeRepository | ||||||
| import org.slf4j.LoggerFactory | import org.slf4j.LoggerFactory | ||||||
|  |  | ||||||
| object PublicKeyAuthenticator { | object PublicKeyAuthenticator { | ||||||
|  |  | ||||||
|   // put in the ServerSession here to be read by GitCommand later |   // 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 = |   def putAuthType(serverSession: ServerSession, authType: AuthType): Unit = | ||||||
|     serverSession.setAttribute(authTypeSessionKey, authType) |     serverSession.setAttribute(authTypeSessionKey, authType) | ||||||
|   | |||||||
| @@ -1,8 +1,7 @@ | |||||||
| package gitbucket.core.ssh | package gitbucket.core.ssh | ||||||
|  |  | ||||||
| import java.util.concurrent.atomic.AtomicBoolean | import java.util.concurrent.atomic.AtomicReference | ||||||
| import javax.servlet.{ServletContextEvent, ServletContextListener} | import javax.servlet.{ServletContextEvent, ServletContextListener} | ||||||
|  |  | ||||||
| import gitbucket.core.service.SystemSettingsService | import gitbucket.core.service.SystemSettingsService | ||||||
| import gitbucket.core.service.SystemSettingsService.SshAddress | import gitbucket.core.service.SystemSettingsService.SshAddress | ||||||
| import gitbucket.core.util.Directory | import gitbucket.core.util.Directory | ||||||
| @@ -11,40 +10,48 @@ import org.slf4j.LoggerFactory | |||||||
|  |  | ||||||
| object SshServer { | object SshServer { | ||||||
|   private val logger = LoggerFactory.getLogger(SshServer.getClass) |   private val logger = LoggerFactory.getLogger(SshServer.getClass) | ||||||
|   private val server = org.apache.sshd.server.SshServer.setUpDefaultServer() |   private val server = new AtomicReference[org.apache.sshd.server.SshServer](null) | ||||||
|   private val active = new AtomicBoolean(false) |  | ||||||
|  |  | ||||||
|   private def configure(sshAddress: SshAddress, baseUrl: String) = { |   private def configure( | ||||||
|     server.setPort(sshAddress.port) |     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( |     val provider = new SimpleGeneratorHostKeyProvider( | ||||||
|       java.nio.file.Paths.get(s"${Directory.GitBucketHome}/gitbucket.ser") |       java.nio.file.Paths.get(s"${Directory.GitBucketHome}/gitbucket.ser") | ||||||
|     ) |     ) | ||||||
|     provider.setAlgorithm("RSA") |     provider.setAlgorithm("RSA") | ||||||
|     provider.setOverwriteAllowed(false) |     provider.setOverwriteAllowed(false) | ||||||
|     server.setKeyPairProvider(provider) |     server.setKeyPairProvider(provider) | ||||||
|     server.setPublickeyAuthenticator(new PublicKeyAuthenticator(sshAddress.genericUser)) |     server.setPublickeyAuthenticator(new PublicKeyAuthenticator(bindAddress.genericUser)) | ||||||
|     server.setCommandFactory( |     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) = { |   def start(bindAddress: SshAddress, publicAddress: SshAddress, baseUrl: String): Unit = { | ||||||
|     if (active.compareAndSet(false, true)) { |     this.server.synchronized { | ||||||
|       configure(sshAddress, baseUrl) |       val server = configure(bindAddress, publicAddress, baseUrl) | ||||||
|       server.start() |       if (this.server.compareAndSet(null, server)) { | ||||||
|       logger.info(s"Start SSH Server Listen on ${server.getPort}") |         server.start() | ||||||
|  |         logger.info(s"Start SSH Server Listen on ${server.getPort}") | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   def stop() = { |   def stop(): Unit = { | ||||||
|     if (active.compareAndSet(true, false)) { |     this.server.synchronized { | ||||||
|       server.stop(true) |       val server = this.server.getAndSet(null) | ||||||
|       logger.info("SSH Server is stopped.") |       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 = { |   override def contextInitialized(sce: ServletContextEvent): Unit = { | ||||||
|     val settings = loadSystemSettings() |     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.") |       logger.error("Could not start SshServer because the baseUrl is not configured.") | ||||||
|     } |     } | ||||||
|     for { |     for { | ||||||
|       sshAddress <- settings.sshAddress |       bindAddress <- settings.sshBindAddress | ||||||
|  |       publicAddress <- settings.sshPublicAddress | ||||||
|       baseUrl <- settings.baseUrl |       baseUrl <- settings.baseUrl | ||||||
|     } SshServer.start(sshAddress, baseUrl) |     } SshServer.start(bindAddress, publicAddress, baseUrl) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   override def contextDestroyed(sce: ServletContextEvent): Unit = { |   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 request2Session(implicit request: HttpServletRequest): JdbcBackend#Session = Database.getSession(request) | ||||||
|  |  | ||||||
|   implicit def context2ApiJsonFormatContext(implicit context: Context): JsonFormat.Context = |   implicit def context2ApiJsonFormatContext(implicit context: Context): JsonFormat.Context = | ||||||
|     JsonFormat.Context(context.baseUrl, context.settings.sshAddress.map { x => |     JsonFormat.Context(context.baseUrl, context.settings.sshUrl) | ||||||
|       s"${x.genericUser}@${x.host}:${x.port}" |  | ||||||
|     }) |  | ||||||
|  |  | ||||||
|   implicit class RichSeq[A](private val seq: Seq[A]) extends AnyVal { |   implicit class RichSeq[A](private val seq: Seq[A]) extends AnyVal { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -228,10 +228,11 @@ object JGitUtil { | |||||||
|    * |    * | ||||||
|    * @param name the tag name |    * @param name the tag name | ||||||
|    * @param time the tagged date |    * @param time the tagged date | ||||||
|    * @param id the commit id |    * @param commitId the commit id | ||||||
|    * @param message the message of the tagged commit |    * @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 |    * The submodule data | ||||||
| @@ -347,7 +348,8 @@ object JGitUtil { | |||||||
|                     ref.getName.stripPrefix("refs/tags/"), |                     ref.getName.stripPrefix("refs/tags/"), | ||||||
|                     revCommit.getCommitterIdent.getWhen, |                     revCommit.getCommitterIdent.getWhen, | ||||||
|                     revCommit.getName, |                     revCommit.getName, | ||||||
|                     revCommit.getShortMessage |                     revCommit.getShortMessage, | ||||||
|  |                     ref.getObjectId.getName | ||||||
|                   ) |                   ) | ||||||
|                 ) |                 ) | ||||||
|               } catch { |               } catch { | ||||||
| @@ -512,7 +514,7 @@ object JGitUtil { | |||||||
|   /** |   /** | ||||||
|    * Returns the first line of the commit message. |    * 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 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 | ||||||
|   | |||||||
| @@ -19,22 +19,40 @@ | |||||||
|   <label class="checkbox"> |   <label class="checkbox"> | ||||||
|     <input type="checkbox" id="sshEnabled" name="ssh.enabled"@if(context.settings.ssh.enabled){ checked}/> |     <input type="checkbox" id="sshEnabled" name="ssh.enabled"@if(context.settings.ssh.enabled){ checked}/> | ||||||
|     Enable SSH access to git repository |     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> |   </label> | ||||||
| </fieldset> | </fieldset> | ||||||
| <div class="ssh"> | <div class="ssh"> | ||||||
|   <div class="form-group"> |   <div class="bindAddress"> | ||||||
|     <label class="control-label col-md-2" for="sshHost">SSH host</label> |     <div class="form-group"> | ||||||
|     <div class="col-md-10"> |       <label class="control-label col-md-2" for="sshBindHost">SSH bind host</label> | ||||||
|       <input type="text" id="sshHost" name="ssh.host" class="form-control" value="@context.settings.ssh.sshHost"/> |       <div class="col-md-10"> | ||||||
|       <span id="error-ssh_host" class="error"></span> |         <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> |   </div> | ||||||
|   <div class="form-group"> |   <div class="publicAddress"> | ||||||
|     <label class="control-label col-md-2" for="sshPort">SSH port</label> |     <div class="form-group"> | ||||||
|     <div class="col-md-10"> |       <label class="control-label col-md-2" for="sshPublicHost">SSH public host</label> | ||||||
|       <input type="text" id="sshPort" name="ssh.port" class="form-control" value="@context.settings.ssh.sshPort"/> |       <div class="col-md-10"> | ||||||
|       <span id="error-ssh_port" class="error"></span> |         <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> |   </div> | ||||||
| </div> | </div> | ||||||
|   | |||||||
| @@ -40,8 +40,6 @@ | |||||||
|     </div> |     </div> | ||||||
|   </div> |   </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> | <script> | ||||||
| $(function(){ | $(function(){ | ||||||
|   @if(elastic){ |   @if(elastic){ | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ | |||||||
|           <td> |           <td> | ||||||
|             <div class="col-md-2 text-right"> |             <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)/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> |               <span class="muted">@gitbucket.core.helper.html.datetimeago(tag.time)</span> | ||||||
|             </div> |             </div> | ||||||
|             <div class="col-md-10" style="border-left: 1px solid #eee"> |             <div class="col-md-10" style="border-left: 1px solid #eee"> | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ | |||||||
|         @defining(repository.tags.find(_.name == release.tag)){ tag => |         @defining(repository.tags.find(_.name == release.tag)){ tag => | ||||||
|           @tag.map { 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)/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> |             <span class="muted">@gitbucket.core.helper.html.datetimeago(tag.time)</span> | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -244,7 +244,7 @@ function updateHighlighting() { | |||||||
|   const isDark = @{highlighterTheme.contains("dark").toString}; |   const isDark = @{highlighterTheme.contains("dark").toString}; | ||||||
|   if (hash.match(/#L\d+(-L\d+)?/)) { |   if (hash.match(/#L\d+(-L\d+)?/)) { | ||||||
|     if (isDark) { |     if (isDark) { | ||||||
|       $('li.highlight').removeClass('highlight-dark'); |       $('li.highlight-dark').removeClass('highlight-dark'); | ||||||
|     } else { |     } else { | ||||||
|       $('li.highlight').removeClass('highlight'); |       $('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 = |   def client(login: String, password: String): GitHub = | ||||||
|     GitHub.connectToEnterprise(s"http://localhost:${port}/api/v3", login, password) |     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. |   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 |     // See the following: https://bugs.eclipse.org/bugs/show_bug.cgi?id=420142 | ||||||
|     val statisticsHandler = new StatisticsHandler |     val statisticsHandler = new StatisticsHandler | ||||||
|   | |||||||
| @@ -2,10 +2,13 @@ package gitbucket.core.api | |||||||
|  |  | ||||||
| import gitbucket.core.TestingGitBucketServer | import gitbucket.core.TestingGitBucketServer | ||||||
| import org.apache.commons.io.IOUtils | import org.apache.commons.io.IOUtils | ||||||
|  | import org.eclipse.jgit.api.Git | ||||||
| import org.scalatest.funsuite.AnyFunSuite | import org.scalatest.funsuite.AnyFunSuite | ||||||
|  |  | ||||||
| import scala.util.Using | 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. |  * 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).getState == GHCommitState.FAILURE) | ||||||
|         assert(statusList.get(1).getContext == "context") |         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, |     milestoneCount = 1, | ||||||
|     branchList = Seq("master", "develop"), |     branchList = Seq("master", "develop"), | ||||||
|     tags = Seq( |     tags = Seq( | ||||||
|       TagInfo(name = "v1.0", time = date("2015-05-05T23:40:27Z"), id = "id1", message = "1.0 released"), |       TagInfo( | ||||||
|       TagInfo(name = "v2.0", time = date("2016-05-05T23:40:27Z"), id = "id2", message = "2.0 released") |         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") |     managers = Seq("myboss") | ||||||
|   ) |   ) | ||||||
| @@ -432,9 +444,29 @@ object ApiSpecModels { | |||||||
|  |  | ||||||
|   val apiPusher = ApiPusher(account) |   val apiPusher = ApiPusher(account) | ||||||
|  |  | ||||||
|   val apiRef = ApiRef( |   //have both urls as https, as the expected samples are using https | ||||||
|     ref = "refs/heads/featureA", |   val gitHubContext = JsonFormat.Context("https://api.github.com", Some("https://api.github.com")) | ||||||
|     `object` = ApiObject(sha1) |  | ||||||
|  |   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" |   val assetFileName = "010203040a0b0c0d" | ||||||
| @@ -765,8 +797,33 @@ object ApiSpecModels { | |||||||
|  |  | ||||||
|   val jsonPusher = """{"name":"octocat","email":"octocat@example.com"}""" |   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 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 = |   val jsonReleaseAsset = | ||||||
|     s"""{ |     s"""{ | ||||||
|       |"name":"release.zip", |       |"name":"release.zip", | ||||||
|   | |||||||
| @@ -8,6 +8,12 @@ class JsonFormatSpec extends AnyFunSuite { | |||||||
|   implicit val format = JsonFormat.jsonFormats |   implicit val format = JsonFormat.jsonFormats | ||||||
|  |  | ||||||
|   private def expected(json: String) = json.replaceAll("\n", "") |   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") { |   test("apiUser") { | ||||||
|     assert(JsonFormat(apiUser) == expected(jsonUser)) |     assert(JsonFormat(apiUser) == expected(jsonUser)) | ||||||
| @@ -76,8 +82,11 @@ class JsonFormatSpec extends AnyFunSuite { | |||||||
|   test("apiPusher") { |   test("apiPusher") { | ||||||
|     assert(JsonFormat(apiPusher) == expected(jsonPusher)) |     assert(JsonFormat(apiPusher) == expected(jsonPusher)) | ||||||
|   } |   } | ||||||
|   test("apiRef") { |   test("apiRefHead") { | ||||||
|     assert(JsonFormat(apiRef) == expected(jsonRef)) |     assertEqualJson(JsonFormat(apiRefHeadsMaster)(gitHubContext), jsonRefHeadsMaster) | ||||||
|  |   } | ||||||
|  |   test("apiRefTag") { | ||||||
|  |     assertEqualJson(JsonFormat(apiRefTag)(gitHubContext), jsonRefTag) | ||||||
|   } |   } | ||||||
|   test("apiReleaseAsset") { |   test("apiReleaseAsset") { | ||||||
|     assert(JsonFormat(apiReleaseAsset) == expected(jsonReleaseAsset)) |     assert(JsonFormat(apiReleaseAsset) == expected(jsonReleaseAsset)) | ||||||
|   | |||||||
| @@ -47,8 +47,8 @@ trait ServiceSpecBase { | |||||||
|       limitVisibleRepositories = false, |       limitVisibleRepositories = false, | ||||||
|       ssh = Ssh( |       ssh = Ssh( | ||||||
|         enabled = false, |         enabled = false, | ||||||
|         sshHost = None, |         bindAddress = None, | ||||||
|         sshPort = None |         publicAddress = None | ||||||
|       ), |       ), | ||||||
|       useSMTP = false, |       useSMTP = false, | ||||||
|       smtp = None, |       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 | 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.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) |   trait MockContext { | ||||||
|  |     val baseUrl = "https://some.example.tech:8080/code-context" | ||||||
|   describe("createCommand") { |     val sshHost = "localhost" | ||||||
|     it("should return GitReceivePack when command is git-receive-pack") { |     val sshPort = 2222 | ||||||
|       assert(factory.createCommand("git-receive-pack '/owner/repo.git'").isInstanceOf[DefaultGitReceivePack] == true) |     lazy val factory = new GitCommandFactory(baseUrl, SshAddress(sshHost, sshPort, "git")) | ||||||
|       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) |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   "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, |       limitVisibleRepositories = false, | ||||||
|       ssh = Ssh( |       ssh = Ssh( | ||||||
|         enabled = false, |         enabled = false, | ||||||
|         sshHost = None, |         bindAddress = None, | ||||||
|         sshPort = None |         publicAddress = None | ||||||
|       ), |       ), | ||||||
|       useSMTP = false, |       useSMTP = false, | ||||||
|       smtp = None, |       smtp = None, | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| package gitbucket.core.view | package gitbucket.core.view | ||||||
|  |  | ||||||
| import gitbucket.core.util.SyntaxSugars | import gitbucket.core.util.SyntaxSugars | ||||||
| import SyntaxSugars._ |  | ||||||
| import org.scalatest.funspec.AnyFunSpec | import org.scalatest.funspec.AnyFunSpec | ||||||
|  |  | ||||||
| class PaginationSpec extends AnyFunSpec { | class PaginationSpec extends AnyFunSpec { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user