mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-03 03:55:58 +01:00
Compare commits
1 Commits
4.37.2
...
optimize-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cc3a0d36e |
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.7
|
uses: actions/cache@v2.1.6
|
||||||
env:
|
env:
|
||||||
cache-name: cache-sbt-libs
|
cache-name: cache-sbt-libs
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -4,6 +4,4 @@ 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." }
|
|
||||||
{ groupId = "com.zaxxer", version = "4." }
|
|
||||||
]
|
]
|
||||||
|
|||||||
16
CHANGELOG.md
16
CHANGELOG.md
@@ -1,22 +1,6 @@
|
|||||||
# 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.2 - 16 Jan 2022
|
|
||||||
- Security fix
|
|
||||||
|
|
||||||
### 4.37.1 - 14 Dec 2021
|
|
||||||
- Update gist-plugin and notification-plugin
|
|
||||||
- Fix SSHCommand extension point for apache-sshd 2.x
|
|
||||||
|
|
||||||
### 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
|
||||||
|
|
||||||
|
|||||||
24
README.md
24
README.md
@@ -61,22 +61,18 @@ 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.37.x
|
What's New in 4.36.x
|
||||||
-------------
|
-------------
|
||||||
### 4.37.2 - 16 Jan 2022
|
### 4.36.2 - 16 Aug 2021
|
||||||
- Security fix
|
- Escape user name in avatar image tag
|
||||||
|
|
||||||
### 4.37.1 - 14 Dec 2021
|
### 4.36.1 - 22 Jul 2021
|
||||||
- Update gist-plugin and notification-plugin
|
- Bump gitbucket-gist-plugin to 4.21.0
|
||||||
- Fix SSHCommand extension point for apache-sshd 2.x
|
|
||||||
|
|
||||||
### 4.37.0 - 11 Dec 2021
|
### 4.36.0 - 17 Jul 2021
|
||||||
- Enhance Git Reference APIs
|
- Tag selector in the repository viewer
|
||||||
- Add milestone data to issue list API
|
- Link issues/pull requests of other repositories
|
||||||
- Support "all" in issue list API
|
- Files and lines can be linked in the diff view
|
||||||
- Support EDDSA in signed commit verification
|
- Option to disable XSS protection
|
||||||
- Support custom SSH url
|
|
||||||
- Relax max passward length limitation
|
|
||||||
- Relax max webhook url length limitation
|
|
||||||
|
|
||||||
See the [change log](CHANGELOG.md) for all of the updates.
|
See the [change log](CHANGELOG.md) for all of the updates.
|
||||||
|
|||||||
20
build.sbt
20
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.37.2"
|
val GitBucketVersion = "4.36.2"
|
||||||
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"
|
||||||
@@ -15,7 +15,7 @@ sourcesInBase := false
|
|||||||
organization := Organization
|
organization := Organization
|
||||||
name := Name
|
name := Name
|
||||||
version := GitBucketVersion
|
version := GitBucketVersion
|
||||||
scalaVersion := "2.13.8"
|
scalaVersion := "2.13.7"
|
||||||
|
|
||||||
scalafmtOnCompile := true
|
scalafmtOnCompile := true
|
||||||
|
|
||||||
@@ -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.8.0" exclude ("org.slf4j", "slf4j-jdk14") exclude ("org.apache.sshd", "sshd-mina") exclude ("org.apache.sshd", "sshd-netty"),
|
"org.apache.sshd" % "apache-sshd" % "2.1.0" exclude ("org.slf4j", "slf4j-jdk14") exclude ("org.apache.sshd", "sshd-mina") exclude ("org.apache.sshd", "sshd-netty"),
|
||||||
"org.apache.tika" % "tika-core" % "2.2.1",
|
"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.10",
|
"ch.qos.logback" % "logback-classic" % "1.2.6",
|
||||||
"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.16",
|
"net.coobird" % "thumbnailator" % "0.4.14",
|
||||||
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
|
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
|
||||||
"com.nimbusds" % "oauth2-oidc-sdk" % "9.22",
|
"com.nimbusds" % "oauth2-oidc-sdk" % "9.19",
|
||||||
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
||||||
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
||||||
"junit" % "junit" % "4.13.2" % "test",
|
"junit" % "junit" % "4.13.2" % "test",
|
||||||
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test" cross CrossVersion.for3Use2_13,
|
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test" cross CrossVersion.for3Use2_13,
|
||||||
"org.mockito" % "mockito-core" % "4.2.0" % "test",
|
"org.mockito" % "mockito-core" % "4.0.0" % "test",
|
||||||
"com.dimafeng" %% "testcontainers-scala" % "0.39.12" % "test",
|
"com.dimafeng" %% "testcontainers-scala" % "0.39.11" % "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.301" % "test"
|
"org.kohsuke" % "github-api" % "1.135" % "test"
|
||||||
)
|
)
|
||||||
|
|
||||||
libraryDependencies ~= {
|
libraryDependencies ~= {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
sbt.version=1.6.1
|
sbt.version=1.5.5
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
||||||
|
|
||||||
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6")
|
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3")
|
||||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.1")
|
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.1")
|
||||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "1.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")
|
||||||
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2")
|
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2")
|
||||||
addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0")
|
addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0")
|
||||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.3")
|
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.2")
|
||||||
|
|
||||||
addDependencyTreePlugin
|
addDependencyTreePlugin
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
notifications:1.11.0
|
notifications:1.10.0
|
||||||
gist:4.22.0
|
gist:4.21.0
|
||||||
emoji:4.6.0
|
emoji:4.6.0
|
||||||
pages:1.10.0
|
pages:1.10.0
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
<?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,8 +119,5 @@ 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")),
|
|
||||||
new Version("4.37.1"),
|
|
||||||
new Version("4.37.2")
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,49 +1,5 @@
|
|||||||
package gitbucket.core.api
|
package gitbucket.core.api
|
||||||
|
|
||||||
import gitbucket.core.util.JGitUtil.TagInfo
|
case class ApiObject(sha: String)
|
||||||
import gitbucket.core.util.RepositoryName
|
|
||||||
import org.eclipse.jgit.lib.Ref
|
|
||||||
|
|
||||||
case class ApiRefCommit(
|
case class ApiRef(ref: String, `object`: ApiObject)
|
||||||
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"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
29
src/main/scala/gitbucket/core/api/ApiTag.scala
Normal file
29
src/main/scala/gitbucket/core/api/ApiTag.scala
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
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.commitId }.getOrElse("")
|
val previousTagId = repository.tags.collectFirst { case x if x.name == previousTag => x.id }.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,6 +1,7 @@
|
|||||||
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._
|
||||||
@@ -49,20 +50,8 @@ 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())),
|
||||||
"bindAddress" -> mapping(
|
"host" -> trim(label("SSH host", optional(text()))),
|
||||||
"host" -> trim(label("Bind SSH host", optional(text()))),
|
"port" -> trim(label("SSH port", optional(number())))
|
||||||
"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(
|
||||||
@@ -127,8 +116,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.bindAddress.isEmpty) {
|
if (settings.ssh.enabled && settings.ssh.sshHost.isEmpty) {
|
||||||
Some("ssh.bindAddress.host" -> "SSH bind host is required if SSH access is enabled.")
|
Some("sshHost" -> "SSH host is required if SSH access is enabled.")
|
||||||
} else None
|
} else None
|
||||||
).flatten
|
).flatten
|
||||||
}
|
}
|
||||||
@@ -319,13 +308,12 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
post("/admin/system", form)(adminOnly { form =>
|
post("/admin/system", form)(adminOnly { form =>
|
||||||
saveSystemSettings(form)
|
saveSystemSettings(form)
|
||||||
|
|
||||||
if (form.ssh.bindAddress != context.settings.sshBindAddress || form.ssh.publicAddress != context.settings.sshPublicAddress) {
|
if (form.sshAddress != context.settings.sshAddress) {
|
||||||
SshServer.stop()
|
SshServer.stop()
|
||||||
for {
|
for {
|
||||||
bindAddress <- form.ssh.bindAddress
|
sshAddress <- form.sshAddress
|
||||||
publicAddress <- form.ssh.publicAddress.orElse(form.ssh.bindAddress)
|
|
||||||
baseUrl <- form.baseUrl
|
baseUrl <- form.baseUrl
|
||||||
} SshServer.start(bindAddress, publicAddress, baseUrl)
|
} SshServer.start(sshAddress, baseUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
flash.update("info", "System settings has been updated.")
|
flash.update("info", "System settings has been updated.")
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
package gitbucket.core.controller.api
|
package gitbucket.core.controller.api
|
||||||
import gitbucket.core.api.{ApiError, ApiRef, CreateARef, JsonFormat, UpdateARef}
|
import gitbucket.core.api.{ApiObject, 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
|
||||||
@@ -15,39 +14,48 @@ import scala.jdk.CollectionConverters._
|
|||||||
import scala.util.Using
|
import scala.util.Using
|
||||||
|
|
||||||
trait ApiGitReferenceControllerBase extends ControllerBase {
|
trait ApiGitReferenceControllerBase extends ControllerBase {
|
||||||
self: ReferrerAuthenticator with WritableUsersAuthenticator =>
|
self: ReferrerAuthenticator =>
|
||||||
|
|
||||||
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 =>
|
||||||
val revstr = multiParams("splat").head
|
getRef()
|
||||||
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")
|
||||||
val revstr = multiParams("splat").head
|
getRef()
|
||||||
getRef(revstr, repository)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
private def getRef() = {
|
||||||
|
val revstr = multiParams("splat").head
|
||||||
|
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||||
|
val ref = git.getRepository().findRef(revstr)
|
||||||
|
|
||||||
|
if (ref != null) {
|
||||||
|
val sha = ref.getObjectId().name()
|
||||||
|
JsonFormat(ApiRef(revstr, ApiObject(sha)))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
val refs = git
|
||||||
|
.getRepository()
|
||||||
|
.getRefDatabase()
|
||||||
|
.getRefsByPrefix("refs/")
|
||||||
|
.asScala
|
||||||
|
|
||||||
|
JsonFormat(refs.map { ref =>
|
||||||
|
val sha = ref.getObjectId().name()
|
||||||
|
ApiRef(revstr, ApiObject(sha))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ii. Get all references
|
* ii. Get all references
|
||||||
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#list-matching-references
|
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#list-matching-references
|
||||||
@@ -57,17 +65,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 { repository =>
|
post("/api/v3/repos/:owner/:repository/git/refs")(referrersOnly { _ =>
|
||||||
extractFromJsonBody[CreateARef].map {
|
extractFromJsonBody[CreateARef].map {
|
||||||
data =>
|
data =>
|
||||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.owner))) { git =>
|
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { 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.fromRef(RepositoryName(repository.owner, repository.name), ref))
|
case Result.NEW => JsonFormat(ApiRef(update.getName, ApiObject(update.getNewObjectId.getName)))
|
||||||
case _ => UnprocessableEntity(result.name())
|
case _ => UnprocessableEntity(result.name())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -81,11 +89,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/*")(writableUsersOnly { repository =>
|
patch("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { _ =>
|
||||||
val refName = multiParams("splat").mkString("/")
|
val refName = multiParams("splat").mkString("/")
|
||||||
extractFromJsonBody[UpdateARef].map {
|
extractFromJsonBody[UpdateARef].map {
|
||||||
data =>
|
data =>
|
||||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.owner))) { git =>
|
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { 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.")
|
||||||
@@ -96,7 +104,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.fromRef(RepositoryName(repository), update.getRef))
|
JsonFormat(ApiRef(update.getName, ApiObject(update.getNewObjectId.getName)))
|
||||||
case _ => UnprocessableEntity(result.name())
|
case _ => UnprocessableEntity(result.name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,7 +116,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/*")(writableUsersOnly { _ =>
|
delete("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { _ =>
|
||||||
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)
|
||||||
@@ -125,34 +133,4 @@ 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,6 +2,7 @@ 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}
|
import gitbucket.core.util.JGitUtil.{CommitInfo, FileInfo, getContentFromId, getFileList, getSummaryMessage}
|
||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
import gitbucket.core.view.helpers.{isRenderable, renderMarkup}
|
import gitbucket.core.view.helpers.{isRenderable, renderMarkup}
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
@@ -49,19 +49,21 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
|||||||
getContents(repository, multiParams("splat").head, params.getOrElse("ref", repository.repository.defaultBranch))
|
getContents(repository, multiParams("splat").head, params.getOrElse("ref", repository.repository.defaultBranch))
|
||||||
})
|
})
|
||||||
|
|
||||||
private def getFileInfo(git: Git, revision: String, pathStr: String, ignoreCase: Boolean): Option[FileInfo] = {
|
private def getFileInfo(git: Git, revision: String, pathStr: String): Option[FileInfo] = {
|
||||||
val (dirName, fileName) = pathStr.lastIndexOf('/') match {
|
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(revision))
|
||||||
case -1 =>
|
getPathObjectId(git, pathStr, revCommit).map { objectId =>
|
||||||
(".", pathStr)
|
FileInfo(
|
||||||
case n =>
|
id = objectId,
|
||||||
(pathStr.take(n), pathStr.drop(n + 1))
|
isDirectory = false,
|
||||||
}
|
name = pathStr.split("/").last,
|
||||||
if (ignoreCase) {
|
path = pathStr.split("/").dropRight(1).mkString("/"),
|
||||||
getFileList(git, revision, dirName, maxFiles = context.settings.repositoryViewer.maxFiles)
|
message = getSummaryMessage(revCommit.getFullMessage, revCommit.getShortMessage),
|
||||||
.find(_.name.toLowerCase.equals(fileName.toLowerCase))
|
commitId = revCommit.getName,
|
||||||
} else {
|
time = revCommit.getAuthorIdent.getWhen,
|
||||||
getFileList(git, revision, dirName, maxFiles = context.settings.repositoryViewer.maxFiles)
|
author = revCommit.getAuthorIdent.getName,
|
||||||
.find(_.name.equals(fileName))
|
mailAddress = revCommit.getAuthorIdent.getEmailAddress,
|
||||||
|
linkUrl = None
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,16 +76,17 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
|||||||
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||||
val fileList = getFileList(git, refStr, path, maxFiles = context.settings.repositoryViewer.maxFiles)
|
val fileList = getFileList(git, refStr, path, maxFiles = context.settings.repositoryViewer.maxFiles)
|
||||||
if (fileList.isEmpty) { // file or NotFound
|
if (fileList.isEmpty) { // file or NotFound
|
||||||
getFileInfo(git, refStr, path, ignoreCase)
|
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(refStr))
|
||||||
.flatMap { f =>
|
getPathObjectId(git, path, revCommit)
|
||||||
|
.flatMap { objectId =>
|
||||||
val largeFile = params.get("large_file").exists(s => s.equals("true"))
|
val largeFile = params.get("large_file").exists(s => s.equals("true"))
|
||||||
val content = getContentFromId(git, f.id, largeFile)
|
val content = getContentFromId(git, objectId, largeFile)
|
||||||
request.getHeader("Accept") match {
|
request.getHeader("Accept") match {
|
||||||
case "application/vnd.github.v3.raw" => {
|
case "application/vnd.github.v3.raw" => {
|
||||||
contentType = "application/vnd.github.v3.raw"
|
contentType = "application/vnd.github.v3.raw"
|
||||||
content
|
content
|
||||||
}
|
}
|
||||||
case "application/vnd.github.v3.html" if isRenderable(f.name) => {
|
case "application/vnd.github.v3.html" if isRenderable(path) => {
|
||||||
contentType = "application/vnd.github.v3.html"
|
contentType = "application/vnd.github.v3.html"
|
||||||
content.map { c =>
|
content.map { c =>
|
||||||
List(
|
List(
|
||||||
@@ -114,11 +117,12 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case _ =>
|
case _ =>
|
||||||
Some(JsonFormat(ApiContents(f, RepositoryName(repository), content)))
|
getFileInfo(git, refStr, path).map { f =>
|
||||||
|
JsonFormat(ApiContents(f, RepositoryName(repository), content))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.getOrElse(NotFound())
|
.getOrElse(NotFound())
|
||||||
|
|
||||||
} else { // directory
|
} else { // directory
|
||||||
JsonFormat(fileList.map { f =>
|
JsonFormat(fileList.map { f =>
|
||||||
ApiContents(f, RepositoryName(repository), None)
|
ApiContents(f, RepositoryName(repository), None)
|
||||||
@@ -148,9 +152,7 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
|||||||
val path = paths.take(paths.size - 1).toList.mkString("/")
|
val path = paths.take(paths.size - 1).toList.mkString("/")
|
||||||
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) {
|
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) {
|
||||||
git =>
|
git =>
|
||||||
val fileInfo = getFileInfo(git, commit, path, false)
|
getFileInfo(git, commit, path) match {
|
||||||
|
|
||||||
fileInfo match {
|
|
||||||
case Some(f) if !data.sha.contains(f.id.getName) =>
|
case Some(f) if !data.sha.contains(f.id.getName) =>
|
||||||
ApiError(
|
ApiError(
|
||||||
"The blob SHA is not matched.",
|
"The blob SHA is not matched.",
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ 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
|
||||||
@@ -185,11 +184,9 @@ 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 =>
|
||||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
JsonFormat(
|
||||||
JsonFormat(
|
repository.tags.map(tagInfo => ApiTag(tagInfo.name, RepositoryName(repository), tagInfo.id))
|
||||||
self.getRef("tags", repository)
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
package gitbucket.core.plugin
|
package gitbucket.core.plugin
|
||||||
|
|
||||||
import javax.servlet.ServletContext
|
import javax.servlet.ServletContext
|
||||||
|
|
||||||
import gitbucket.core.controller.{Context, ControllerBase}
|
import gitbucket.core.controller.{Context, ControllerBase}
|
||||||
import gitbucket.core.model.{Account, Issue}
|
import gitbucket.core.model.{Account, Issue}
|
||||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||||
import io.github.gitbucket.solidbase.model.Version
|
import io.github.gitbucket.solidbase.model.Version
|
||||||
import org.apache.sshd.server.channel.ChannelSession
|
|
||||||
import org.apache.sshd.server.command.Command
|
import org.apache.sshd.server.command.Command
|
||||||
import play.twirl.api.Html
|
import play.twirl.api.Html
|
||||||
|
|
||||||
import scala.util.Using
|
import scala.util.Using
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -324,7 +323,7 @@ abstract class Plugin {
|
|||||||
/**
|
/**
|
||||||
* Override to add ssh command providers.
|
* Override to add ssh command providers.
|
||||||
*/
|
*/
|
||||||
val sshCommandProviders: Seq[PartialFunction[String, ChannelSession => Command]] = Nil
|
val sshCommandProviders: Seq[PartialFunction[String, Command]] = Nil
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override to add ssh command providers.
|
* Override to add ssh command providers.
|
||||||
@@ -333,7 +332,7 @@ abstract class Plugin {
|
|||||||
registry: PluginRegistry,
|
registry: PluginRegistry,
|
||||||
context: ServletContext,
|
context: ServletContext,
|
||||||
settings: SystemSettings
|
settings: SystemSettings
|
||||||
): Seq[PartialFunction[String, ChannelSession => Command]] = Nil
|
): Seq[PartialFunction[String, Command]] = Nil
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is invoked in initialization of plugin system.
|
* This method is invoked in initialization of plugin system.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.nio.file.{Files, Paths, StandardWatchEventKinds}
|
|||||||
import java.util.Base64
|
import java.util.Base64
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
import javax.servlet.ServletContext
|
import javax.servlet.ServletContext
|
||||||
import com.github.zafarkhaja.semver.Version
|
import com.github.zafarkhaja.semver.Version
|
||||||
import gitbucket.core.controller.{Context, ControllerBase}
|
import gitbucket.core.controller.{Context, ControllerBase}
|
||||||
@@ -20,7 +21,6 @@ import io.github.gitbucket.solidbase.Solidbase
|
|||||||
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
|
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
|
||||||
import io.github.gitbucket.solidbase.model.Module
|
import io.github.gitbucket.solidbase.model.Module
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import org.apache.sshd.server.channel.ChannelSession
|
|
||||||
import org.apache.sshd.server.command.Command
|
import org.apache.sshd.server.command.Command
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import play.twirl.api.Html
|
import play.twirl.api.Html
|
||||||
@@ -58,7 +58,7 @@ class PluginRegistry {
|
|||||||
private val suggestionProviders = new ConcurrentLinkedQueue[SuggestionProvider]
|
private val suggestionProviders = new ConcurrentLinkedQueue[SuggestionProvider]
|
||||||
suggestionProviders.add(new UserNameSuggestionProvider())
|
suggestionProviders.add(new UserNameSuggestionProvider())
|
||||||
suggestionProviders.add(new IssueSuggestionProvider())
|
suggestionProviders.add(new IssueSuggestionProvider())
|
||||||
private val sshCommandProviders = new ConcurrentLinkedQueue[PartialFunction[String, ChannelSession => Command]]()
|
private val sshCommandProviders = new ConcurrentLinkedQueue[PartialFunction[String, Command]]()
|
||||||
|
|
||||||
def addPlugin(pluginInfo: PluginInfo): Unit = plugins.add(pluginInfo)
|
def addPlugin(pluginInfo: PluginInfo): Unit = plugins.add(pluginInfo)
|
||||||
|
|
||||||
@@ -177,11 +177,10 @@ class PluginRegistry {
|
|||||||
|
|
||||||
def getSuggestionProviders: Seq[SuggestionProvider] = suggestionProviders.asScala.toSeq
|
def getSuggestionProviders: Seq[SuggestionProvider] = suggestionProviders.asScala.toSeq
|
||||||
|
|
||||||
def addSshCommandProvider(sshCommandProvider: PartialFunction[String, ChannelSession => Command]): Unit =
|
def addSshCommandProvider(sshCommandProvider: PartialFunction[String, Command]): Unit =
|
||||||
sshCommandProviders.add(sshCommandProvider)
|
sshCommandProviders.add(sshCommandProvider)
|
||||||
|
|
||||||
def getSshCommandProviders: Seq[PartialFunction[String, ChannelSession => Command]] =
|
def getSshCommandProviders: Seq[PartialFunction[String, Command]] = sshCommandProviders.asScala.toSeq
|
||||||
sshCommandProviders.asScala.toSeq
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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.commitId }.getOrElse(forkedId)
|
forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.id }.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.commitId }.getOrElse(originId)
|
originRepository.tags.collectFirst { case x if x.name == originId => x.id }.getOrElse(originId)
|
||||||
val forkedId2 =
|
val forkedId2 =
|
||||||
forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.commitId }.getOrElse(forkedId)
|
forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.id }.getOrElse(forkedId)
|
||||||
|
|
||||||
(Option(oldGit.getRepository.resolve(originId2)), Option(newGit.getRepository.resolve(forkedId2)))
|
(Option(oldGit.getRepository.resolve(originId2)), Option(newGit.getRepository.resolve(forkedId2)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ 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 {
|
||||||
@@ -836,10 +835,12 @@ 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] =
|
||||||
context.settings.sshUrl(owner, name)
|
if (context.settings.ssh.enabled) {
|
||||||
|
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,10 +4,9 @@ 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.{getOptionValue, _}
|
import gitbucket.core.service.SystemSettingsService._
|
||||||
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 {
|
||||||
@@ -30,14 +29,8 @@ 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.bindAddress.foreach { bindAddress =>
|
settings.ssh.sshHost.foreach(x => props.setProperty(SshHost, x.trim))
|
||||||
props.setProperty(SshBindAddressHost, bindAddress.host.trim())
|
settings.ssh.sshPort.foreach(x => props.setProperty(SshPort, x.toString))
|
||||||
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 =>
|
||||||
@@ -102,10 +95,6 @@ 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),
|
||||||
@@ -123,20 +112,9 @@ trait SystemSettingsService {
|
|||||||
getValue(props, Notification, false),
|
getValue(props, Notification, false),
|
||||||
getValue(props, LimitVisibleRepositories, false),
|
getValue(props, LimitVisibleRepositories, false),
|
||||||
Ssh(
|
Ssh(
|
||||||
enabled = getValue(props, SshEnabled, false),
|
getValue(props, SshEnabled, false),
|
||||||
bindAddress = {
|
getOptionValue[String](props, SshHost, None).map(_.trim),
|
||||||
// try the new-style configuration first
|
getOptionValue(props, SshPort, Some(DefaultSshPort))
|
||||||
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,
|
||||||
@@ -204,6 +182,7 @@ trait SystemSettingsService {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object SystemSettingsService {
|
object SystemSettingsService {
|
||||||
@@ -235,6 +214,7 @@ 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("/")
|
||||||
|
|
||||||
@@ -251,17 +231,11 @@ object SystemSettingsService {
|
|||||||
.fold(base)(_ + base.dropWhile(_ != ':'))
|
.fold(base)(_ + base.dropWhile(_ != ':'))
|
||||||
}
|
}
|
||||||
|
|
||||||
def sshBindAddress: Option[SshAddress] =
|
def sshAddress: Option[SshAddress] =
|
||||||
ssh.bindAddress
|
ssh.sshHost.collect {
|
||||||
|
case host if ssh.enabled =>
|
||||||
def sshPublicAddress: Option[SshAddress] =
|
SshAddress(host, ssh.sshPort.getOrElse(DefaultSshPort), "git")
|
||||||
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(
|
||||||
@@ -274,35 +248,9 @@ object SystemSettingsService {
|
|||||||
|
|
||||||
case class Ssh(
|
case class Ssh(
|
||||||
enabled: Boolean,
|
enabled: Boolean,
|
||||||
bindAddress: Option[SshAddress],
|
sshHost: Option[String],
|
||||||
publicAddress: Option[SshAddress]
|
sshPort: Option[Int]
|
||||||
) {
|
)
|
||||||
|
|
||||||
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,
|
||||||
@@ -348,25 +296,7 @@ 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])
|
||||||
|
|
||||||
@@ -374,8 +304,6 @@ 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
|
||||||
@@ -397,10 +325,6 @@ 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,6 +3,7 @@ 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
|
||||||
@@ -208,7 +209,9 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
|
|||||||
|
|
||||||
val settings = loadSystemSettings()
|
val settings = loadSystemSettings()
|
||||||
val baseUrl = settings.baseUrl(request)
|
val baseUrl = settings.baseUrl(request)
|
||||||
val sshUrl = settings.sshUrl(owner, repository)
|
val sshUrl = settings.sshAddress.map { x =>
|
||||||
|
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,31 +5,26 @@ 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}
|
import org.apache.sshd.server.{Environment, ExitCallback, SessionAware}
|
||||||
import org.apache.sshd.server.command.{Command, CommandFactory}
|
import org.apache.sshd.server.command.{Command, CommandFactory}
|
||||||
import org.apache.sshd.server.session.{ServerSession, ServerSessionAware}
|
import org.apache.sshd.server.session.ServerSession
|
||||||
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 ServerSessionAware {
|
abstract class GitCommand extends Command with SessionAware {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(classOf[GitCommand])
|
private val logger = LoggerFactory.getLogger(classOf[GitCommand])
|
||||||
|
|
||||||
@@ -62,12 +57,12 @@ abstract class GitCommand extends Command with ServerSessionAware {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final override def start(channel: ChannelSession, env: Environment): Unit = {
|
final override def start(env: Environment): Unit = {
|
||||||
val thread = new Thread(newTask())
|
val thread = new Thread(newTask())
|
||||||
thread.start()
|
thread.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
override def destroy(channel: ChannelSession): Unit = {}
|
override def destroy(): Unit = {}
|
||||||
|
|
||||||
override def setExitCallback(callback: ExitCallback): Unit = {
|
override def setExitCallback(callback: ExitCallback): Unit = {
|
||||||
this.callback = callback
|
this.callback = callback
|
||||||
@@ -164,7 +159,7 @@ class DefaultGitUploadPack(owner: String, repoName: String)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String, sshAddress: SshAddress)
|
class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String, sshUrl: Option[String])
|
||||||
extends DefaultGitCommand(owner, repoName)
|
extends DefaultGitCommand(owner, repoName)
|
||||||
with RepositoryService
|
with RepositoryService
|
||||||
with AccountService
|
with AccountService
|
||||||
@@ -182,8 +177,7 @@ 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 =
|
val hook = new CommitLogHook(owner, repoName, userName(authType), baseUrl, sshUrl)
|
||||||
new CommitLogHook(owner, repoName, userName(authType), baseUrl, Some(sshAddress.getUrl(owner, repoName)))
|
|
||||||
receive.setPreReceiveHook(hook)
|
receive.setPreReceiveHook(hook)
|
||||||
receive.setPostReceiveHook(hook)
|
receive.setPostReceiveHook(hook)
|
||||||
}
|
}
|
||||||
@@ -233,10 +227,10 @@ class PluginGitReceivePack(repoName: String, routing: GitRepositoryRouting)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GitCommandFactory(baseUrl: String, sshAddress: SshAddress) extends CommandFactory {
|
class GitCommandFactory(baseUrl: String, sshUrl: Option[String]) extends CommandFactory {
|
||||||
private val logger = LoggerFactory.getLogger(classOf[GitCommandFactory])
|
private val logger = LoggerFactory.getLogger(classOf[GitCommandFactory])
|
||||||
|
|
||||||
override def createCommand(channel: ChannelSession, command: String): Command = {
|
override def createCommand(command: String): Command = {
|
||||||
import GitCommand._
|
import GitCommand._
|
||||||
logger.debug(s"command: $command")
|
logger.debug(s"command: $command")
|
||||||
|
|
||||||
@@ -244,24 +238,19 @@ class GitCommandFactory(baseUrl: String, sshAddress: SshAddress) extends Command
|
|||||||
case f if f.isDefinedAt(command) => f(command)
|
case f if f.isDefinedAt(command) => f(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginCommand.map(_.apply(channel)).getOrElse {
|
pluginCommand match {
|
||||||
val (simpleRegex, defaultRegex) =
|
case Some(x) => x
|
||||||
if (sshAddress.isDefaultPort) {
|
case None =>
|
||||||
(SimpleCommandRegexPort22, DefaultCommandRegexPort22)
|
command match {
|
||||||
} else {
|
case SimpleCommandRegex("upload", repoName) if (pluginRepository(repoName)) =>
|
||||||
(SimpleCommandRegex, DefaultCommandRegex)
|
new PluginGitUploadPack(repoName, routing(repoName))
|
||||||
|
case SimpleCommandRegex("receive", repoName) if (pluginRepository(repoName)) =>
|
||||||
|
new PluginGitReceivePack(repoName, routing(repoName))
|
||||||
|
case DefaultCommandRegex("upload", owner, repoName) => new DefaultGitUploadPack(owner, repoName)
|
||||||
|
case DefaultCommandRegex("receive", owner, repoName) =>
|
||||||
|
new DefaultGitReceivePack(owner, repoName, baseUrl, sshUrl)
|
||||||
|
case _ => new UnknownCommand(command)
|
||||||
}
|
}
|
||||||
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,23 +1,20 @@
|
|||||||
package gitbucket.core.ssh
|
package gitbucket.core.ssh
|
||||||
|
|
||||||
import gitbucket.core.service.SystemSettingsService.SshAddress
|
import gitbucket.core.service.SystemSettingsService.SshAddress
|
||||||
import org.apache.sshd.server.channel.ChannelSession
|
import org.apache.sshd.common.Factory
|
||||||
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 org.apache.sshd.server.shell.ShellFactory
|
import java.io.{OutputStream, InputStream}
|
||||||
|
|
||||||
import java.io.{InputStream, OutputStream}
|
|
||||||
import org.eclipse.jgit.lib.Constants
|
import org.eclipse.jgit.lib.Constants
|
||||||
|
|
||||||
class NoShell(sshAddress: SshAddress) extends ShellFactory {
|
class NoShell(sshAddress: SshAddress) extends Factory[Command] {
|
||||||
override def createShell(channel: ChannelSession): Command = new Command() {
|
override def create(): 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(channel: ChannelSession, env: Environment): Unit = {
|
override def start(env: Environment): Unit = {
|
||||||
val placeholderAddress = sshAddress.getUrl("OWNER", "REPOSITORY_NAME")
|
|
||||||
val message =
|
val message =
|
||||||
"""
|
"""
|
||||||
| Welcome to
|
| Welcome to
|
||||||
@@ -33,8 +30,8 @@ class NoShell(sshAddress: SshAddress) extends ShellFactory {
|
|||||||
|
|
|
|
||||||
| Please use:
|
| Please use:
|
||||||
|
|
|
|
||||||
| git clone %s
|
| git clone ssh://%s@%s:%d/OWNER/REPOSITORY_NAME.git
|
||||||
""".stripMargin.format(placeholderAddress).replace("\n", "\r\n") + "\r\n"
|
""".stripMargin.format(sshAddress.genericUser, sshAddress.host, sshAddress.port).replace("\n", "\r\n") + "\r\n"
|
||||||
err.write(Constants.encode(message))
|
err.write(Constants.encode(message))
|
||||||
err.flush()
|
err.flush()
|
||||||
in.close()
|
in.close()
|
||||||
@@ -43,7 +40,7 @@ class NoShell(sshAddress: SshAddress) extends ShellFactory {
|
|||||||
callback.onExit(127)
|
callback.onExit(127)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def destroy(channel: ChannelSession): Unit = {}
|
override def destroy(): 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.AttributeRepository
|
import org.apache.sshd.common.AttributeStore
|
||||||
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 AttributeRepository.AttributeKey[AuthType]
|
private val authTypeSessionKey = new AttributeStore.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,7 +1,8 @@
|
|||||||
package gitbucket.core.ssh
|
package gitbucket.core.ssh
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
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
|
||||||
@@ -10,48 +11,40 @@ 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 = new AtomicReference[org.apache.sshd.server.SshServer](null)
|
private val server = org.apache.sshd.server.SshServer.setUpDefaultServer()
|
||||||
|
private val active = new AtomicBoolean(false)
|
||||||
|
|
||||||
private def configure(
|
private def configure(sshAddress: SshAddress, baseUrl: String) = {
|
||||||
bindAddress: SshAddress,
|
server.setPort(sshAddress.port)
|
||||||
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(bindAddress.genericUser))
|
server.setPublickeyAuthenticator(new PublicKeyAuthenticator(sshAddress.genericUser))
|
||||||
server.setCommandFactory(
|
server.setCommandFactory(
|
||||||
new GitCommandFactory(baseUrl, publicAddress)
|
new GitCommandFactory(baseUrl, Some(s"${sshAddress.genericUser}@${sshAddress.host}:${sshAddress.port}"))
|
||||||
)
|
)
|
||||||
server.setShellFactory(new NoShell(publicAddress))
|
server.setShellFactory(new NoShell(sshAddress))
|
||||||
server
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def start(bindAddress: SshAddress, publicAddress: SshAddress, baseUrl: String): Unit = {
|
def start(sshAddress: SshAddress, baseUrl: String) = {
|
||||||
this.server.synchronized {
|
if (active.compareAndSet(false, true)) {
|
||||||
val server = configure(bindAddress, publicAddress, baseUrl)
|
configure(sshAddress, baseUrl)
|
||||||
if (this.server.compareAndSet(null, server)) {
|
server.start()
|
||||||
server.start()
|
logger.info(s"Start SSH Server Listen on ${server.getPort}")
|
||||||
logger.info(s"Start SSH Server Listen on ${server.getPort}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def stop(): Unit = {
|
def stop() = {
|
||||||
this.server.synchronized {
|
if (active.compareAndSet(true, false)) {
|
||||||
val server = this.server.getAndSet(null)
|
server.stop(true)
|
||||||
if (server != null) {
|
logger.info("SSH Server is stopped.")
|
||||||
server.stop()
|
|
||||||
logger.info("SSH Server is stopped.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def isActive = active.get
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -66,14 +59,13 @@ 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.sshBindAddress.isDefined && settings.baseUrl.isEmpty) {
|
if (settings.sshAddress.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 {
|
||||||
bindAddress <- settings.sshBindAddress
|
sshAddress <- settings.sshAddress
|
||||||
publicAddress <- settings.sshPublicAddress
|
|
||||||
baseUrl <- settings.baseUrl
|
baseUrl <- settings.baseUrl
|
||||||
} SshServer.start(bindAddress, publicAddress, baseUrl)
|
} SshServer.start(sshAddress, baseUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def contextDestroyed(sce: ServletContextEvent): Unit = {
|
override def contextDestroyed(sce: ServletContextEvent): Unit = {
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ 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.sshUrl)
|
JsonFormat.Context(context.baseUrl, context.settings.sshAddress.map { x =>
|
||||||
|
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,11 +228,10 @@ object JGitUtil {
|
|||||||
*
|
*
|
||||||
* @param name the tag name
|
* @param name the tag name
|
||||||
* @param time the tagged date
|
* @param time the tagged date
|
||||||
* @param commitId the commit id
|
* @param id 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, commitId: String, message: String, objectId: String)
|
case class TagInfo(name: String, time: Date, id: String, message: String)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The submodule data
|
* The submodule data
|
||||||
@@ -348,8 +347,7 @@ 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 {
|
||||||
@@ -514,7 +512,7 @@ object JGitUtil {
|
|||||||
/**
|
/**
|
||||||
* Returns the first line of the commit message.
|
* Returns the first line of the commit message.
|
||||||
*/
|
*/
|
||||||
private def getSummaryMessage(fullMessage: String, shortMessage: String): String = {
|
def getSummaryMessage(fullMessage: String, shortMessage: String): String = {
|
||||||
val i = fullMessage.trim.indexOf('\n')
|
val i = fullMessage.trim.indexOf('\n')
|
||||||
val firstLine = if (i >= 0) fullMessage.trim.substring(0, i).trim else fullMessage
|
val firstLine = if (i >= 0) fullMessage.trim.substring(0, i).trim else fullMessage
|
||||||
if (firstLine.length > shortMessage.length) shortMessage else firstLine
|
if (firstLine.length > shortMessage.length) shortMessage else firstLine
|
||||||
|
|||||||
@@ -212,19 +212,19 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
|||||||
Html(
|
Html(
|
||||||
message
|
message
|
||||||
.replaceAll("\\[issue:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]"){ m =>
|
.replaceAll("\\[issue:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]"){ m =>
|
||||||
getIssueFromCache(m.group(1), m.group(2), m.group(3)) match {
|
val issue = getIssueFromCache(m.group(1), m.group(2), m.group(3))
|
||||||
case Some(issue) =>
|
if (issue.isDefined) {
|
||||||
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/issues/${m.group(3)}" title="${StringUtil.escapeHtml(issue.title)}">${m.group(1)}/${m.group(2)}#${m.group(3)}</a>"""
|
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/issues/${m.group(3)}" title="${issue.get.title}">${m.group(1)}/${m.group(2)}#${m.group(3)}</a>"""
|
||||||
case None =>
|
} else {
|
||||||
s"${m.group(1)}/${m.group(2)}#${m.group(3)}"
|
s"${m.group(1)}/${m.group(2)}#${m.group(3)}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.replaceAll("\\[pullreq:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]"){ m =>
|
.replaceAll("\\[pullreq:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]"){ m =>
|
||||||
getIssueFromCache(m.group(1), m.group(2), m.group(3)) match {
|
val pullreq = getIssueFromCache(m.group(1), m.group(2), m.group(3))
|
||||||
case Some(pullreq) =>
|
if (pullreq.isDefined) {
|
||||||
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/pull/${m.group(3)}" title="${StringUtil.escapeHtml(pullreq.title)}">${m.group(1)}/${m.group(2)}#${m.group(3)}</a>"""
|
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/pull/${m.group(3)}" title="${pullreq.get.title}">${m.group(1)}/${m.group(2)}#${m.group(3)}</a>"""
|
||||||
case None =>
|
} else {
|
||||||
s"${m.group(1)}/${m.group(2)}#${m.group(3)}"
|
s"${m.group(1)}/${m.group(2)}#${m.group(3)}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.replaceAll("\\[repo:([^\\s]+?)/([^\\s]+?)\\]") { m =>
|
.replaceAll("\\[repo:([^\\s]+?)/([^\\s]+?)\\]") { m =>
|
||||||
|
|||||||
@@ -19,40 +19,22 @@
|
|||||||
<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 bind host and Base URL are required if SSH access is enabled)</span>
|
<span class="muted normal">(Both SSH host and Base URL are required if SSH access is enabled)</span>
|
||||||
</label>
|
</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<div class="ssh">
|
<div class="ssh">
|
||||||
<div class="bindAddress">
|
<div class="form-group">
|
||||||
<div class="form-group">
|
<label class="control-label col-md-2" for="sshHost">SSH host</label>
|
||||||
<label class="control-label col-md-2" for="sshBindHost">SSH bind host</label>
|
<div class="col-md-10">
|
||||||
<div class="col-md-10">
|
<input type="text" id="sshHost" name="ssh.host" class="form-control" value="@context.settings.ssh.sshHost"/>
|
||||||
<input type="text" id="sshBindHost" name="ssh.bindAddress.host" class="form-control" value="@context.settings.ssh.bindAddress.map(_.host)"/>
|
<span id="error-ssh_host" class="error"></span>
|
||||||
<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="publicAddress">
|
<div class="form-group">
|
||||||
<div class="form-group">
|
<label class="control-label col-md-2" for="sshPort">SSH port</label>
|
||||||
<label class="control-label col-md-2" for="sshPublicHost">SSH public host</label>
|
<div class="col-md-10">
|
||||||
<div class="col-md-10">
|
<input type="text" id="sshPort" name="ssh.port" class="form-control" value="@context.settings.ssh.sshPort"/>
|
||||||
<input type="text" id="sshPublicHost" name="ssh.publicAddress.host" class="form-control" value="@context.settings.ssh.publicAddress.map(_.host)"/>
|
<span id="error-ssh_port" class="error"></span>
|
||||||
<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>
|
||||||
|
|||||||
@@ -24,15 +24,57 @@
|
|||||||
case "create_tag" => simpleActivity(activity)
|
case "create_tag" => simpleActivity(activity)
|
||||||
case "delete_tag" => simpleActivity(activity)
|
case "delete_tag" => simpleActivity(activity)
|
||||||
case "fork" => simpleActivity(activity)
|
case "fork" => simpleActivity(activity)
|
||||||
case "push" => pushActivity(activity)
|
case "push" => customActivity(activity){
|
||||||
case "create_wiki" => createWikiActivity(activity)
|
<div class="small activity-message">
|
||||||
case "edit_wiki" => editWikiActivity(activity)
|
{activity.additionalInfo.get.split("\n").reverse.take(4).zipWithIndex.map{ case (commit, i) =>
|
||||||
case "delete_wiki" => simpleActivity(activity)
|
if(i == 3){
|
||||||
|
<div>...</div>
|
||||||
|
} else {
|
||||||
|
if(commit.nonEmpty){
|
||||||
|
<div>
|
||||||
|
<a href={s"${context.path}/${activity.userName}/${activity.repositoryName}/commit/${commit. substring(0, 40)}"} class="monospace">{commit.substring(0, 7)}</a>
|
||||||
|
<span>{commit.substring(41)}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
case "create_wiki" => customActivity(activity){
|
||||||
|
<div class="small activity-message">
|
||||||
|
Created <a href={s"${context.path}/${activity.userName}/${activity.repositoryName}/wiki/${activity.additionalInfo.get}"}>{activity.additionalInfo.get}</a>.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
case "edit_wiki" => customActivity(activity){
|
||||||
|
activity.additionalInfo.get.split(":") match {
|
||||||
|
case Array(pageName, commitId) =>
|
||||||
|
<div class="small activity-message">
|
||||||
|
Edited <a href={s"${context.path}/${activity.userName}/${activity.repositoryName}/wiki/${pageName}"}>{pageName}</a>.
|
||||||
|
<a href={s"${context.path}/${activity.userName}/${activity.repositoryName}/wiki/${pageName}/_compare/${commitId.substring(0, 7)}^...${commitId.substring(0, 7)}"}>View the diff »</a>
|
||||||
|
</div>
|
||||||
|
case Array(pageName) =>
|
||||||
|
<div class="small activity-message">
|
||||||
|
Edited <a href={s"${context.path}/${activity.userName}/${activity.repositoryName}/wiki/${pageName}"}>{pageName}</a>.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "delete_wiki" => simpleActivity(activity)
|
||||||
})
|
})
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@customActivity(activity: gitbucket.core.model.Activity)(additionalInfo: Any) = {
|
||||||
|
<div>
|
||||||
|
<div class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</div>
|
||||||
|
<div class="strong">
|
||||||
|
@helpers.avatarLink(activity.activityUserName, 16)
|
||||||
|
@helpers.activityMessage(activity.message)
|
||||||
|
</div>
|
||||||
|
@additionalInfo
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
@simpleActivity(activity: gitbucket.core.model.Activity) = {
|
@simpleActivity(activity: gitbucket.core.model.Activity) = {
|
||||||
<div>
|
<div>
|
||||||
<span class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</span>
|
<span class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</span>
|
||||||
@@ -43,66 +85,3 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@pushActivity(activity: gitbucket.core.model.Activity) = {
|
|
||||||
<div>
|
|
||||||
<div class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</div>
|
|
||||||
<div class="strong">
|
|
||||||
@helpers.avatarLink(activity.activityUserName, 16)
|
|
||||||
@helpers.activityMessage(activity.message)
|
|
||||||
</div>
|
|
||||||
<div class="small activity-message">
|
|
||||||
@activity.additionalInfo.get.split("\n").reverse.take(4).zipWithIndex.map { case (commit, i) =>
|
|
||||||
@if(i == 3){
|
|
||||||
<div>...</div>
|
|
||||||
} else {
|
|
||||||
@if(commit.nonEmpty){
|
|
||||||
<div>
|
|
||||||
<a href="@{context.path}/@{activity.userName}/@{activity.repositoryName}/commit/@{commit. substring(0, 40)}" class="monospace">@{commit.substring(0, 7)}</a>
|
|
||||||
<span>@{commit.substring(41)}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@createWikiActivity(activity: gitbucket.core.model.Activity) = {
|
|
||||||
<div>
|
|
||||||
<div class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</div>
|
|
||||||
<div class="strong">
|
|
||||||
@helpers.avatarLink(activity.activityUserName, 16)
|
|
||||||
@helpers.activityMessage(activity.message)
|
|
||||||
</div>
|
|
||||||
<div class="small activity-message">
|
|
||||||
Created <a href="@{context.path}/@{activity.userName}/@{activity.repositoryName}/wiki/@{activity.additionalInfo}">@{activity.additionalInfo}</a>.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@editWikiActivity(activity: gitbucket.core.model.Activity) = {
|
|
||||||
<div>
|
|
||||||
<div class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</div>
|
|
||||||
<div class="strong">
|
|
||||||
@helpers.avatarLink(activity.activityUserName, 16)
|
|
||||||
@helpers.activityMessage(activity.message)
|
|
||||||
</div>
|
|
||||||
@defining(activity.additionalInfo.get.split(":")){ additionalInfo =>
|
|
||||||
@if(additionalInfo.length == 2) {
|
|
||||||
@defining((additionalInfo(0), additionalInfo(1))) { case (pageName, commitId) =>
|
|
||||||
<div class="small activity-message">
|
|
||||||
Edited <a href="@{context.path}/@{activity.userName}/@{activity.repositoryName}/wiki/@pageName">@pageName</a>.
|
|
||||||
<a href="@{context.path}/@{activity.userName}/@{activity.repositoryName}/wiki/@{pageName}/_compare/@{commitId.substring(0, 7)}^...@{commitId.substring(0, 7)}">View the diff »</a>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@if(additionalInfo.length == 1) {
|
|
||||||
@defining(additionalInfo(0)) { pageName =>
|
|
||||||
<div class="small activity-message">
|
|
||||||
Edited <a href="@{context.path}/@{activity.userName}/@{activity.repositoryName}/wiki/@{pageName}">@pageName</a>.
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -40,6 +40,8 @@
|
|||||||
</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.commitId" class="monospace muted"><i class="octicon octicon-git-commit"></i>@tag.commitId.substring(0, 7)</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>
|
||||||
<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.commitId" class="monospace muted"><i class="octicon octicon-git-commit"></i>@tag.commitId.substring(0, 7)</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>
|
||||||
<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-dark').removeClass('highlight-dark');
|
$('li.highlight').removeClass('highlight-dark');
|
||||||
} else {
|
} else {
|
||||||
$('li.highlight').removeClass('highlight');
|
$('li.highlight').removeClass('highlight');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
<?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,8 +52,6 @@ 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,13 +2,10 @@ 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
|
import org.kohsuke.github.{GHCommitState, GitHub}
|
||||||
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Need to run `sbt package` before running this test.
|
* Need to run `sbt package` before running this test.
|
||||||
@@ -137,27 +134,6 @@ 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,20 +83,8 @@ object ApiSpecModels {
|
|||||||
milestoneCount = 1,
|
milestoneCount = 1,
|
||||||
branchList = Seq("master", "develop"),
|
branchList = Seq("master", "develop"),
|
||||||
tags = Seq(
|
tags = Seq(
|
||||||
TagInfo(
|
TagInfo(name = "v1.0", time = date("2015-05-05T23:40:27Z"), id = "id1", message = "1.0 released"),
|
||||||
name = "v1.0",
|
TagInfo(name = "v2.0", time = date("2016-05-05T23:40:27Z"), id = "id2", message = "2.0 released")
|
||||||
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")
|
||||||
)
|
)
|
||||||
@@ -444,29 +432,9 @@ object ApiSpecModels {
|
|||||||
|
|
||||||
val apiPusher = ApiPusher(account)
|
val apiPusher = ApiPusher(account)
|
||||||
|
|
||||||
//have both urls as https, as the expected samples are using https
|
val apiRef = ApiRef(
|
||||||
val gitHubContext = JsonFormat.Context("https://api.github.com", Some("https://api.github.com"))
|
ref = "refs/heads/featureA",
|
||||||
|
`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"
|
||||||
@@ -797,33 +765,8 @@ 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,12 +8,6 @@ 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))
|
||||||
@@ -82,11 +76,8 @@ class JsonFormatSpec extends AnyFunSuite {
|
|||||||
test("apiPusher") {
|
test("apiPusher") {
|
||||||
assert(JsonFormat(apiPusher) == expected(jsonPusher))
|
assert(JsonFormat(apiPusher) == expected(jsonPusher))
|
||||||
}
|
}
|
||||||
test("apiRefHead") {
|
test("apiRef") {
|
||||||
assertEqualJson(JsonFormat(apiRefHeadsMaster)(gitHubContext), jsonRefHeadsMaster)
|
assert(JsonFormat(apiRef) == expected(jsonRef))
|
||||||
}
|
|
||||||
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,
|
||||||
bindAddress = None,
|
sshHost = None,
|
||||||
publicAddress = None
|
sshPort = None
|
||||||
),
|
),
|
||||||
useSMTP = false,
|
useSMTP = false,
|
||||||
smtp = None,
|
smtp = None,
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
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,102 +1,38 @@
|
|||||||
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.matchers.should.Matchers
|
import org.scalatest.funspec.AnyFunSpec
|
||||||
import org.scalatest.wordspec.AnyWordSpec
|
|
||||||
|
|
||||||
class GitCommandFactorySpec extends AnyWordSpec with Matchers {
|
class GitCommandFactorySpec extends AnyFunSpec {
|
||||||
|
|
||||||
trait MockContext {
|
val factory = new GitCommandFactory("http://localhost:8080", None)
|
||||||
val baseUrl = "https://some.example.tech:8080/code-context"
|
|
||||||
val sshHost = "localhost"
|
|
||||||
val sshPort = 2222
|
|
||||||
lazy val factory = new GitCommandFactory(baseUrl, SshAddress(sshHost, sshPort, "git"))
|
|
||||||
}
|
|
||||||
|
|
||||||
"createCommand" when {
|
describe("createCommand") {
|
||||||
val channel = new ChannelSession()
|
it("should return GitReceivePack when command is git-receive-pack") {
|
||||||
"receiving a git-receive-pack command" should {
|
assert(factory.createCommand("git-receive-pack '/owner/repo.git'").isInstanceOf[DefaultGitReceivePack] == true)
|
||||||
"return DefaultGitReceivePack" when {
|
assert(
|
||||||
"the path matches owner/repo" in new MockContext {
|
factory.createCommand("git-receive-pack '/owner/repo.wiki.git'").isInstanceOf[DefaultGitReceivePack] == true
|
||||||
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 {
|
it("should return GitUploadPack when command is git-upload-pack") {
|
||||||
"return DefaultGitUploadPack" when {
|
assert(factory.createCommand("git-upload-pack '/owner/repo.git'").isInstanceOf[DefaultGitUploadPack] == true)
|
||||||
"the path matches owner/repo" in new MockContext {
|
assert(factory.createCommand("git-upload-pack '/owner/repo.wiki.git'").isInstanceOf[DefaultGitUploadPack] == true)
|
||||||
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 {
|
it("should return UnknownCommand when command is not git-(upload|receive)-pack") {
|
||||||
"return UnknownCommand" in new MockContext {
|
assert(factory.createCommand("git- '/owner/repo.git'").isInstanceOf[UnknownCommand] == true)
|
||||||
assert(factory.createCommand(channel, "git-destroy-pack '/owner/repo.git'").isInstanceOf[UnknownCommand])
|
assert(factory.createCommand("git-pack '/owner/repo.git'").isInstanceOf[UnknownCommand] == true)
|
||||||
assert(factory.createCommand(channel, "git-irrigate-pack '/apples.git'").isInstanceOf[UnknownCommand])
|
assert(factory.createCommand("git-a-pack '/owner/repo.git'").isInstanceOf[UnknownCommand] == true)
|
||||||
assert(factory.createCommand(channel, "git-force-push '/stolen/nuke.git'").isInstanceOf[UnknownCommand])
|
assert(factory.createCommand("git-up-pack '/owner/repo.git'").isInstanceOf[UnknownCommand] == true)
|
||||||
assert(factory.createCommand(channel, "git-delete '/backups.git'").isInstanceOf[UnknownCommand])
|
assert(factory.createCommand("\ngit-upload-pack '/owner/repo.git'").isInstanceOf[UnknownCommand] == true)
|
||||||
assert(factory.createCommand(channel, "git-pack '/your/bags.git'").isInstanceOf[UnknownCommand])
|
}
|
||||||
assert(factory.createCommand(channel, "git- '/bananas.git'").isInstanceOf[UnknownCommand])
|
it("should return UnknownCommand when git command has no valid arguments") {
|
||||||
assert(factory.createCommand(channel, "99 tickets of bugs on the wall").isInstanceOf[UnknownCommand])
|
// 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,8 +166,8 @@ class AvatarImageProviderSpec extends AnyFunSpec {
|
|||||||
limitVisibleRepositories = false,
|
limitVisibleRepositories = false,
|
||||||
ssh = Ssh(
|
ssh = Ssh(
|
||||||
enabled = false,
|
enabled = false,
|
||||||
bindAddress = None,
|
sshHost = None,
|
||||||
publicAddress = None
|
sshPort = None
|
||||||
),
|
),
|
||||||
useSMTP = false,
|
useSMTP = false,
|
||||||
smtp = None,
|
smtp = None,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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