Merge branch 'master' into feature/get-single-commit-api

# Conflicts:
#	src/main/scala/gitbucket/core/controller/ApiController.scala
This commit is contained in:
Naoki Takezoe
2017-09-05 09:28:36 +09:00
26 changed files with 244 additions and 149 deletions

View File

@@ -25,11 +25,9 @@ class ScalatraBootstrap extends LifeCycle with SystemSettingsService {
context.getFilterRegistration("gitAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
context.addFilter("apiAuthenticationFilter", new ApiAuthenticationFilter)
context.getFilterRegistration("apiAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/api/v3/*")
context.addFilter("ghCompatRepositoryAccessFilter", new GHCompatRepositoryAccessFilter)
context.getFilterRegistration("ghCompatRepositoryAccessFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
// Register controllers
context.mount(new AnonymousAccessController, "/*")
context.mount(new PreProcessController, "/*")
context.addFilter("pluginControllerFilter", new PluginControllerFilter)
context.getFilterRegistration("pluginControllerFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")

View File

@@ -40,5 +40,6 @@ object GitBucketCoreModule extends Module("gitbucket-core",
new SqlMigration("update/gitbucket-core_4.14.sql")
),
new Version("4.14.1"),
new Version("4.15.0")
new Version("4.15.0"),
new Version("4.16.0")
)

View File

@@ -1,14 +0,0 @@
package gitbucket.core.controller
class AnonymousAccessController extends AnonymousAccessControllerBase
trait AnonymousAccessControllerBase extends ControllerBase {
get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) {
if(!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
!context.currentPath.startsWith("/register")) {
Unauthorized()
} else {
pass()
}
}
}

View File

@@ -5,14 +5,13 @@ import gitbucket.core.model._
import gitbucket.core.service.IssuesService.IssueSearchCondition
import gitbucket.core.service.PullRequestService._
import gitbucket.core.service._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.Directory._
import gitbucket.core.util.JGitUtil._
import gitbucket.core.util._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.JGitUtil._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util._
import gitbucket.core.view.helpers.{isRenderable, renderMarkup}
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.revwalk.RevWalk
import org.scalatra.{Created, NoContent, UnprocessableEntity}
import scala.collection.JavaConverters._
@@ -127,10 +126,10 @@ trait ApiControllerBase extends ControllerBase {
/**
* https://developer.github.com/v3/repos/branches/#get-branch
*/
get ("/api/v3/repos/:owner/:repo/branches/:branch")(referrersOnly { repository =>
get ("/api/v3/repos/:owner/:repo/branches/*")(referrersOnly { repository =>
//import gitbucket.core.api._
(for{
branch <- params.get("branch") if repository.branchList.contains(branch)
branch <- params.get("splat") if repository.branchList.contains(branch)
br <- getBranches(repository.owner, repository.name, repository.repository.defaultBranch, repository.repository.originUserName.isEmpty).find(_.name == branch)
} yield {
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
@@ -289,10 +288,10 @@ trait ApiControllerBase extends ControllerBase {
/**
* https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection
*/
patch("/api/v3/repos/:owner/:repo/branches/:branch")(ownerOnly { repository =>
patch("/api/v3/repos/:owner/:repo/branches/*")(ownerOnly { repository =>
import gitbucket.core.api._
(for{
branch <- params.get("branch") if repository.branchList.contains(branch)
branch <- params.get("splat") if repository.branchList.contains(branch)
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
br <- getBranches(repository.owner, repository.name, repository.repository.defaultBranch, repository.repository.originUserName.isEmpty).find(_.name == branch)
} yield {

View File

@@ -26,6 +26,7 @@ import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.revwalk.RevCommit
import org.eclipse.jgit.treewalk._
import org.apache.commons.io.IOUtils
import org.slf4j.LoggerFactory
/**
* Provides generic features for controller implementations.
@@ -34,6 +35,8 @@ abstract class ControllerBase extends ScalatraFilter
with ClientSideValidationFormSupport with JacksonJsonSupport with I18nSupport with FlashMapSupport with Validations
with SystemSettingsService {
private val logger = LoggerFactory.getLogger(getClass)
implicit val jsonFormats = gitbucket.core.api.JsonFormat.jsonFormats
before("/api/v3/*") {
@@ -147,6 +150,20 @@ abstract class ControllerBase extends ScalatraFilter
}
}
error{
case e => {
logger.error(s"Catch unhandled error in request: ${request}", e)
if(request.hasAttribute(Keys.Request.Ajax)){
org.scalatra.InternalServerError()
} else if(request.hasAttribute(Keys.Request.APIv3)){
contentType = formats("json")
org.scalatra.InternalServerError(ApiError("Internal Server Error"))
} else {
org.scalatra.InternalServerError(gitbucket.core.html.error("Internal Server Error", Some(e)))
}
}
}
override def url(path: String, params: Iterable[(String, Any)] = Iterable.empty,
includeContextPath: Boolean = true, includeServletPath: Boolean = true,
absolutize: Boolean = true, withSessionId: Boolean = true)

View File

@@ -0,0 +1,40 @@
package gitbucket.core.controller
import org.scalatra.MovedPermanently
class PreProcessController extends PreProcessControllerBase
trait PreProcessControllerBase extends ControllerBase {
/**
* Provides GitHub compatible URLs for Git client.
*
* <ul>
* <li>git clone http://localhost:8080/owner/repo</li>
* <li>git clone http://localhost:8080/owner/repo.git</li>
* </ul>
*
* @see https://git-scm.com/book/en/v2/Git-Internals-Transfer-Protocols
*/
get("/*/*/info/refs") {
val query = Option(request.getQueryString).map("?" + _).getOrElse("")
halt(MovedPermanently(baseUrl + "/git" + request.getRequestURI + query))
}
/**
* Filter requests from anonymous users.
*
* If anonymous access is allowed, pass all requests.
* But if it's not allowed, demands authentication except some paths.
*/
get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) {
if(!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
!context.currentPath.startsWith("/register")) {
Unauthorized()
} else {
pass()
}
}
}

View File

@@ -251,7 +251,7 @@ trait PullRequestsControllerBase extends ControllerBase {
using(Git.open(getRepositoryDir(owner, name))) { git =>
// mark issue as merged and close.
val loginAccount = context.loginAccount.get
createComment(owner, name, loginAccount.userName, issueId, form.message, "merge")
val commentId = createComment(owner, name, loginAccount.userName, issueId, form.message, "merge")
createComment(owner, name, loginAccount.userName, issueId, "Close", "close")
updateClosed(owner, name, issueId, true)
@@ -282,7 +282,10 @@ trait PullRequestsControllerBase extends ControllerBase {
callPullRequestWebHook("closed", repository, issueId, context.baseUrl, context.loginAccount.get)
// call hooks
PluginRegistry().getPullRequestHooks.foreach(_.merged(issue, repository))
PluginRegistry().getPullRequestHooks.foreach{ h =>
h.addedComment(commentId, form.message, issue, repository)
h.merged(issue, repository)
}
redirect(s"/${owner}/${name}/pull/${issueId}")
}
@@ -321,8 +324,8 @@ trait PullRequestsControllerBase extends ControllerBase {
get("/:owner/:repository/compare/*...*")(referrersOnly { forkedRepository =>
val Seq(origin, forked) = multiParams("splat")
val (originOwner, originId) = parseCompareIdentifie(origin, forkedRepository.owner)
val (forkedOwner, forkedId) = parseCompareIdentifie(forked, forkedRepository.owner)
val (originOwner, originId) = parseCompareIdentifier(origin, forkedRepository.owner)
val (forkedOwner, forkedId) = parseCompareIdentifier(forked, forkedRepository.owner)
(for(
originRepositoryName <- if(originOwner == forkedOwner) {
@@ -408,8 +411,8 @@ trait PullRequestsControllerBase extends ControllerBase {
ajaxGet("/:owner/:repository/compare/*...*/mergecheck")(readableUsersOnly { forkedRepository =>
val Seq(origin, forked) = multiParams("splat")
val (originOwner, tmpOriginBranch) = parseCompareIdentifie(origin, forkedRepository.owner)
val (forkedOwner, tmpForkedBranch) = parseCompareIdentifie(forked, forkedRepository.owner)
val (originOwner, tmpOriginBranch) = parseCompareIdentifier(origin, forkedRepository.owner)
val (forkedOwner, tmpForkedBranch) = parseCompareIdentifier(forked, forkedRepository.owner)
(for(
originRepositoryName <- if(originOwner == forkedOwner){
@@ -502,7 +505,7 @@ trait PullRequestsControllerBase extends ControllerBase {
* - "owner:branch" to ("owner", "branch")
* - "branch" to ("defaultOwner", "branch")
*/
private def parseCompareIdentifie(value: String, defaultOwner: String): (String, String) =
private def parseCompareIdentifier(value: String, defaultOwner: String): (String, String) =
if(value.contains(':')){
val array = value.split(":")
(array(0), array(1))

View File

@@ -13,7 +13,7 @@ import gitbucket.core.util.StringUtil._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.Directory._
import gitbucket.core.model.{Account, WebHook}
import gitbucket.core.model.{Account, CommitState, CommitStatus, WebHook}
import gitbucket.core.service.WebHookService._
import gitbucket.core.view
import gitbucket.core.view.helpers
@@ -174,13 +174,24 @@ trait RepositoryViewerControllerBase extends ControllerBase {
val (branchName, path) = repository.splitPath(multiParams("splat").head)
val page = params.get("page").flatMap(_.toIntOpt).getOrElse(1)
def getStatuses(sha: String): List[CommitStatus] = {
getCommitStatues(repository.owner, repository.name, sha)
}
def getSummary(statuses: List[CommitStatus]): (CommitState, String) = {
val stateMap = statuses.groupBy(_.state)
val state = CommitState.combine(stateMap.keySet)
val summary = stateMap.map{ case (keyState, states) => states.size+" "+keyState.name }.mkString(", ")
state -> summary
}
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
JGitUtil.getCommitLog(git, branchName, page, 30, path) match {
case Right((logs, hasNext)) =>
html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository,
logs.splitWith{ (commit1, commit2) =>
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
}, page, hasNext, hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
}, page, hasNext, hasDeveloperRole(repository.owner, repository.name, context.loginAccount), getStatuses, getSummary)
case Left(_) => NotFound()
}
}
@@ -467,9 +478,13 @@ trait RepositoryViewerControllerBase extends ControllerBase {
val comment = getCommitComment(repository.owner, repository.name, commentId.toString).get
form.issueId match {
case Some(issueId) =>
recordCommentPullRequestActivity(repository.owner, repository.name, context.loginAccount.get.userName, issueId, form.content)
callPullRequestReviewCommentWebHook("create", comment, repository, issueId, context.baseUrl, context.loginAccount.get)
case None => recordCommentCommitActivity(repository.owner, repository.name, context.loginAccount.get.userName, id, form.content)
getPullRequest(repository.owner, repository.name, issueId).foreach { case (issue, pullRequest) =>
recordCommentPullRequestActivity(repository.owner, repository.name, context.loginAccount.get.userName, issueId, form.content)
PluginRegistry().getPullRequestHooks.foreach(_.addedComment(commentId, form.content, issue, repository))
callPullRequestReviewCommentWebHook("create", comment, repository, issue, pullRequest, context.baseUrl, context.loginAccount.get)
}
case None =>
recordCommentCommitActivity(repository.owner, repository.name, context.loginAccount.get.userName, id, form.content)
}
helper.html.commitcomment(comment, hasDeveloperRole(repository.owner, repository.name, context.loginAccount), repository)
})

View File

@@ -63,7 +63,8 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
"tls" -> trim(label("Enable TLS", optional(boolean()))),
"ssl" -> trim(label("Enable SSL", optional(boolean()))),
"keystore" -> trim(label("Keystore", optional(text())))
)(Ldap.apply))
)(Ldap.apply)),
"skinName" -> trim(label("AdminLTE skin name", text(required)))
)(SystemSettings.apply).verifying { settings =>
Vector(
if(settings.ssh && settings.baseUrl.isEmpty){
@@ -174,8 +175,9 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
post("/admin/system/sendmail", sendMailForm)(adminOnly { form =>
try {
new Mailer(form.smtp).send(form.testAddress,
"Test message from GitBucket", "This is a test message from GitBucket.",
context.loginAccount.get)
"Test message from GitBucket", context.loginAccount.get,
"This is a test message from GitBucket.", None
)
"Test mail has been sent to: " + form.testAddress

View File

@@ -264,6 +264,7 @@ object PluginRegistry {
(extraJars ++ pluginJars).foreach { pluginJar =>
val installedJar = new File(installedDir, pluginJar.getName)
FileUtils.copyFile(pluginJar, installedJar)
logger.info(s"Initialize ${pluginJar.getName}")
@@ -400,12 +401,15 @@ class PluginWatchThread(context: ServletContext, dir: String) extends Thread wit
events.foreach { event =>
logger.info(event.kind + ": " + event.context)
}
gitbucket.core.servlet.Database() withTransaction { session =>
logger.info("Reloading plugins...")
PluginRegistry.reload(context, loadSystemSettings(), session.conn)
logger.info("Reloading finished.")
}
new Thread {
override def run(): Unit = {
gitbucket.core.servlet.Database() withTransaction { session =>
logger.info("Reloading plugins...")
PluginRegistry.reload(context, loadSystemSettings(), session.conn)
logger.info("Reloading finished.")
}
}
}.start()
}
detectedWatchKey.reset()
}

View File

@@ -455,9 +455,8 @@ trait IssuesService {
def createIssueComment(owner: String, repository: String, commit: CommitInfo)(implicit s: Session): Unit = {
extractIssueId(commit.fullMessage).foreach { issueId =>
if(getIssue(owner, repository, issueId).isDefined){
getAccountByMailAddress(commit.committerEmailAddress).foreach { account =>
createComment(owner, repository, account.userName, issueId.toInt, commit.fullMessage + " " + commit.id, "commit")
}
val userName = getAccountByMailAddress(commit.committerEmailAddress).map(_.userName).getOrElse(commit.committerName)
createComment(owner, repository, userName, issueId.toInt, commit.fullMessage + " " + commit.id, "commit")
}
}
}

View File

@@ -54,6 +54,7 @@ trait SystemSettingsService {
ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x))
}
}
props.setProperty(SkinName, settings.skinName.toString)
using(new java.io.FileOutputStream(GitBucketConf)){ out =>
props.store(out, null)
}
@@ -111,7 +112,8 @@ trait SystemSettingsService {
getOptionValue(props, LdapKeystore, None)))
} else {
None
}
},
getValue(props, SkinName, "skin-blue")
)
}
}
@@ -136,7 +138,8 @@ object SystemSettingsService {
useSMTP: Boolean,
smtp: Option[Smtp],
ldapAuthentication: Boolean,
ldap: Option[Ldap]){
ldap: Option[Ldap],
skinName: String){
def baseUrl(request: HttpServletRequest): String = baseUrl.fold(request.baseUrl)(_.stripSuffix("/"))
def sshAddress:Option[SshAddress] =
@@ -219,6 +222,7 @@ object SystemSettingsService {
private val LdapTls = "ldap.tls"
private val LdapSsl = "ldap.ssl"
private val LdapKeystore = "ldap.keystore"
private val SkinName = "skinName"
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = {
getSystemProperty(key).getOrElse(getEnvironmentVariable(key).getOrElse {

View File

@@ -296,13 +296,13 @@ trait WebHookPullRequestService extends WebHookService {
trait WebHookPullRequestReviewCommentService extends WebHookService {
self: AccountService with RepositoryService with PullRequestService with IssuesService with CommitsService =>
def callPullRequestReviewCommentWebHook(action: String, comment: CommitComment, repository: RepositoryService.RepositoryInfo, issueId: Int, baseUrl: String, sender: Account)
def callPullRequestReviewCommentWebHook(action: String, comment: CommitComment, repository: RepositoryService.RepositoryInfo,
issue: Issue, pullRequest: PullRequest, baseUrl: String, sender: Account)
(implicit s: Session, c: JsonFormat.Context): Unit = {
import WebHookService._
callWebHookOf(repository.owner, repository.name, WebHook.PullRequestReviewComment){
val users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set(sender))
for{
(issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId)
users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set(sender))
baseOwner <- users.get(repository.owner)
headOwner <- users.get(pullRequest.requestUserName)
issueUser <- users.get(issue.openedUserName)

View File

@@ -1,37 +0,0 @@
package gitbucket.core.servlet
import javax.servlet._
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
import gitbucket.core.service.SystemSettingsService
/**
* A controller to provide GitHub compatible URL for Git clients.
*/
class GHCompatRepositoryAccessFilter extends Filter with SystemSettingsService {
/**
* Pattern of GitHub compatible repository URL.
* <code>/:user/:repo.git/</code>
*/
private val githubRepositoryPattern = """^/[^/]+/[^/]+\.git/.*""".r
override def init(filterConfig: FilterConfig) = {}
override def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain) = {
implicit val request = req.asInstanceOf[HttpServletRequest]
val agent = request.getHeader("USER-AGENT")
val response = res.asInstanceOf[HttpServletResponse]
val requestPath = request.getRequestURI.substring(request.getContextPath.length)
requestPath match {
case githubRepositoryPattern() if agent != null && agent.toLowerCase.indexOf("git") >= 0 =>
response.sendRedirect(baseUrl + "/git" + requestPath)
case _ =>
chain.doFilter(req, res)
}
}
override def destroy() = {}
}

View File

@@ -24,7 +24,7 @@ class PluginControllerFilter extends Filter {
val controller = PluginRegistry().getControllers().filter { case (_, path) =>
val requestUri = request.asInstanceOf[HttpServletRequest].getRequestURI
val start = path.replaceFirst("/\\*$", "/")
path.endsWith("/*") && (requestUri + "/").startsWith(start)
(requestUri + "/").startsWith(start)
}
val filterChainWrapper = controller.foldLeft(chain){ case (chain, (controller, _)) =>

View File

@@ -19,7 +19,7 @@ import org.apache.sshd.server.scp.UnknownCommand
import org.eclipse.jgit.errors.RepositoryNotFoundException
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
}

View File

@@ -19,8 +19,12 @@ import SystemSettingsService.Smtp
* Please see the plugin for details.
*/
trait Notifier {
def toNotify(subject: String, textMsg: String)
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit = {
toNotify(subject, textMsg, None)(recipients)
}
def toNotify(subject: String, msg: String)
def toNotify(subject: String, textMsg: String, htmlMsg: Option[String])
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit
}
@@ -35,7 +39,7 @@ object Notifier {
class Mailer(private val smtp: Smtp) extends Notifier {
private val logger = LoggerFactory.getLogger(classOf[Mailer])
def toNotify(subject: String, msg: String)
def toNotify(subject: String, textMsg: String, htmlMsg: Option[String] = None)
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit = {
context.loginAccount.foreach { loginAccount =>
val database = Database()
@@ -43,7 +47,7 @@ class Mailer(private val smtp: Smtp) extends Notifier {
val f = Future {
database withSession { session =>
recipients(loginAccount)(session) foreach { to =>
send(to, subject, msg, loginAccount)
send(to, subject, loginAccount, textMsg, htmlMsg)
}
}
"Notifications Successful."
@@ -55,7 +59,7 @@ class Mailer(private val smtp: Smtp) extends Notifier {
}
}
def send(to: String, subject: String, msg: String, loginAccount: Account): Unit = {
def send(to: String, subject: String, loginAccount: Account, textMsg: String, htmlMsg: Option[String] = None): Unit = {
val email = new HtmlEmail
email.setHostName(smtp.host)
email.setSmtpPort(smtp.port.get)
@@ -80,13 +84,16 @@ class Mailer(private val smtp: Smtp) extends Notifier {
}
email.setCharset("UTF-8")
email.setSubject(subject)
email.setHtmlMsg(msg)
email.setTextMsg(textMsg)
htmlMsg.foreach { msg =>
email.setHtmlMsg(msg)
}
email.addTo(to).send
}
}
class MockMailer extends Notifier {
def toNotify(subject: String, msg: String)
def toNotify(subject: String, textMsg: String, htmlMsg: Option[String] = None)
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit = ()
}

View File

@@ -1,5 +1,6 @@
@(info: Option[Any])(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.util.DatabaseConfig
@import gitbucket.core.view.helpers
@gitbucket.core.html.main("System settings"){
@gitbucket.core.admin.html.menu("system"){
@gitbucket.core.helper.html.information(info)
@@ -344,6 +345,36 @@
</div>
</div>
*@
<!--====================================================================-->
<!-- AdminLTE SkinName -->
<!--====================================================================-->
<hr>
<label class="strong">
AdminLTE skin name
</label>
<div class="form-group">
<label class="control-label col-md-3" for="skinName">Skin name</label>
<div class="col-md-9">
<select id="skinName" name="skinName">
@Seq(
"skin-black",
"skin-black-light",
"skin-blue",
"skin-blue-light",
"skin-green",
"skin-green-light",
"skin-purple",
"skin-purple-light",
"skin-red",
"skin-red-light",
"skin-yellow",
"skin-yellow-light",
).map{ skin =>
<option @if(skin == context.settings.skinName){selected}>@skin</option>
}
</select>
</div>
</div>
</div>
</div>
<div class="align-right" style="margin-top: 20px;">

View File

@@ -1,8 +1,22 @@
@(title: String)(implicit context: gitbucket.core.controller.Context)
@(title: String, e: Option[Throwable]=None)(implicit context: gitbucket.core.controller.Context)
@gitbucket.core.html.main("Error"){
<div class="content-wrapper main-center">
<div class="content body">
<h1>@title</h1>
@if(context.loginAccount.map{_.isAdmin}.getOrElse(false)){
@e.map { ex =>
<h2>@ex.getMessage</h2>
<table class="table table-condensed table-striped table-hover">
<tbody>
@ex.getStackTrace.map{ st =>
<tr><td>@st</td></tr>
}
</tbody>
</table>
}
} else {
<div>Please contact your administrator.</div>
}
</div>
</div>
}
}

View File

@@ -16,7 +16,7 @@
<link href="@helpers.assets("/vendors/google-code-prettify/prettify.css")" type="text/css" rel="stylesheet"/>
<link href="@helpers.assets("/vendors/facebox/facebox.css")" rel="stylesheet"/>
<link href="@helpers.assets("/vendors/AdminLTE-2.3.11/css/AdminLTE.min.css")" rel="stylesheet">
<link href="@helpers.assets("/vendors/AdminLTE-2.3.11/css/skins/skin-blue.min.css")" rel="stylesheet">
<link href="@helpers.assets(s"/vendors/AdminLTE-2.3.11/css/skins/${context.settings.skinName}.min.css")" rel="stylesheet">
<link href="@helpers.assets("/vendors/font-awesome-4.6.3/css/font-awesome.min.css")" rel="stylesheet">
<link href="@helpers.assets("/vendors/jquery-ui/jquery-ui.min.css")" rel="stylesheet">
<link href="@helpers.assets("/vendors/jquery-ui/jquery-ui.structure.min.css")" rel="stylesheet">
@@ -42,7 +42,7 @@
}
<script src="@helpers.assets("/vendors/AdminLTE-2.3.11/js/app.js")" type="text/javascript"></script>
</head>
<body class="skin-blue page-load @if(body.toString.contains("menu-item-hover")){sidebar-mini} @if(context.sidebarCollapse){sidebar-collapse}">
<body class="@context.settings.skinName page-load @if(body.toString.contains("menu-item-hover")){sidebar-mini} @if(context.sidebarCollapse){sidebar-collapse}">
<div class="wrapper">
<header class="main-header">
<a href="@context.path/" class="logo">

View File

@@ -4,7 +4,9 @@
commits: Seq[Seq[gitbucket.core.util.JGitUtil.CommitInfo]],
page: Int,
hasNext: Boolean,
hasWritePermission: Boolean)(implicit context: gitbucket.core.controller.Context)
hasWritePermission: Boolean,
getStatuses: String => List[gitbucket.core.model.CommitStatus],
getSummary: List[gitbucket.core.model.CommitStatus] => (gitbucket.core.model.CommitState, String))(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@gitbucket.core.html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
@gitbucket.core.html.menu("files", repository){
@@ -63,6 +65,30 @@
}
@helpers.user(commit.committerName, commit.committerEmailAddress, "username")
<span class="muted">committed @gitbucket.core.helper.html.datetimeago(commit.commitTime)</span>
@defining({
val statuses = getStatuses(commit.id)
val (summary, summaryText) = getSummary(statuses)
(statuses, summary, summaryText)
}){ case (statuses, summaryState, summaryText) =>
@if(statuses.nonEmpty){
@helpers.commitStateIcon(summaryState)
<strong class="text-@{summaryState.name}">@helpers.commitStateText(summaryState, commit.id)</strong>
<span class="text-@{summaryState.name}">- @summaryText checks</span>
<a href="#" class="toggle-check">Show all checks</a>
<div style="display: none;">
@statuses.map{ status =>
<div class="build-status-item">
<span class="build-status-icon text-@{status.state.name}">@helpers.commitStateIcon(status.state)</span>
<strong>@status.context</strong>
@status.description.map { desc => <span class="muted">- @desc</span> }
<span>
@status.targetUrl.map { url => - <a href="@url">Details</a> }
</span>
</div>
}
</div>
}
}
</div>
</div>
</div>
@@ -86,5 +112,19 @@
}
</ul>
</nav>
<script>
$(function () {
$('.toggle-check').click(function(){
var div = $(this).next('div');
if(div.is(':visible')){
$(this).text('Show all checks');
} else {
$(this).text('Hide all checks');
}
div.toggle();
return false;
});
})
</script>
}
}

View File

@@ -118,7 +118,9 @@ class AvatarImageProviderSpec extends FunSpec with MockitoSugar {
useSMTP = false,
smtp = None,
ldapAuthentication = false,
ldap = None)
ldap = None,
skinName = "skin-blue"
)
/**
* Adapter to test AvatarImageProviderImpl.