Merge branch 'wip/baseurl' of https://github.com/ritschwumm/gitbucket into ritschwumm-wip/baseurl

This commit is contained in:
Naoki Takezoe
2016-02-12 23:15:59 +09:00
27 changed files with 189 additions and 148 deletions

View File

@@ -133,7 +133,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
val members = getGroupMembers(account.userName) val members = getGroupMembers(account.userName)
gitbucket.core.account.html.repositories(account, gitbucket.core.account.html.repositories(account,
if(account.isGroupAccount) Nil else getGroupsByUserName(userName), if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
getVisibleRepositories(context.loginAccount, context.baseUrl, Some(userName)), getVisibleRepositories(context.loginAccount, Some(userName)),
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager })) context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }))
} }
} }
@@ -366,7 +366,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
*/ */
post("/new", newRepositoryForm)(usersOnly { form => post("/new", newRepositoryForm)(usersOnly { form =>
LockUtil.lock(s"${form.owner}/${form.name}"){ LockUtil.lock(s"${form.owner}/${form.name}"){
if(getRepository(form.owner, form.name, context.baseUrl).isEmpty){ if(getRepository(form.owner, form.name).isEmpty){
createRepository(form.owner, form.name, form.description, form.isPrivate, form.createReadme) createRepository(form.owner, form.name, form.description, form.isPrivate, form.createReadme)
} }
@@ -385,9 +385,9 @@ trait AccountControllerBase extends AccountManagementControllerBase {
data <- extractFromJsonBody[CreateARepository] if data.isValid data <- extractFromJsonBody[CreateARepository] if data.isValid
} yield { } yield {
LockUtil.lock(s"${owner}/${data.name}") { LockUtil.lock(s"${owner}/${data.name}") {
if(getRepository(owner, data.name, context.baseUrl).isEmpty){ if(getRepository(owner, data.name).isEmpty){
createRepository(owner, data.name, data.description, data.`private`, data.auto_init) createRepository(owner, data.name, data.description, data.`private`, data.auto_init)
val repository = getRepository(owner, data.name, context.baseUrl).get val repository = getRepository(owner, data.name).get
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(owner).get))) JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(owner).get)))
} else { } else {
ApiError( ApiError(
@@ -409,9 +409,9 @@ trait AccountControllerBase extends AccountManagementControllerBase {
data <- extractFromJsonBody[CreateARepository] if data.isValid data <- extractFromJsonBody[CreateARepository] if data.isValid
} yield { } yield {
LockUtil.lock(s"${groupName}/${data.name}") { LockUtil.lock(s"${groupName}/${data.name}") {
if(getRepository(groupName, data.name, context.baseUrl).isEmpty){ if(getRepository(groupName, data.name).isEmpty){
createRepository(groupName, data.name, data.description, data.`private`, data.auto_init) createRepository(groupName, data.name, data.description, data.`private`, data.auto_init)
val repository = getRepository(groupName, data.name, context.baseUrl).get val repository = getRepository(groupName, data.name).get
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(groupName).get))) JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(groupName).get)))
} else { } else {
ApiError( ApiError(
@@ -447,7 +447,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
val accountName = form.accountName val accountName = form.accountName
LockUtil.lock(s"${accountName}/${repository.name}"){ LockUtil.lock(s"${accountName}/${repository.name}"){
if(getRepository(accountName, repository.name, baseUrl).isDefined || if(getRepository(accountName, repository.name).isDefined ||
(accountName != loginUserName && !getGroupsByUserName(loginUserName).contains(accountName))){ (accountName != loginUserName && !getGroupsByUserName(loginUserName).contains(accountName))){
// redirect to the repository if repository already exists // redirect to the repository if repository already exists
redirect(s"/${accountName}/${repository.name}") redirect(s"/${accountName}/${repository.name}")

View File

@@ -3,6 +3,7 @@ package gitbucket.core.controller
import gitbucket.core.api.ApiError import gitbucket.core.api.ApiError
import gitbucket.core.model.Account import gitbucket.core.model.Account
import gitbucket.core.service.{AccountService, SystemSettingsService} import gitbucket.core.service.{AccountService, SystemSettingsService}
import gitbucket.core.service.RepositoryService.{RepositoryInfo, RepositoryUrls}
import gitbucket.core.util.ControlUtil._ import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
@@ -180,11 +181,12 @@ abstract class ControllerBase extends ScalatraFilter
* Context object for the current request. * Context object for the current request.
*/ */
case class Context(settings: SystemSettingsService.SystemSettings, loginAccount: Option[Account], request: HttpServletRequest){ case class Context(settings: SystemSettingsService.SystemSettings, loginAccount: Option[Account], request: HttpServletRequest){
val path = settings.baseUrl.getOrElse(request.getContextPath) val path = settings.baseUrl.getOrElse(request.getContextPath)
val currentPath = request.getRequestURI.substring(request.getContextPath.length) val currentPath = request.getRequestURI.substring(request.getContextPath.length)
val baseUrl = settings.baseUrl(request) val baseUrl = settings.baseUrl(request)
val host = new java.net.URL(baseUrl).getHost val host = new java.net.URL(baseUrl).getHost
val urls = (repositoryInfo:RepositoryInfo) => new RepositoryUrls(baseUrl, settings.sshAddress, repositoryInfo.owner, repositoryInfo.name)
val wikiUrls = (repositoryInfo:RepositoryInfo) => new RepositoryUrls(baseUrl, settings.sshAddress, repositoryInfo.owner, repositoryInfo.name + ".wiki")
val platform = request.getHeader("User-Agent") match { val platform = request.getHeader("User-Agent") match {
case null => null case null => null
case agent if agent.contains("Mac") => "mac" case agent if agent.contains("Mac") => "mac"

View File

@@ -94,7 +94,7 @@ trait DashboardControllerBase extends ControllerBase {
val userName = context.loginAccount.get.userName val userName = context.loginAccount.get.userName
val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName) val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName)
val userRepos = getUserRepositories(userName, context.baseUrl, true).map(repo => repo.owner -> repo.name) val userRepos = getUserRepositories(userName, true).map(repo => repo.owner -> repo.name)
val page = IssueSearchCondition.page(request) val page = IssueSearchCondition.page(request)
html.issues( html.issues(

View File

@@ -29,8 +29,8 @@ trait IndexControllerBase extends ControllerBase {
val loginAccount = context.loginAccount val loginAccount = context.loginAccount
if(loginAccount.isEmpty) { if(loginAccount.isEmpty) {
html.index(getRecentActivities(), html.index(getRecentActivities(),
getVisibleRepositories(loginAccount, context.baseUrl, withoutPhysicalInfo = true), getVisibleRepositories(loginAccount, withoutPhysicalInfo = true),
loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl, withoutPhysicalInfo = true) }.getOrElse(Nil) loginAccount.map{ account => getUserRepositories(account.userName, withoutPhysicalInfo = true) }.getOrElse(Nil)
) )
} else { } else {
val loginUserName = loginAccount.get.userName val loginUserName = loginAccount.get.userName
@@ -40,8 +40,8 @@ trait IndexControllerBase extends ControllerBase {
visibleOwnerSet ++= loginUserGroups visibleOwnerSet ++= loginUserGroups
html.index(getRecentActivitiesByOwners(visibleOwnerSet), html.index(getRecentActivitiesByOwners(visibleOwnerSet),
getVisibleRepositories(loginAccount, context.baseUrl, withoutPhysicalInfo = true), getVisibleRepositories(loginAccount, withoutPhysicalInfo = true),
loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl, withoutPhysicalInfo = true) }.getOrElse(Nil) loginAccount.map{ account => getUserRepositories(account.userName, withoutPhysicalInfo = true) }.getOrElse(Nil)
) )
} }
} }

View File

@@ -137,7 +137,7 @@ trait PullRequestsControllerBase extends ControllerBase {
baseOwner <- users.get(repository.owner) baseOwner <- users.get(repository.owner)
headOwner <- users.get(pullRequest.requestUserName) headOwner <- users.get(pullRequest.requestUserName)
issueUser <- users.get(issue.openedUserName) issueUser <- users.get(issue.openedUserName)
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName, baseUrl) headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
} yield { } yield {
JsonFormat(ApiPullRequest( JsonFormat(ApiPullRequest(
issue, issue,
@@ -196,7 +196,7 @@ trait PullRequestsControllerBase extends ControllerBase {
issue, issue,
pullreq, pullreq,
repository, repository,
getRepository(pullreq.requestUserName, pullreq.requestRepositoryName, context.baseUrl).get) getRepository(pullreq.requestUserName, pullreq.requestRepositoryName).get)
} }
} getOrElse NotFound } getOrElse NotFound
}) })
@@ -229,7 +229,7 @@ trait PullRequestsControllerBase extends ControllerBase {
if(branchProtection.needStatusCheck(loginAccount.userName)){ if(branchProtection.needStatusCheck(loginAccount.userName)){
flash += "error" -> s"branch ${pullreq.requestBranch} is protected need status check." flash += "error" -> s"branch ${pullreq.requestBranch} is protected need status check."
} else { } else {
val repository = getRepository(owner, name, context.baseUrl).get val repository = getRepository(owner, name).get
LockUtil.lock(s"${owner}/${name}"){ LockUtil.lock(s"${owner}/${name}"){
val alias = if(pullreq.repositoryName == pullreq.requestRepositoryName && pullreq.userName == pullreq.requestUserName){ val alias = if(pullreq.repositoryName == pullreq.requestRepositoryName && pullreq.userName == pullreq.requestUserName){
pullreq.branch pullreq.branch
@@ -310,7 +310,7 @@ trait PullRequestsControllerBase extends ControllerBase {
pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.commitIdTo) pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.commitIdTo)
// close issue by content of pull request // close issue by content of pull request
val defaultBranch = getRepository(owner, name, context.baseUrl).get.repository.defaultBranch val defaultBranch = getRepository(owner, name).get.repository.defaultBranch
if(pullreq.branch == defaultBranch){ if(pullreq.branch == defaultBranch){
commits.flatten.foreach { commit => commits.flatten.foreach { commit =>
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name) closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name)
@@ -343,7 +343,7 @@ trait PullRequestsControllerBase extends ControllerBase {
val headBranch:Option[String] = params.get("head") val headBranch:Option[String] = params.get("head")
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match { (forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
case (Some(originUserName), Some(originRepositoryName)) => { case (Some(originUserName), Some(originRepositoryName)) => {
getRepository(originUserName, originRepositoryName, context.baseUrl).map { originRepository => getRepository(originUserName, originRepositoryName).map { originRepository =>
using( using(
Git.open(getRepositoryDir(originUserName, originRepositoryName)), Git.open(getRepositoryDir(originUserName, originRepositoryName)),
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name)) Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
@@ -384,12 +384,12 @@ trait PullRequestsControllerBase extends ControllerBase {
forkedRepository.repository.originRepositoryName forkedRepository.repository.originRepositoryName
} else { } else {
// Sibling repository // Sibling repository
getUserRepositories(originOwner, context.baseUrl).find { x => getUserRepositories(originOwner).find { x =>
x.repository.originUserName == forkedRepository.repository.originUserName && x.repository.originUserName == forkedRepository.repository.originUserName &&
x.repository.originRepositoryName == forkedRepository.repository.originRepositoryName x.repository.originRepositoryName == forkedRepository.repository.originRepositoryName
}.map(_.repository.repositoryName) }.map(_.repository.repositoryName)
}; };
originRepository <- getRepository(originOwner, originRepositoryName, context.baseUrl) originRepository <- getRepository(originOwner, originRepositoryName)
) yield { ) yield {
using( using(
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)), Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),
@@ -457,7 +457,7 @@ trait PullRequestsControllerBase extends ControllerBase {
getForkedRepositories(forkedRepository.owner, forkedRepository.name).find(_._1 == originOwner).map(_._2) getForkedRepositories(forkedRepository.owner, forkedRepository.name).find(_._1 == originOwner).map(_._2)
} }
}; };
originRepository <- getRepository(originOwner, originRepositoryName, context.baseUrl) originRepository <- getRepository(originOwner, originRepositoryName)
) yield { ) yield {
using( using(
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)), Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),

View File

@@ -560,8 +560,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
html.forked( html.forked(
getRepository( getRepository(
repository.repository.originUserName.getOrElse(repository.owner), repository.repository.originUserName.getOrElse(repository.owner),
repository.repository.originRepositoryName.getOrElse(repository.name), repository.repository.originRepositoryName.getOrElse(repository.name)),
context.baseUrl),
getForkedRepositories( getForkedRepositories(
repository.repository.originUserName.getOrElse(repository.owner), repository.repository.originUserName.getOrElse(repository.owner),
repository.repository.originRepositoryName.getOrElse(repository.name)), repository.repository.originRepositoryName.getOrElse(repository.name)),

View File

@@ -23,6 +23,7 @@ trait SystemSettingsControllerBase extends ControllerBase {
"notification" -> trim(label("Notification", boolean())), "notification" -> trim(label("Notification", boolean())),
"activityLogLimit" -> trim(label("Limit of activity logs", optional(number()))), "activityLogLimit" -> trim(label("Limit of activity logs", optional(number()))),
"ssh" -> trim(label("SSH access", boolean())), "ssh" -> trim(label("SSH access", boolean())),
"sshHost" -> trim(label("SSH host", optional(text()))),
"sshPort" -> trim(label("SSH port", optional(number()))), "sshPort" -> trim(label("SSH port", optional(number()))),
"useSMTP" -> trim(label("SMTP", boolean())), "useSMTP" -> trim(label("SMTP", boolean())),
"smtp" -> optionalIfNotChecked("useSMTP", mapping( "smtp" -> optionalIfNotChecked("useSMTP", mapping(
@@ -50,9 +51,14 @@ trait SystemSettingsControllerBase extends ControllerBase {
"keystore" -> trim(label("Keystore", optional(text()))) "keystore" -> trim(label("Keystore", optional(text())))
)(Ldap.apply)) )(Ldap.apply))
)(SystemSettings.apply).verifying { settings => )(SystemSettings.apply).verifying { settings =>
Vector(
if(settings.ssh && settings.baseUrl.isEmpty){ if(settings.ssh && settings.baseUrl.isEmpty){
Seq("baseUrl" -> "Base URL is required if SSH access is enabled.") Some("baseUrl" -> "Base URL is required if SSH access is enabled.")
} else Nil } else None,
if(settings.ssh && settings.sshHost.isEmpty){
Some("sshHost" -> "SSH host is required if SSH access is enabled.")
} else None
).flatten
} }
private val pluginForm = mapping( private val pluginForm = mapping(
@@ -68,16 +74,13 @@ trait SystemSettingsControllerBase extends ControllerBase {
post("/admin/system", form)(adminOnly { form => post("/admin/system", form)(adminOnly { form =>
saveSystemSettings(form) saveSystemSettings(form)
if(form.ssh && SshServer.isActive && context.settings.sshPort != form.sshPort){ if (form.sshAddress != context.settings.sshAddress) {
SshServer.stop() SshServer.stop()
for {
sshAddress <- form.sshAddress
baseUrl <- form.baseUrl
} }
SshServer.start(sshAddress, baseUrl)
if(form.ssh && !SshServer.isActive && form.baseUrl.isDefined){
SshServer.start(
form.sshPort.getOrElse(SystemSettingsService.DefaultSshPort),
form.baseUrl.get)
} else if(!form.ssh && SshServer.isActive){
SshServer.stop()
} }
flash += "info" -> "System settings has been updated." flash += "info" -> "System settings has been updated."

View File

@@ -1,5 +1,6 @@
package gitbucket.core.service package gitbucket.core.service
import gitbucket.core.service.SystemSettingsService.SshAddress
import gitbucket.core.model.{Collaborator, Repository, Account} import gitbucket.core.model.{Collaborator, Repository, Account}
import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile._
import gitbucket.core.util.JGitUtil import gitbucket.core.util.JGitUtil
@@ -194,10 +195,9 @@ trait RepositoryService { self: AccountService =>
* *
* @param userName the user name of the repository owner * @param userName the user name of the repository owner
* @param repositoryName the repository name * @param repositoryName the repository name
* @param baseUrl the base url of this application
* @return the repository information * @return the repository information
*/ */
def getRepository(userName: String, repositoryName: String, baseUrl: String)(implicit s: Session): Option[RepositoryInfo] = { def getRepository(userName: String, repositoryName: String)(implicit s: Session): Option[RepositoryInfo] = {
(Repositories filter { t => t.byRepository(userName, repositoryName) } firstOption) map { repository => (Repositories filter { t => t.byRepository(userName, repositoryName) } firstOption) map { repository =>
// for getting issue count and pull request count // for getting issue count and pull request count
val issues = Issues.filter { t => val issues = Issues.filter { t =>
@@ -205,7 +205,7 @@ trait RepositoryService { self: AccountService =>
}.map(_.pullRequest).list }.map(_.pullRequest).list
new RepositoryInfo( new RepositoryInfo(
JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl), JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName),
repository, repository,
issues.count(_ == false), issues.count(_ == false),
issues.count(_ == true), issues.count(_ == true),
@@ -234,7 +234,7 @@ trait RepositoryService { self: AccountService =>
}.list }.list
} }
def getUserRepositories(userName: String, baseUrl: String, withoutPhysicalInfo: Boolean = false) def getUserRepositories(userName: String, withoutPhysicalInfo: Boolean = false)
(implicit s: Session): List[RepositoryInfo] = { (implicit s: Session): List[RepositoryInfo] = {
Repositories.filter { t1 => Repositories.filter { t1 =>
(t1.userName === userName.bind) || (t1.userName === userName.bind) ||
@@ -242,9 +242,9 @@ trait RepositoryService { self: AccountService =>
}.sortBy(_.lastActivityDate desc).list.map{ repository => }.sortBy(_.lastActivityDate desc).list.map{ repository =>
new RepositoryInfo( new RepositoryInfo(
if(withoutPhysicalInfo){ if(withoutPhysicalInfo){
new JGitUtil.RepositoryInfo(repository.userName, repository.repositoryName, baseUrl) new JGitUtil.RepositoryInfo(repository.userName, repository.repositoryName)
} else { } else {
JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl) JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName)
}, },
repository, repository,
getForkedCount( getForkedCount(
@@ -260,13 +260,12 @@ trait RepositoryService { self: AccountService =>
* If repositoryUserName is given then filters results by repository owner. * If repositoryUserName is given then filters results by repository owner.
* *
* @param loginAccount the logged in account * @param loginAccount the logged in account
* @param baseUrl the base url of this application
* @param repositoryUserName the repository owner (if None then returns all repositories which are visible for logged in user) * @param repositoryUserName the repository owner (if None then returns all repositories which are visible for logged in user)
* @param withoutPhysicalInfo if true then the result does not include physical repository information such as commit count, * @param withoutPhysicalInfo if true then the result does not include physical repository information such as commit count,
* branches and tags * branches and tags
* @return the repository information which is sorted in descending order of lastActivityDate. * @return the repository information which is sorted in descending order of lastActivityDate.
*/ */
def getVisibleRepositories(loginAccount: Option[Account], baseUrl: String, repositoryUserName: Option[String] = None, def getVisibleRepositories(loginAccount: Option[Account], repositoryUserName: Option[String] = None,
withoutPhysicalInfo: Boolean = false) withoutPhysicalInfo: Boolean = false)
(implicit s: Session): List[RepositoryInfo] = { (implicit s: Session): List[RepositoryInfo] = {
(loginAccount match { (loginAccount match {
@@ -284,9 +283,9 @@ trait RepositoryService { self: AccountService =>
}.sortBy(_.lastActivityDate desc).list.map{ repository => }.sortBy(_.lastActivityDate desc).list.map{ repository =>
new RepositoryInfo( new RepositoryInfo(
if(withoutPhysicalInfo){ if(withoutPhysicalInfo){
new JGitUtil.RepositoryInfo(repository.userName, repository.repositoryName, baseUrl) new JGitUtil.RepositoryInfo(repository.userName, repository.repositoryName)
} else { } else {
JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl) JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName)
}, },
repository, repository,
getForkedCount( getForkedCount(
@@ -389,31 +388,45 @@ trait RepositoryService { self: AccountService =>
object RepositoryService { object RepositoryService {
case class RepositoryInfo(owner: String, name: String, httpUrl: String, repository: Repository, case class RepositoryInfo(owner: String, name: String, repository: Repository,
issueCount: Int, pullCount: Int, commitCount: Int, forkedCount: Int, issueCount: Int, pullCount: Int, commitCount: Int, forkedCount: Int,
branchList: Seq[String], tags: Seq[JGitUtil.TagInfo], managers: Seq[String]){ branchList: Seq[String], tags: Seq[JGitUtil.TagInfo], managers: Seq[String]) {
lazy val host = """^https?://(.+?)(:\d+)?/""".r.findFirstMatchIn(httpUrl).get.group(1)
def sshUrl(port: Int, userName: String) = s"ssh://${userName}@${host}:${port}/${owner}/${name}.git"
def sshOpenRepoUrl(platform: String, port: Int, userName: String) = openRepoUrl(platform, sshUrl(port, userName))
def httpOpenRepoUrl(platform: String) = openRepoUrl(platform, httpUrl)
def openRepoUrl(platform: String, openUrl: String) = s"github-${platform}://openRepo/${openUrl}"
/** /**
* Creates instance with issue count and pull request count. * Creates instance with issue count and pull request count.
*/ */
def this(repo: JGitUtil.RepositoryInfo, model: Repository, issueCount: Int, pullCount: Int, forkedCount: Int, managers: Seq[String]) = def this(repo: JGitUtil.RepositoryInfo, model: Repository, issueCount: Int, pullCount: Int, forkedCount: Int, managers: Seq[String]) =
this(repo.owner, repo.name, repo.url, model, issueCount, pullCount, repo.commitCount, forkedCount, repo.branchList, repo.tags, managers) this(
repo.owner, repo.name, model,
issueCount, pullCount,
repo.commitCount, forkedCount, repo.branchList, repo.tags, managers)
/** /**
* Creates instance without issue count and pull request count. * Creates instance without issue count and pull request count.
*/ */
def this(repo: JGitUtil.RepositoryInfo, model: Repository, forkedCount: Int, managers: Seq[String]) = def this(repo: JGitUtil.RepositoryInfo, model: Repository, forkedCount: Int, managers: Seq[String]) =
this(repo.owner, repo.name, repo.url, model, 0, 0, repo.commitCount, forkedCount, repo.branchList, repo.tags, managers) this(
repo.owner, repo.name, model,
0, 0,
repo.commitCount, forkedCount, repo.branchList, repo.tags, managers)
}
final class RepositoryUrls(baseUrl:String, sshAddress:Option[SshAddress], owner:String, name:String) {
def httpUrl:String =
s"${baseUrl}/git/${owner}/${name}.git"
// BETTER make this return an Option and use it in the gui
def sshUrl(userName: String):String =
sshAddress.fold("")(adr => s"ssh://${userName}@${adr.host}:${adr.port}/${owner}/${name}.git")
def sshOpenRepoUrl(platform: String, userName: String) =
openRepoUrl(platform, sshUrl(userName))
def httpOpenRepoUrl(platform: String) =
openRepoUrl(platform, httpUrl)
private def openRepoUrl(platform: String, openUrl: String) =
s"github-${platform}://openRepo/${openUrl}"
} }
case class RepositoryTreeNode(owner: String, name: String, children: List[RepositoryTreeNode]) case class RepositoryTreeNode(owner: String, name: String, children: List[RepositoryTreeNode])

View File

@@ -1,6 +1,7 @@
package gitbucket.core.service package gitbucket.core.service
import gitbucket.core.util.{Directory, ControlUtil} import gitbucket.core.util.{Directory, ControlUtil}
import gitbucket.core.util.Implicits._
import Directory._ import Directory._
import ControlUtil._ import ControlUtil._
import SystemSettingsService._ import SystemSettingsService._
@@ -21,6 +22,7 @@ trait SystemSettingsService {
props.setProperty(Notification, settings.notification.toString) props.setProperty(Notification, settings.notification.toString)
settings.activityLogLimit.foreach(x => props.setProperty(ActivityLogLimit, x.toString)) settings.activityLogLimit.foreach(x => props.setProperty(ActivityLogLimit, x.toString))
props.setProperty(Ssh, settings.ssh.toString) props.setProperty(Ssh, settings.ssh.toString)
settings.sshHost.foreach(x => props.setProperty(SshHost, x.trim))
settings.sshPort.foreach(x => props.setProperty(SshPort, x.toString)) settings.sshPort.foreach(x => props.setProperty(SshPort, x.toString))
props.setProperty(UseSMTP, settings.useSMTP.toString) props.setProperty(UseSMTP, settings.useSMTP.toString)
if(settings.useSMTP) { if(settings.useSMTP) {
@@ -75,6 +77,7 @@ trait SystemSettingsService {
getValue(props, Notification, false), getValue(props, Notification, false),
getOptionValue[Int](props, ActivityLogLimit, None), getOptionValue[Int](props, ActivityLogLimit, None),
getValue(props, Ssh, false), getValue(props, Ssh, false),
getOptionValue[String](props, SshHost, None).map(_.trim),
getOptionValue(props, SshPort, Some(DefaultSshPort)), getOptionValue(props, SshPort, Some(DefaultSshPort)),
getValue(props, UseSMTP, getValue(props, Notification, false)), // handle migration scenario from only notification to useSMTP getValue(props, UseSMTP, getValue(props, Notification, false)), // handle migration scenario from only notification to useSMTP
if(getValue(props, UseSMTP, getValue(props, Notification, false))){ if(getValue(props, UseSMTP, getValue(props, Notification, false))){
@@ -126,16 +129,20 @@ object SystemSettingsService {
notification: Boolean, notification: Boolean,
activityLogLimit: Option[Int], activityLogLimit: Option[Int],
ssh: Boolean, ssh: Boolean,
sshHost: Option[String],
sshPort: Option[Int], sshPort: Option[Int],
useSMTP: Boolean, useSMTP: Boolean,
smtp: Option[Smtp], smtp: Option[Smtp],
ldapAuthentication: Boolean, ldapAuthentication: Boolean,
ldap: Option[Ldap]){ ldap: Option[Ldap]){
def baseUrl(request: HttpServletRequest): String = baseUrl.getOrElse { def baseUrl(request: HttpServletRequest): String = baseUrl.fold(request.baseUrl)(_.stripSuffix("/"))
defining(request.getRequestURL.toString){ url =>
url.substring(0, url.length - (request.getRequestURI.length - request.getContextPath.length)) def sshAddress:Option[SshAddress] =
for {
host <- sshHost
if ssh
} }
}.stripSuffix("/") yield SshAddress(host, sshPort.getOrElse(DefaultSshPort))
} }
case class Ldap( case class Ldap(
@@ -161,6 +168,10 @@ object SystemSettingsService {
fromAddress: Option[String], fromAddress: Option[String],
fromName: Option[String]) fromName: Option[String])
case class SshAddress(
host:String,
port:Int)
val DefaultSshPort = 29418 val DefaultSshPort = 29418
val DefaultSmtpPort = 25 val DefaultSmtpPort = 25
val DefaultLdapPort = 389 val DefaultLdapPort = 389
@@ -174,6 +185,7 @@ object SystemSettingsService {
private val Notification = "notification" private val Notification = "notification"
private val ActivityLogLimit = "activity_log_limit" private val ActivityLogLimit = "activity_log_limit"
private val Ssh = "ssh" private val Ssh = "ssh"
private val SshHost = "ssh.host"
private val SshPort = "ssh.port" private val SshPort = "ssh.port"
private val UseSMTP = "useSMTP" private val UseSMTP = "useSMTP"
private val SmtpHost = "smtp.host" private val SmtpHost = "smtp.host"

View File

@@ -161,7 +161,7 @@ trait WebHookPullRequestService extends WebHookService {
baseOwner <- users.get(repository.owner) baseOwner <- users.get(repository.owner)
headOwner <- users.get(pullRequest.requestUserName) headOwner <- users.get(pullRequest.requestUserName)
issueUser <- users.get(issue.openedUserName) issueUser <- users.get(issue.openedUserName)
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName, baseUrl) headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
} yield { } yield {
WebHookPullRequestPayload( WebHookPullRequestPayload(
action = action, action = action,
@@ -200,7 +200,7 @@ trait WebHookPullRequestService extends WebHookService {
import WebHookService._ import WebHookService._
for{ for{
((issue, issueUser, pullRequest, baseOwner, headOwner), webHooks) <- getPullRequestsByRequestForWebhook(requestRepository.owner, requestRepository.name, requestBranch) ((issue, issueUser, pullRequest, baseOwner, headOwner), webHooks) <- getPullRequestsByRequestForWebhook(requestRepository.owner, requestRepository.name, requestBranch)
baseRepo <- getRepository(pullRequest.userName, pullRequest.repositoryName, baseUrl) baseRepo <- getRepository(pullRequest.userName, pullRequest.repositoryName)
} yield { } yield {
val payload = WebHookPullRequestPayload( val payload = WebHookPullRequestPayload(
action = action, action = action,
@@ -229,7 +229,7 @@ trait WebHookPullRequestReviewCommentService extends WebHookService {
baseOwner <- users.get(repository.owner) baseOwner <- users.get(repository.owner)
headOwner <- users.get(pullRequest.requestUserName) headOwner <- users.get(pullRequest.requestUserName)
issueUser <- users.get(issue.openedUserName) issueUser <- users.get(issue.openedUserName)
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName, baseUrl) headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
} yield { } yield {
WebHookPullRequestReviewCommentPayload( WebHookPullRequestReviewCommentPayload(
action = action, action = action,

View File

@@ -1,6 +1,7 @@
package gitbucket.core.service package gitbucket.core.service
import java.util.Date import java.util.Date
import gitbucket.core.service.SystemSettingsService.SshAddress
import gitbucket.core.model.Account import gitbucket.core.model.Account
import gitbucket.core.util._ import gitbucket.core.util._
import gitbucket.core.util.ControlUtil._ import gitbucket.core.util.ControlUtil._
@@ -14,6 +15,7 @@ import org.eclipse.jgit.patch._
import org.eclipse.jgit.api.errors.PatchFormatException import org.eclipse.jgit.api.errors.PatchFormatException
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import RepositoryService.RepositoryInfo import RepositoryService.RepositoryInfo
import RepositoryService.RepositoryUrls
object WikiService { object WikiService {
@@ -37,11 +39,6 @@ object WikiService {
* @param date the commit date * @param date the commit date
*/ */
case class WikiPageHistoryInfo(name: String, committer: String, message: String, date: Date) case class WikiPageHistoryInfo(name: String, committer: String, message: String, date: Date)
def httpUrl(repository: RepositoryInfo) = repository.httpUrl.replaceFirst("\\.git\\Z", ".wiki.git")
def sshUrl(repository: RepositoryInfo, settings: SystemSettingsService.SystemSettings, userName: String) =
repository.sshUrl(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), userName).replaceFirst("\\.git\\Z", ".wiki.git")
} }
trait WikiService { trait WikiService {

View File

@@ -74,7 +74,7 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
request.paths match { request.paths match {
case Array(_, repositoryOwner, repositoryName, _*) => case Array(_, repositoryOwner, repositoryName, _*) =>
getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki\\.git$|\\.git$", ""), "") match { getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki\\.git$|\\.git$", "")) match {
case Some(repository) => { case Some(repository) => {
if(!isUpdating && !repository.repository.isPrivate && settings.allowAnonymousAccess){ if(!isUpdating && !repository.repository.isPrivate && settings.allowAnonymousAccess){
chain.doFilter(request, response) chain.doFilter(request, response)

View File

@@ -160,7 +160,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
countIssue(IssueSearchCondition(state = "open"), false, owner -> repository) + countIssue(IssueSearchCondition(state = "open"), false, owner -> repository) +
countIssue(IssueSearchCondition(state = "closed"), false, owner -> repository) countIssue(IssueSearchCondition(state = "closed"), false, owner -> repository)
val repositoryInfo = getRepository(owner, repository, baseUrl).get val repositoryInfo = getRepository(owner, repository).get
// Extract new commit and apply issue comment // Extract new commit and apply issue comment
val defaultBranch = repositoryInfo.repository.defaultBranch val defaultBranch = repositoryInfo.repository.defaultBranch

View File

@@ -87,11 +87,11 @@ abstract class DefaultGitCommand(val owner: String, val repoName: String) extend
} }
class DefaultGitUploadPack(owner: String, repoName: String, baseUrl: String) extends DefaultGitCommand(owner, repoName) class DefaultGitUploadPack(owner: String, repoName: String) extends DefaultGitCommand(owner, repoName)
with RepositoryService with AccountService { with RepositoryService with AccountService {
override protected def runTask(user: String)(implicit session: Session): Unit = { override protected def runTask(user: String)(implicit session: Session): Unit = {
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", ""), baseUrl).foreach { repositoryInfo => getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")).foreach { repositoryInfo =>
if(!repositoryInfo.repository.isPrivate || isWritableUser(user, repositoryInfo)){ if(!repositoryInfo.repository.isPrivate || isWritableUser(user, repositoryInfo)){
using(Git.open(getRepositoryDir(owner, repoName))) { git => using(Git.open(getRepositoryDir(owner, repoName))) { git =>
val repository = git.getRepository val repository = git.getRepository
@@ -107,7 +107,7 @@ class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String) ex
with RepositoryService with AccountService { with RepositoryService with AccountService {
override protected def runTask(user: String)(implicit session: Session): Unit = { override protected def runTask(user: String)(implicit session: Session): Unit = {
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", ""), baseUrl).foreach { repositoryInfo => getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")).foreach { repositoryInfo =>
if(isWritableUser(user, repositoryInfo)){ if(isWritableUser(user, repositoryInfo)){
using(Git.open(getRepositoryDir(owner, repoName))) { git => using(Git.open(getRepositoryDir(owner, repoName))) { git =>
val repository = git.getRepository val repository = git.getRepository
@@ -124,7 +124,7 @@ class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String) ex
} }
} }
class PluginGitUploadPack(repoName: String, baseUrl: String, routing: GitRepositoryRouting) extends GitCommand class PluginGitUploadPack(repoName: String, routing: GitRepositoryRouting) extends GitCommand
with SystemSettingsService { with SystemSettingsService {
override protected def runTask(user: String)(implicit session: Session): Unit = { override protected def runTask(user: String)(implicit session: Session): Unit = {
@@ -139,7 +139,7 @@ class PluginGitUploadPack(repoName: String, baseUrl: String, routing: GitReposit
} }
} }
class PluginGitReceivePack(repoName: String, baseUrl: String, routing: GitRepositoryRouting) extends GitCommand class PluginGitReceivePack(repoName: String, routing: GitRepositoryRouting) extends GitCommand
with SystemSettingsService { with SystemSettingsService {
override protected def runTask(user: String)(implicit session: Session): Unit = { override protected def runTask(user: String)(implicit session: Session): Unit = {
@@ -163,9 +163,9 @@ class GitCommandFactory(baseUrl: String) extends CommandFactory {
logger.debug(s"command: $command") logger.debug(s"command: $command")
command match { command match {
case SimpleCommandRegex ("upload" , repoName) if(pluginRepository(repoName)) => new PluginGitUploadPack (repoName, baseUrl, routing(repoName)) case SimpleCommandRegex ("upload" , repoName) if(pluginRepository(repoName)) => new PluginGitUploadPack (repoName, routing(repoName))
case SimpleCommandRegex ("receive", repoName) if(pluginRepository(repoName)) => new PluginGitReceivePack(repoName, baseUrl, routing(repoName)) case SimpleCommandRegex ("receive", repoName) if(pluginRepository(repoName)) => new PluginGitReceivePack(repoName, routing(repoName))
case DefaultCommandRegex("upload" , owner, repoName) => new DefaultGitUploadPack (owner, repoName, baseUrl) case DefaultCommandRegex("upload" , owner, repoName) => new DefaultGitUploadPack (owner, repoName)
case DefaultCommandRegex("receive", owner, repoName) => new DefaultGitReceivePack(owner, repoName, baseUrl) case DefaultCommandRegex("receive", owner, repoName) => new DefaultGitReceivePack(owner, repoName, baseUrl)
case _ => new UnknownCommand(command) case _ => new UnknownCommand(command)
} }

View File

@@ -1,12 +1,13 @@
package gitbucket.core.ssh package gitbucket.core.ssh
import gitbucket.core.service.SystemSettingsService import gitbucket.core.service.SystemSettingsService
import gitbucket.core.service.SystemSettingsService.SshAddress
import org.apache.sshd.common.Factory import org.apache.sshd.common.Factory
import org.apache.sshd.server.{Environment, ExitCallback, Command} import org.apache.sshd.server.{Environment, ExitCallback, Command}
import java.io.{OutputStream, InputStream} import java.io.{OutputStream, InputStream}
import org.eclipse.jgit.lib.Constants import org.eclipse.jgit.lib.Constants
class NoShell extends Factory[Command] with SystemSettingsService { class NoShell(sshAddress:SshAddress) extends Factory[Command] {
override def create(): 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
@@ -15,7 +16,6 @@ class NoShell extends Factory[Command] with SystemSettingsService {
override def start(env: Environment): Unit = { override def start(env: Environment): Unit = {
val user = env.getEnv.get("USER") val user = env.getEnv.get("USER")
val port = loadSystemSettings().sshPort.getOrElse(SystemSettingsService.DefaultSshPort)
val message = val message =
""" """
| Welcome to | Welcome to
@@ -31,8 +31,8 @@ class NoShell extends Factory[Command] with SystemSettingsService {
| |
| Please use: | Please use:
| |
| git clone ssh://%s@GITBUCKET_HOST:%d/OWNER/REPOSITORY_NAME.git | git clone ssh://%s@%s:%d/OWNER/REPOSITORY_NAME.git
""".stripMargin.format(user, port).replace("\n", "\r\n") + "\r\n" """.stripMargin.format(user, 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()

View File

@@ -5,7 +5,8 @@ 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.util.Directory import gitbucket.core.service.SystemSettingsService.SshAddress
import gitbucket.core.util.{Directory}
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@@ -14,20 +15,20 @@ object SshServer {
private val server = org.apache.sshd.server.SshServer.setUpDefaultServer() private val server = org.apache.sshd.server.SshServer.setUpDefaultServer()
private val active = new AtomicBoolean(false) private val active = new AtomicBoolean(false)
private def configure(port: Int, baseUrl: String) = { private def configure(sshAddress: SshAddress, baseUrl: String) = {
server.setPort(port) server.setPort(sshAddress.port)
val provider = new SimpleGeneratorHostKeyProvider(new File(s"${Directory.GitBucketHome}/gitbucket.ser")) val provider = new SimpleGeneratorHostKeyProvider(new File(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) server.setPublickeyAuthenticator(new PublicKeyAuthenticator)
server.setCommandFactory(new GitCommandFactory(baseUrl)) server.setCommandFactory(new GitCommandFactory(baseUrl))
server.setShellFactory(new NoShell) server.setShellFactory(new NoShell(sshAddress))
} }
def start(port: Int, baseUrl: String) = { def start(sshAddress: SshAddress, baseUrl: String) = {
if(active.compareAndSet(false, true)){ if(active.compareAndSet(false, true)){
configure(port, baseUrl) configure(sshAddress, baseUrl)
server.start() server.start()
logger.info(s"Start SSH Server Listen on ${server.getPort}") logger.info(s"Start SSH Server Listen on ${server.getPort}")
} }
@@ -55,20 +56,18 @@ 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.ssh){ if (settings.sshAddress.isDefined && settings.baseUrl.isEmpty) {
settings.baseUrl match {
case None =>
logger.error("Could not start SshServer because the baseUrl is not configured.") logger.error("Could not start SshServer because the baseUrl is not configured.")
case Some(baseUrl) =>
SshServer.start(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), baseUrl)
} }
for {
sshAddress <- settings.sshAddress
baseUrl <- settings.baseUrl
} }
SshServer.start(sshAddress, baseUrl)
} }
override def contextDestroyed(sce: ServletContextEvent): Unit = { override def contextDestroyed(sce: ServletContextEvent): Unit = {
if(loadSystemSettings().ssh){
SshServer.stop() SshServer.stop()
} }
}
} }

View File

@@ -36,7 +36,7 @@ trait OwnerAuthenticator { self: ControllerBase with RepositoryService with Acco
private def authenticate(action: (RepositoryInfo) => Any) = { private def authenticate(action: (RepositoryInfo) => Any) = {
{ {
defining(request.paths){ paths => defining(request.paths){ paths =>
getRepository(paths(0), paths(1), baseUrl).map { repository => getRepository(paths(0), paths(1)).map { repository =>
context.loginAccount match { context.loginAccount match {
case Some(x) if(x.isAdmin) => action(repository) case Some(x) if(x.isAdmin) => action(repository)
case Some(x) if(repository.owner == x.userName) => action(repository) case Some(x) if(repository.owner == x.userName) => action(repository)
@@ -95,7 +95,7 @@ trait CollaboratorsAuthenticator { self: ControllerBase with RepositoryService =
private def authenticate(action: (RepositoryInfo) => Any) = { private def authenticate(action: (RepositoryInfo) => Any) = {
{ {
defining(request.paths){ paths => defining(request.paths){ paths =>
getRepository(paths(0), paths(1), baseUrl).map { repository => getRepository(paths(0), paths(1)).map { repository =>
context.loginAccount match { context.loginAccount match {
case Some(x) if(x.isAdmin) => action(repository) case Some(x) if(x.isAdmin) => action(repository)
case Some(x) if(paths(0) == x.userName) => action(repository) case Some(x) if(paths(0) == x.userName) => action(repository)
@@ -118,7 +118,7 @@ trait ReferrerAuthenticator { self: ControllerBase with RepositoryService =>
private def authenticate(action: (RepositoryInfo) => Any) = { private def authenticate(action: (RepositoryInfo) => Any) = {
{ {
defining(request.paths){ paths => defining(request.paths){ paths =>
getRepository(paths(0), paths(1), baseUrl).map { repository => getRepository(paths(0), paths(1)).map { repository =>
if(!repository.repository.isPrivate){ if(!repository.repository.isPrivate){
action(repository) action(repository)
} else { } else {
@@ -145,7 +145,7 @@ trait ReadableUsersAuthenticator { self: ControllerBase with RepositoryService =
private def authenticate(action: (RepositoryInfo) => Any) = { private def authenticate(action: (RepositoryInfo) => Any) = {
{ {
defining(request.paths){ paths => defining(request.paths){ paths =>
getRepository(paths(0), paths(1), baseUrl).map { repository => getRepository(paths(0), paths(1)).map { repository =>
context.loginAccount match { context.loginAccount match {
case Some(x) if(x.isAdmin) => action(repository) case Some(x) if(x.isAdmin) => action(repository)
case Some(x) if(!repository.repository.isPrivate) => action(repository) case Some(x) if(!repository.repository.isPrivate) => action(repository)

View File

@@ -75,6 +75,11 @@ object Implicits {
def gitRepositoryPath: String = request.getRequestURI.replaceFirst("^/git/", "/") def gitRepositoryPath: String = request.getRequestURI.replaceFirst("^/git/", "/")
def baseUrl:String = {
val url = request.getRequestURL.toString
val len = url.length - (request.getRequestURI.length - request.getContextPath.length)
url.substring(0, len).stripSuffix("/")
}
} }
implicit class RichSession(session: HttpSession){ implicit class RichSession(session: HttpSession){

View File

@@ -32,14 +32,13 @@ object JGitUtil {
* *
* @param owner the user name of the repository owner * @param owner the user name of the repository owner
* @param name the repository name * @param name the repository name
* @param url the repository URL
* @param commitCount the commit count. If the repository has over 1000 commits then this property is 1001. * @param commitCount the commit count. If the repository has over 1000 commits then this property is 1001.
* @param branchList the list of branch names * @param branchList the list of branch names
* @param tags the list of tags * @param tags the list of tags
*/ */
case class RepositoryInfo(owner: String, name: String, url: String, commitCount: Int, branchList: List[String], tags: List[TagInfo]){ case class RepositoryInfo(owner: String, name: String, commitCount: Int, branchList: List[String], tags: List[TagInfo]){
def this(owner: String, name: String, baseUrl: String) = { def this(owner: String, name: String) = {
this(owner, name, s"${baseUrl}/git/${owner}/${name}.git", 0, Nil, Nil) this(owner, name, 0, Nil, Nil)
} }
} }
@@ -174,14 +173,14 @@ object JGitUtil {
/** /**
* Returns the repository information. It contains branch names and tag names. * Returns the repository information. It contains branch names and tag names.
*/ */
def getRepositoryInfo(owner: String, repository: String, baseUrl: String): RepositoryInfo = { def getRepositoryInfo(owner: String, repository: String): RepositoryInfo = {
using(Git.open(getRepositoryDir(owner, repository))){ git => using(Git.open(getRepositoryDir(owner, repository))){ git =>
try { try {
// get commit count // get commit count
val commitCount = git.log.all.call.iterator.asScala.map(_ => 1).take(10001).sum val commitCount = git.log.all.call.iterator.asScala.map(_ => 1).take(10001).sum
RepositoryInfo( RepositoryInfo(
owner, repository, s"${baseUrl}/git/${owner}/${repository}.git", owner, repository,
// commit count // commit count
commitCount, commitCount,
// branches // branches
@@ -197,7 +196,7 @@ object JGitUtil {
} catch { } catch {
// not initialized // not initialized
case e: NoHeadException => RepositoryInfo( case e: NoHeadException => RepositoryInfo(
owner, repository, s"${baseUrl}/git/${owner}/${repository}.git", 0, Nil, Nil) owner, repository, 0, Nil, Nil)
} }
} }

View File

@@ -60,6 +60,8 @@ object Markdown {
pages: List[String]) pages: List[String])
(implicit val context: Context) extends Renderer(options) with LinkConverter with RequestCache { (implicit val context: Context) extends Renderer(options) with LinkConverter with RequestCache {
private val repositoryUrls = context.urls(repository)
override def heading(text: String, level: Int, raw: String): String = { override def heading(text: String, level: Int, raw: String): String = {
val id = generateAnchorName(text) val id = generateAnchorName(text)
val out = new StringBuilder() val out = new StringBuilder()
@@ -135,7 +137,7 @@ object Markdown {
(link, link) (link, link)
} }
val url = repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/" + StringUtil.urlEncode(page) val url = repositoryUrls.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/" + StringUtil.urlEncode(page)
if(pages.contains(page)){ if(pages.contains(page)){
"<a href=\"" + url + "\">" + escape(label) + "</a>" "<a href=\"" + url + "\">" + escape(label) + "</a>"
} else { } else {
@@ -157,14 +159,14 @@ object Markdown {
} else if(context.currentPath.contains("/tree/")){ } else if(context.currentPath.contains("/tree/")){
val paths = context.currentPath.split("/") val paths = context.currentPath.split("/")
val branch = if(paths.length > 3) paths.drop(4).mkString("/") else repository.repository.defaultBranch val branch = if(paths.length > 3) paths.drop(4).mkString("/") else repository.repository.defaultBranch
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "") repositoryUrls.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "")
} else { } else {
val paths = context.currentPath.split("/") val paths = context.currentPath.split("/")
val branch = if(paths.length > 3) paths.last else repository.repository.defaultBranch val branch = if(paths.length > 3) paths.last else repository.repository.defaultBranch
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "") repositoryUrls.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "")
} }
} else { } else {
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/_blob/" + url repositoryUrls.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/_blob/" + url
} }
} }

View File

@@ -111,13 +111,22 @@
Enable SSH access to git repository Enable SSH access to git repository
</label> </label>
</fieldset> </fieldset>
<div class="form-group ssh"> <div class="ssh">
<div class="form-group">
<label class="control-label col-md-3" for="sshHost">SSH Host</label>
<div class="col-md-9">
<input type="text" id="sshHost" name="sshHost" class="form-control" value="@settings.sshHost"/>
<span id="error-sshHost" class="error"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-3" for="sshPort">SSH Port</label> <label class="control-label col-md-3" for="sshPort">SSH Port</label>
<div class="col-md-9"> <div class="col-md-9">
<input type="text" id="sshPort" name="sshPort" class="form-control" value="@settings.sshPort"/> <input type="text" id="sshPort" name="sshPort" class="form-control" value="@settings.sshPort"/>
<span id="error-sshPort" class="error"></span> <span id="error-sshPort" class="error"></span>
</div> </div>
</div> </div>
</div>
<p class="muted"> <p class="muted">
Base URL is required if SSH access is enabled. Base URL is required if SSH access is enabled.
</p> </p>

View File

@@ -37,7 +37,7 @@
<script src="@assets/vendors/jquery-hotkeys/jquery.hotkeys.js"></script> <script src="@assets/vendors/jquery-hotkeys/jquery.hotkeys.js"></script>
@repository.map { repository => @repository.map { repository =>
@if(!repository.repository.isPrivate){ @if(!repository.repository.isPrivate){
<meta name="go-import" content="@context.baseUrl.replaceFirst("^https?://", "")/@repository.owner/@repository.name git @repository.httpUrl" /> <meta name="go-import" content="@context.baseUrl.replaceFirst("^https?://", "")/@repository.owner/@repository.name git @context.urls(repository).httpUrl" />
} }
} }
</head> </head>

View File

@@ -80,8 +80,8 @@
<div class="small"> <div class="small">
<strong id="repository-url-proto">HTTP</strong> <span class="mute">clone URL</span> <strong id="repository-url-proto">HTTP</strong> <span class="mute">clone URL</span>
</div> </div>
@helper.html.copy("repository-url-copy", repository.httpUrl){ @helper.html.copy("repository-url-copy", context.urls(repository).httpUrl){
<input type="text" value="@repository.httpUrl" id="repository-url" class="form-control input-sm" readonly> <input type="text" value="@context.urls(repository).httpUrl" id="repository-url" class="form-control input-sm" readonly>
} }
@if(settings.ssh && loginAccount.isDefined){ @if(settings.ssh && loginAccount.isDefined){
<div class="small"> <div class="small">
@@ -91,7 +91,7 @@
@id.map { id => @id.map { id =>
@if(context.platform != "linux" && context.platform != null){ @if(context.platform != "linux" && context.platform != null){
<div style="margin-top: 10px;"> <div style="margin-top: 10px;">
<a href="@repository.httpOpenRepoUrl(context.platform)" id="repository-clone-url" class="btn btn-sm btn-default btn-block"><i class="octicon octicon-desktop-download"></i>&nbsp;&nbsp;Clone in Desktop</a> <a href="@context.urls(repository).httpOpenRepoUrl(context.platform)" id="repository-clone-url" class="btn btn-sm btn-default btn-block"><i class="octicon octicon-desktop-download"></i>&nbsp;&nbsp;Clone in Desktop</a>
</div> </div>
} }
<div style="margin-top: 10px;"> <div style="margin-top: 10px;">
@@ -184,15 +184,15 @@ $(function(){
@if(settings.ssh && loginAccount.isDefined){ @if(settings.ssh && loginAccount.isDefined){
$('#repository-url-http').click(function(){ $('#repository-url-http').click(function(){
$('#repository-url-proto').text('HTTP'); $('#repository-url-proto').text('HTTP');
$('#repository-url').val('@repository.httpUrl'); $('#repository-url').val('@context.urls(repository).httpUrl');
$('#repository-clone-url').attr('href', '@repository.httpOpenRepoUrl(context.platform)') $('#repository-clone-url').attr('href', '@context.urls(repository).httpOpenRepoUrl(context.platform)')
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val()); $('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
}); });
$('#repository-url-ssh').click(function(){ $('#repository-url-ssh').click(function(){
$('#repository-url-proto').text('SSH'); $('#repository-url-proto').text('SSH');
$('#repository-url').val('@repository.sshUrl(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), loginAccount.get.userName)'); $('#repository-url').val('@context.urls(repository).sshUrl(loginAccount.get.userName)');
$('#repository-clone-url').attr('href', '@repository.sshOpenRepoUrl(context.platform, settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), loginAccount.get.userName)'); $('#repository-clone-url').attr('href', '@context.urls(repository).sshOpenRepoUrl(context.platform, loginAccount.get.userName)');
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val()); $('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
}); });
} }

View File

@@ -100,21 +100,21 @@
you can perform a manual merge on the command line. you can perform a manual merge on the command line.
</p> </p>
} }
@helper.html.copy("repository-url-copy", forkedRepository.httpUrl, true){ @helper.html.copy("repository-url-copy", context.urls(forkedRepository).httpUrl, true){
<div class="btn-group" data-toggle="buttons-radio"> <div class="btn-group" data-toggle="buttons-radio">
<button class="btn btn-small active" type="button" id="repository-url-http">HTTP</button> <button class="btn btn-small active" type="button" id="repository-url-http">HTTP</button>
@if(settings.ssh && loginAccount.isDefined){ @if(settings.ssh && loginAccount.isDefined){
<button class="btn btn-small" type="button" id="repository-url-ssh" style="border-radius: 0px;">SSH</button> <button class="btn btn-small" type="button" id="repository-url-ssh" style="border-radius: 0px;">SSH</button>
} }
</div> </div>
<input type="text" style="width: 500px;" value="@forkedRepository.httpUrl" id="repository-url" readonly /> <input type="text" style="width: 500px;" value="@context.urls(forkedRepository).httpUrl" id="repository-url" readonly />
} }
<div> <div>
<p> <p>
<span class="strong">Step 1:</span> From your project repository, check out a new branch and test the changes. <span class="strong">Step 1:</span> From your project repository, check out a new branch and test the changes.
</p> </p>
@defining(s"git checkout -b ${pullreq.requestUserName}-${pullreq.requestBranch} ${pullreq.branch}\n" + @defining(s"git checkout -b ${pullreq.requestUserName}-${pullreq.requestBranch} ${pullreq.branch}\n" +
s"git pull ${forkedRepository.httpUrl} ${pullreq.requestBranch}"){ command => s"git pull ${context.urls(forkedRepository).httpUrl} ${pullreq.requestBranch}"){ command =>
@helper.html.copy("merge-command-copy-1", command){ @helper.html.copy("merge-command-copy-1", command){
<pre style="width: 600px; float: left; font-size: 12px; border-radius: 3px 0px 3px 3px;" id="merge-command">@Html(command)</pre> <pre style="width: 600px; float: left; font-size: 12px; border-radius: 3px 0px 3px 3px;" id="merge-command">@Html(command)</pre>
} }
@@ -174,24 +174,24 @@ $(function(){
@if(settings.ssh && loginAccount.isDefined){ @if(settings.ssh && loginAccount.isDefined){
$('#repository-url-http').click(function(){ $('#repository-url-http').click(function(){
// Update URL box // Update URL box
$('#repository-url').val('@forkedRepository.httpUrl'); $('#repository-url').val('@context.urls(forkedRepository).httpUrl');
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val()); $('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
// Update command guidance // Update command guidance
$('#merge-command').text($('#merge-command').text().replace( $('#merge-command').text($('#merge-command').text().replace(
'@forkedRepository.sshUrl(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), loginAccount.get.userName)', '@context.urls(forkedRepository).sshUrl(loginAccount.get.userName)',
'@forkedRepository.httpUrl' '@context.urls(forkedRepository).httpUrl'
)); ));
$('#merge-command-copy-1').attr('data-clipboard-text', $('#merge-command').text()); $('#merge-command-copy-1').attr('data-clipboard-text', $('#merge-command').text());
}); });
$('#repository-url-ssh').click(function(){ $('#repository-url-ssh').click(function(){
// Update URL box // Update URL box
$('#repository-url').val('@forkedRepository.sshUrl(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), loginAccount.get.userName)'); $('#repository-url').val('@context.urls(forkedRepository).sshUrl(loginAccount.get.userName)');
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val()); $('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
// Update command guidance // Update command guidance
$('#merge-command').text($('#merge-command').text().replace( $('#merge-command').text($('#merge-command').text().replace(
'@forkedRepository.httpUrl', '@context.urls(forkedRepository).httpUrl',
'@forkedRepository.sshUrl(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), loginAccount.get.userName)' '@context.urls(forkedRepository).sshUrl(loginAccount.get.userName)'
)); ));
$('#merge-command-copy-1').attr('data-clipboard-text', $('#merge-command').text()); $('#merge-command-copy-1').attr('data-clipboard-text', $('#merge-command').text());
}); });

View File

@@ -10,10 +10,10 @@
} else { } else {
<h3><strong>Quick setup</strong> — if you've done this kind of thing before</h3> <h3><strong>Quick setup</strong> — if you've done this kind of thing before</h3>
<div class="empty-repo-options"> <div class="empty-repo-options">
via <a href="@repository.httpUrl" class="git-protocol-selector">HTTP</a> via <a href="@context.urls(repository).httpUrl" class="git-protocol-selector">HTTP</a>
@if(settings.ssh && loginAccount.isDefined){ @if(settings.ssh && loginAccount.isDefined){
or or
<a href="@repository.sshUrl(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), loginAccount.get.userName)" class="git-protocol-selector">SSH</a> <a href="@context.urls(repository).sshUrl(loginAccount.get.userName)" class="git-protocol-selector">SSH</a>
} }
</div> </div>
<h3 style="margin-top: 30px;">Create a new repository on the command line</h3> <h3 style="margin-top: 30px;">Create a new repository on the command line</h3>
@@ -22,12 +22,12 @@
git init git init
git add README.md git add README.md
git commit -m "first commit" git commit -m "first commit"
git remote add origin <span class="live-clone-url">@repository.httpUrl</span> git remote add origin <span class="live-clone-url">@context.urls(repository).httpUrl</span>
git push -u origin master git push -u origin master
} }
<h3 style="margin-top: 30px;">Push an existing repository from the command line</h3> <h3 style="margin-top: 30px;">Push an existing repository from the command line</h3>
@pre { @pre {
git remote add origin <span class="live-clone-url">@repository.httpUrl</span> git remote add origin <span class="live-clone-url">@context.urls(repository).httpUrl</span>
git push -u origin master git push -u origin master
} }
<script> <script>

View File

@@ -67,8 +67,8 @@
<div class="small"> <div class="small">
<strong>Clone this wiki locally</strong> <strong>Clone this wiki locally</strong>
</div> </div>
@helper.html.copy("repository-url-copy", httpUrl(repository)){ @helper.html.copy("repository-url-copy", context.wikiUrls(repository).httpUrl){
<input type="text" value="@httpUrl(repository)" id="repository-url" class="form-control input-sm" readonly> <input type="text" value="@context.wikiUrls(repository).httpUrl" id="repository-url" class="form-control input-sm" readonly>
} }
@if(settings.ssh && loginAccount.isDefined){ @if(settings.ssh && loginAccount.isDefined){
<div class="small"> <div class="small">
@@ -133,11 +133,11 @@ $(function(){
@if(settings.ssh && loginAccount.isDefined){ @if(settings.ssh && loginAccount.isDefined){
$('#repository-url-http').click(function(){ $('#repository-url-http').click(function(){
$('#repository-url').val('@httpUrl(repository)'); $('#repository-url').val('@context.wikiUrls(repository).httpUrl');
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val()); $('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
}); });
$('#repository-url-ssh').click(function(){ $('#repository-url-ssh').click(function(){
$('#repository-url').val('@sshUrl(repository, settings, loginAccount.get.userName)'); $('#repository-url').val('@context.wikiUrls(repository).sshUrl(loginAccount.get.userName)');
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val()); $('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
}); });
} }

View File

@@ -104,6 +104,7 @@ class AvatarImageProviderSpec extends FunSpec with MockitoSugar {
notification = false, notification = false,
activityLogLimit = None, activityLogLimit = None,
ssh = false, ssh = false,
sshHost = None,
sshPort = None, sshPort = None,
useSMTP = false, useSMTP = false,
smtp = None, smtp = None,