Merge branch 'master' into feature/plugin-hotdeploy

This commit is contained in:
Naoki Takezoe
2017-03-23 20:45:52 +09:00
35 changed files with 321 additions and 145 deletions

View File

@@ -16,3 +16,23 @@ cache:
- $HOME/.coursier
- $HOME/.embedmysql
- $HOME/.embedpostgresql
matrix:
include:
- dist: trusty
group: edge
sudo: required
jdk: oraclejdk9
script:
# https://github.com/sbt/sbt/pull/2951
- git clone https://github.com/retronym/java9-rt-export
- cd java9-rt-export/
- git checkout 1019a2873d057dd7214f4135e84283695728395d
- jdk_switcher use oraclejdk8
- sbt package
- jdk_switcher use oraclejdk9
- mkdir -p $HOME/.sbt/0.13/java9-rt-ext; java -jar target/java9-rt-export-*.jar $HOME/.sbt/0.13/java9-rt-ext/rt.jar
- jar tf $HOME/.sbt/0.13/java9-rt-ext/rt.jar | grep java/lang/Object
- cd ..
- echo "sbt.version=0.13.14-RC1" > project/build.properties
- wget https://raw.githubusercontent.com/paulp/sbt-extras/9ade5fa54914ca8aded44105bf4b9a60966f3ccd/sbt && chmod +x ./sbt
- ./sbt -Dscala.ext.dirs=$HOME/.sbt/0.13/java9-rt-ext test

View File

@@ -1,4 +1,3 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
@@ -179,7 +178,7 @@
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
@@ -187,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2013-2016 GitBucket Team
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -21,8 +21,8 @@ resolvers ++= Seq(
"amateras-snapshot" at "http://amateras.sourceforge.jp/mvn-snapshot/"
)
libraryDependencies ++= Seq(
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.6.0.201612231935-r",
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.6.0.201612231935-r",
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.6.1.201703071140-r",
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.6.1.201703071140-r",
"org.scalatra" %% "scalatra" % ScalatraVersion,
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
"org.json4s" %% "json4s-jackson" % "3.5.0",
@@ -54,12 +54,13 @@ libraryDependencies ++= Seq(
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
"junit" % "junit" % "4.12" % "test",
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test",
"org.mockito" % "mockito-core" % "2.7.16" % "test",
"com.wix" % "wix-embedded-mysql" % "2.1.4" % "test",
"ru.yandex.qatools.embed" % "postgresql-embedded" % "1.14" % "test"
)
// Compiler settings
scalacOptions := Seq("-deprecation", "-language:postfixOps", "-opt:_")
scalacOptions := Seq("-deprecation", "-language:postfixOps")
javacOptions in compile ++= Seq("-target", "8", "-source", "8")
javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml"

View File

@@ -1,8 +1,9 @@
package gitbucket.core.api
import java.util.Base64
import gitbucket.core.util.JGitUtil.FileInfo
import gitbucket.core.util.RepositoryName
import org.apache.commons.codec.binary.Base64
case class ApiContents(
`type`: String,
@@ -20,7 +21,7 @@ object ApiContents{
ApiContents("dir", fileInfo.name, fileInfo.path, fileInfo.commitId, None, None)(repositoryName)
} else {
content.map(arr =>
ApiContents("file", fileInfo.name, fileInfo.path, fileInfo.commitId, Some(Base64.encodeBase64String(arr)), Some("base64"))(repositoryName)
ApiContents("file", fileInfo.name, fileInfo.path, fileInfo.commitId, Some(Base64.getEncoder.encodeToString(arr)), Some("base64"))(repositoryName)
).getOrElse(ApiContents("file", fileInfo.name, fileInfo.path, fileInfo.commitId, None, None)(repositoryName))
}
}

View File

@@ -15,6 +15,7 @@ import io.github.gitbucket.scalatra.forms._
import org.apache.commons.io.FileUtils
import org.scalatra.i18n.Messages
import org.scalatra.BadRequest
import java.util.Date
class AccountController extends AccountControllerBase
@@ -149,11 +150,20 @@ trait AccountControllerBase extends AccountManagementControllerBase {
get("/:userName/_avatar"){
val userName = params("userName")
getAccountByUserName(userName).flatMap(_.image).map { image =>
RawData(FileUtil.getMimeType(image), new java.io.File(getUserUploadDir(userName), image))
} getOrElse {
contentType = "image/png"
Thread.currentThread.getContextClassLoader.getResourceAsStream("noimage.png")
getAccountByUserName(userName).map{ account =>
response.setDateHeader("Last-Modified", account.updatedDate.getTime)
account.image.map{ image =>
RawData(FileUtil.getMimeType(image), new java.io.File(getUserUploadDir(userName), image))
}.getOrElse{
contentType = "image/png"
(if (account.isGroupAccount) {
TextAvatarUtil.textGroupAvatar(account.fullName)
} else {
TextAvatarUtil.textAvatar(account.fullName)
}).getOrElse(Thread.currentThread.getContextClassLoader.getResourceAsStream("noimage.png"))
}
}.getOrElse{
NotFound()
}
}

View File

@@ -1,13 +1,11 @@
package gitbucket.core.model
trait DeployKeyComponent { self: Profile =>
trait DeployKeyComponent extends TemplateComponent { self: Profile =>
import profile.api._
lazy val DeployKeys = TableQuery[DeployKeys]
class DeployKeys(tag: Tag) extends Table[DeployKey](tag, "DEPLOY_KEY") {
val userName = column[String]("USER_NAME")
val repositoryName = column[String]("REPOSITORY_NAME")
class DeployKeys(tag: Tag) extends Table[DeployKey](tag, "DEPLOY_KEY") with BasicTemplate {
val deployKeyId = column[Int]("DEPLOY_KEY_ID", O AutoInc)
val title = column[String]("TITLE")
val publicKey = column[String]("PUBLIC_KEY")

View File

@@ -3,6 +3,7 @@ package gitbucket.core.plugin
import java.io.{File, FilenameFilter, InputStream}
import java.net.URLClassLoader
import java.nio.file.{Paths, StandardWatchEventKinds}
import java.util.Base64
import javax.servlet.ServletContext
import gitbucket.core.controller.{Context, ControllerBase}
@@ -17,7 +18,6 @@ import gitbucket.core.util.Directory._
import io.github.gitbucket.solidbase.Solidbase
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
import io.github.gitbucket.solidbase.model.Module
import org.apache.commons.codec.binary.{Base64, StringUtils}
import org.apache.commons.io.FileUtils
import org.slf4j.LoggerFactory
@@ -57,7 +57,7 @@ class PluginRegistry {
def getPlugins(): List[PluginInfo] = plugins.toList
def addImage(id: String, bytes: Array[Byte]): Unit = {
val encoded = StringUtils.newStringUtf8(Base64.encodeBase64(bytes, false))
val encoded = Base64.getEncoder.encodeToString(bytes)
images += ((id, encoded))
}

View File

@@ -73,6 +73,7 @@ trait RepositoryService { self: AccountService =>
val collaborators = Collaborators .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val protectedBranches = ProtectedBranches .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val protectedBranchContexts = ProtectedBranchContexts.filter(_.byRepository(oldUserName, oldRepositoryName)).list
val deployKeys = DeployKeys .filter(_.byRepository(oldUserName, oldRepositoryName)).list
Repositories.filter { t =>
(t.originUserName === oldUserName.bind) && (t.originRepositoryName === oldRepositoryName.bind)
@@ -112,6 +113,7 @@ trait RepositoryService { self: AccountService =>
CommitStatuses .insertAll(commitStatuses.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
ProtectedBranches .insertAll(protectedBranches.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
ProtectedBranchContexts.insertAll(protectedBranchContexts.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
DeployKeys .insertAll(deployKeys .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
// Update source repository of pull requests
PullRequests.filter { t =>
@@ -126,11 +128,6 @@ trait RepositoryService { self: AccountService =>
userName = newUserName,
repositoryName = newRepositoryName
)) :_*)
IssueLabels.insertAll(issueLabels.map(x => x.copy(
labelId = newLabelMap(oldLabelMap(x.labelId)),
userName = newUserName,
repositoryName = newRepositoryName
)) :_*)
// TODO Drop transfered owner from collaborators?
Collaborators.insertAll(collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
@@ -168,6 +165,7 @@ trait RepositoryService { self: AccountService =>
Milestones .filter(_.byRepository(userName, repositoryName)).delete
WebHooks .filter(_.byRepository(userName, repositoryName)).delete
WebHookEvents .filter(_.byRepository(userName, repositoryName)).delete
DeployKeys .filter(_.byRepository(userName, repositoryName)).delete
Repositories .filter(_.byRepository(userName, repositoryName)).delete
// Update ORIGIN_USER_NAME and ORIGIN_REPOSITORY_NAME

View File

@@ -18,9 +18,9 @@ class GitAuthenticationFilter extends Filter with RepositoryService with Account
private val logger = LoggerFactory.getLogger(classOf[GitAuthenticationFilter])
def init(config: FilterConfig) = {}
def destroy(): Unit = {}
def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = {
val request = req.asInstanceOf[HttpServletRequest]
val response = res.asInstanceOf[HttpServletResponse]
@@ -85,11 +85,16 @@ class GitAuthenticationFilter extends Filter with RepositoryService with Account
auth <- Option(request.getHeader("Authorization"))
Array(username, password) = AuthUtil.decodeAuthHeader(auth).split(":", 2)
account <- authenticate(settings, username, password)
} yield if (isUpdating || repository.repository.isPrivate) {
} yield if (isUpdating) {
if (hasDeveloperRole(repository.owner, repository.name, Some(account))) {
request.setAttribute(Keys.Request.UserName, account.userName)
true
} else false
} else if(repository.repository.isPrivate){
if (hasGuestRole(repository.owner, repository.name, Some(account))) {
request.setAttribute(Keys.Request.UserName, account.userName)
true
} else false
} else true
passed.getOrElse(false)
}
@@ -114,4 +119,4 @@ class GitAuthenticationFilter extends Filter with RepositoryService with Account
action()
}
}
}

View File

@@ -26,6 +26,7 @@ class PluginAssetsServlet extends HttpServlet {
val bytes = IOUtils.toByteArray(in)
resp.setContentLength(bytes.length)
resp.setContentType(FileUtil.getContentType(path, bytes))
resp.setHeader("Cache-Control", "max-age=3600")
resp.getOutputStream.write(bytes)
} finally {
in.close()

View File

@@ -1,8 +1,8 @@
package gitbucket.core.ssh
import java.security.PublicKey
import java.util.Base64
import org.apache.commons.codec.binary.Base64
import org.apache.sshd.common.config.keys.KeyUtils
import org.apache.sshd.common.util.buffer.ByteArrayBuffer
import org.eclipse.jgit.lib.Constants
@@ -22,7 +22,7 @@ object SshUtil {
}
try {
val encodedKey = parts(1)
val decode = Base64.decodeBase64(Constants.encodeASCII(encodedKey))
val decode = Base64.getDecoder.decode(Constants.encodeASCII(encodedKey))
Some(new ByteArrayBuffer(decode).getRawPublicKey)
} catch {
case e: Throwable =>

View File

@@ -1,5 +1,6 @@
package gitbucket.core.util
import java.util.Base64
import javax.servlet.http.HttpServletResponse
/**
@@ -13,9 +14,9 @@ object AuthUtil {
def decodeAuthHeader(header: String): String = {
try {
new String(new sun.misc.BASE64Decoder().decodeBuffer(header.substring(6)))
new String(Base64.getDecoder.decode(header.substring(6)))
} catch {
case _: Throwable => ""
}
}
}
}

View File

@@ -538,6 +538,7 @@ object JGitUtil {
} else {
// initial commit
using(new TreeWalk(git.getRepository)){ treeWalk =>
treeWalk.setRecursive(true)
treeWalk.addTree(revCommit.getTree)
val buffer = new scala.collection.mutable.ListBuffer[DiffInfo]()
while(treeWalk.next){

View File

@@ -1,12 +1,12 @@
package gitbucket.core.util
import java.net.{URLDecoder, URLEncoder}
import java.util.Base64
import org.mozilla.universalchardet.UniversalDetector
import SyntaxSugars._
import org.apache.commons.io.input.BOMInputStream
import org.apache.commons.io.IOUtils
import org.apache.commons.codec.binary.Base64
import scala.util.control.Exception._
@@ -34,14 +34,14 @@ object StringUtil {
val spec = new javax.crypto.spec.SecretKeySpec(BlowfishKey.getBytes(), "Blowfish")
val cipher = javax.crypto.Cipher.getInstance("Blowfish")
cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, spec)
new String(Base64.encodeBase64(cipher.doFinal(value.getBytes("UTF-8"))), "UTF-8")
Base64.getEncoder.encodeToString(cipher.doFinal(value.getBytes("UTF-8")))
}
def decodeBlowfish(value: String): String = {
val spec = new javax.crypto.spec.SecretKeySpec(BlowfishKey.getBytes(), "Blowfish")
val cipher = javax.crypto.Cipher.getInstance("Blowfish")
cipher.init(javax.crypto.Cipher.DECRYPT_MODE, spec)
new String(cipher.doFinal(Base64.decodeBase64(value)), "UTF-8")
new String(cipher.doFinal(Base64.getDecoder.decode(value)), "UTF-8")
}
def urlEncode(value: String): String = URLEncoder.encode(value, "UTF-8").replace("+", "%20")

View File

@@ -0,0 +1,127 @@
package gitbucket.core.util
import java.io.ByteArrayOutputStream
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
import java.awt.{Color, Font, Graphics2D, RenderingHints}
import java.awt.font.{FontRenderContext, TextLayout}
import java.awt.geom.AffineTransform
object TextAvatarUtil {
private val iconSize = 200
private val fontSize = 180
private val roundSize = 60
private val shadowSize = 20
private val bgSaturation = 0.68f
private val bgBlightness = 0.73f
private val shadowBlightness = 0.23f
private val font = new Font(Font.SANS_SERIF, Font.PLAIN, fontSize)
private val transparent = new Color(0, 0, 0, 0)
// https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
private def relativeLuminance(c: Color): Double = {
val rgb = Seq(c.getRed, c.getGreen, c.getBlue).map{_/255.0}.map{x => if (x <= 0.03928) x / 12.92 else math.pow((x + 0.055) / 1.055, 2.4)}
0.2126 * rgb(0) + 0.7152 * rgb(1) + 0.0722 * rgb(2)
}
// https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
private def contrastRatio(c1: Color, c2: Color): Double = {
val l1 = relativeLuminance(c1)
val l2 = relativeLuminance(c2)
if (l1 > l2) (l1 + 0.05) / (l2 + 0.05) else (l2 + 0.05) / (l1 + 0.05)
}
private def goodContrastColor(base: Color, c1: Color, c2: Color): Color = {
if (contrastRatio(base, c1) > contrastRatio(base, c2)) c1 else c2
}
private def strToHue(text: String): Float = {
Integer.parseInt(StringUtil.md5(text).substring(0, 2), 16) / 256f
}
private def getCenterToDraw(drawText: String, font: Font, w: Int, h: Int): (Int, Int) = {
val context = new FontRenderContext(new AffineTransform(), true, true)
val txt = new TextLayout(drawText, font, context)
val bounds = txt.getBounds
val x: Int = ((w - bounds.getWidth) / 2 - bounds.getX).toInt
val y: Int = ((h - bounds.getHeight) / 2 - bounds.getY).toInt
(x, y)
}
private def textImage(drawText: String, bgColor: Color, fgColor: Color): Array[Byte] = {
val canvas = new BufferedImage(iconSize, iconSize, BufferedImage.TYPE_INT_ARGB)
val g = canvas.createGraphics()
val center = getCenterToDraw(drawText, font, iconSize, iconSize)
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g.setColor(transparent)
g.fillRect(0, 0, iconSize, iconSize)
g.setColor(bgColor)
g.fillRoundRect(0, 0, iconSize, iconSize, roundSize, roundSize)
g.setColor(fgColor)
g.setFont(font)
g.drawString(drawText, center._1, center._2)
g.dispose()
val stream = new ByteArrayOutputStream
ImageIO.write(canvas, "png", stream)
stream.toByteArray
}
def textAvatar(nameText: String): Option[Array[Byte]] = {
val drawText = nameText.substring(0, 1)
val bgHue = strToHue(nameText)
val bgColor = Color.getHSBColor(bgHue, bgSaturation, bgBlightness)
val fgColor = goodContrastColor(bgColor, Color.BLACK, Color.WHITE)
val font = new Font(Font.SANS_SERIF, Font.PLAIN, fontSize)
if (font.canDisplayUpTo(drawText) == -1) Some(textImage(drawText, bgColor, fgColor)) else None
}
private def textGroupImage(drawText: String, bgColor: Color, fgColor: Color, shadowColor: Color): Array[Byte] = {
val canvas = new BufferedImage(iconSize, iconSize, BufferedImage.TYPE_INT_ARGB)
val g = canvas.createGraphics()
val center = getCenterToDraw(drawText, font, iconSize - shadowSize, iconSize - shadowSize)
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g.setColor(transparent)
g.fillRect(0, 0, iconSize, iconSize)
g.setColor(shadowColor)
g.fillRect(shadowSize, shadowSize, iconSize, iconSize)
g.setColor(bgColor)
g.fillRect(0, 0, iconSize - shadowSize, iconSize - shadowSize)
g.setColor(fgColor)
g.setFont(font)
g.drawString(drawText, center._1, center._2)
g.dispose()
val stream = new ByteArrayOutputStream
ImageIO.write(canvas, "png", stream)
stream.toByteArray
}
def textGroupAvatar(nameText: String): Option[Array[Byte]] = {
val drawText = nameText.substring(0, 1)
val bgHue = strToHue(nameText)
val bgColor = Color.getHSBColor(bgHue, bgSaturation, bgBlightness)
val fgColor = goodContrastColor(bgColor, Color.BLACK, Color.WHITE)
val shadowColor = Color.getHSBColor(bgHue, bgSaturation, shadowBlightness)
if (font.canDisplayUpTo(drawText) == -1) Some(textGroupImage(drawText, bgColor, fgColor, shadowColor)) else None
}
}

View File

@@ -20,7 +20,7 @@ trait AvatarImageProvider { self: RequestCache =>
if(account.image.isEmpty && context.settings.gravatar){
s"""https://www.gravatar.com/avatar/${StringUtil.md5(account.mailAddress.toLowerCase)}?s=${size}&d=retro&r=g"""
} else {
s"""${context.path}/${account.userName}/_avatar"""
s"""${context.path}/${account.userName}/_avatar?${helpers.hashDate(account.updatedDate)}"""
}
} getOrElse {
s"""${context.path}/_unknown/_avatar"""
@@ -31,7 +31,7 @@ trait AvatarImageProvider { self: RequestCache =>
if(account.image.isEmpty && context.settings.gravatar){
s"""https://www.gravatar.com/avatar/${StringUtil.md5(account.mailAddress.toLowerCase)}?s=${size}&d=retro&r=g"""
} else {
s"""${context.path}/${account.userName}/_avatar"""
s"""${context.path}/${account.userName}/_avatar?${helpers.hashDate(account.updatedDate)}"""
}
} getOrElse {
if(context.settings.gravatar){
@@ -49,4 +49,4 @@ trait AvatarImageProvider { self: RequestCache =>
}
}
}
}

View File

@@ -44,7 +44,6 @@ object Markdown {
val renderer = new GitBucketMarkedRenderer(options, repository,
enableWikiLink, enableRefsLink, enableAnchor, enableTaskList, hasWritePermission, pages)
//helpers.decorateHtml(Marked.marked(source, options, renderer), repository)
Marked.marked(source, options, renderer)
}

View File

@@ -74,6 +74,21 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
*/
def date(date: Date): String = new SimpleDateFormat("yyyy-MM-dd").format(date)
/**
* Format java.util.Date to "yyyyMMDDHHmmss" (for url hash ex. /some/path.css?19800101010203
*/
def hashDate(date: Date): String = new SimpleDateFormat("yyyyMMddHHmmss").format(date)
/**
* java.util.Date of boot timestamp.
*/
val bootDate: Date = new Date()
/**
* hashDate of bootDate for /assets, /plugin-assets
*/
def hashQuery: String = hashDate(bootDate)
/**
* Returns singular if count is 1, otherwise plural.
* If plural is not specified, returns singular + "s" as plural.
@@ -209,8 +224,14 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
/**
* Returns the url to the root of assets.
*/
@deprecated("Use assets(path: String)(implicit context: Context) instead.", "4.11.0")
def assets(implicit context: Context): String = s"${context.path}/assets"
/**
* Returns the url to the path of assets.
*/
def assets(path: String)(implicit context: Context): String = s"${context.path}/assets${path}?${hashQuery}"
/**
* Generates the text link to the account page.
* If user does not exist or disabled, this method returns user name as text without link.
@@ -344,6 +365,10 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
decorateHtml(HtmlFormat.fill(out).toString, repository)
}
/**
* Decorate a given HTML by TextDecorators which are provided by plug-ins.
* TextDecorators are applied to only text parts of a given HTML.
*/
def decorateHtml(html: String, repository: RepositoryInfo)(implicit context: Context): String = {
PluginRegistry().getTextDecorators.foldLeft(html){ case (html, decorator) =>
val text = new StringBuilder()

View File

@@ -4,7 +4,7 @@
@import gitbucket.core.view.helpers
@gitbucket.core.account.html.main(account, groupNames, "activity"){
<div class="pull-right">
<a href="@context.path/@{account.userName}.atom"><img src="@{helpers.assets}/common/images/feed.png" alt="activities"></a>
<a href="@context.path/@{account.userName}.atom"><img src="@helpers.assets("/common/images/feed.png")" alt="activities"></a>
</div>
@gitbucket.core.helper.html.activities(activities)
}

View File

@@ -16,9 +16,9 @@
</div>
</div>
Showing <a href="javascript:void(0);" id="toggle-file-list">@diffs.size changed @helpers.plural(diffs.size, "file")</a>
<ul id="commit-file-list" style="display: none;">
<ul id="commit-file-list" style="display: none;clear:right">
@diffs.zipWithIndex.map { case (diff, i) =>
<li@if(i > 0){ class="border"}>
<li class="border">
<span class="pull-right diffstat" data-diff-id="@i"></span>
<a href="#diff-@i">
@if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){
@@ -130,8 +130,8 @@
</tr>
</table>
}
<script type="text/javascript" src="@helpers.assets/vendors/jsdifflib/difflib.js"></script>
<link href="@helpers.assets/vendors/jsdifflib/diffview.css" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="@helpers.assets("/vendors/jsdifflib/difflib.js")"></script>
<link href="@helpers.assets("/vendors/jsdifflib/diffview.css")" type="text/css" rel="stylesheet" />
<script>
$(function(){
@if(showIndex){

View File

@@ -38,8 +38,8 @@
</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>
<link href="@helpers.assets("/vendors/google-code-prettify/prettify.css")" type="text/css" rel="stylesheet"/>
<script src="@helpers.assets("/vendors/google-code-prettify/prettify.js")"></script>
<script>
$(function(){
@if(elastic){
@@ -47,7 +47,7 @@ $(function(){
}
$('#preview@uid').click(function(){
$('#preview-area@uid').html('<img src="@helpers.assets/common/images/indicator.gif"> Previewing...');
$('#preview-area@uid').html('<img src="@helpers.assets("/common/images/indicator.gif")"> Previewing...');
$.post('@helpers.url(repository)/_preview', {
content : $('#content@uid').val(),
enableWikiLink : @enableWikiLink,

View File

@@ -13,7 +13,7 @@
@gitbucket.core.dashboard.html.tab()
<div class="container">
<div class="pull-right">
<a href="@context.path/activities.atom"><img src="@helpers.assets/common/images/feed.png" alt="activities"></a>
<a href="@context.path/activities.atom"><img src="@helpers.assets("/common/images/feed.png")" alt="activities"></a>
</div>
@gitbucket.core.helper.html.activities(activities)
</div>

View File

@@ -46,7 +46,9 @@
} else {
referenced the @issueOrPullRequest()
}
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
<a href="@helpers.url(repository)/issues/@issue.get.issueId#comment-@comment.commentId">
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
</a>
</span>
@if(comment.action != "commit" && comment.action != "merge" && comment.action != "refer"
&& (isManageable || context.loginAccount.map(_.userName == comment.commentedUserName).getOrElse(false))){

View File

@@ -7,42 +7,42 @@
<meta charset="utf-8">
<title>@title</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<link rel="icon" href="@helpers.assets/common/images/gitbucket.png" type="image/vnd.microsoft.icon" />
<link rel="icon" href="@helpers.assets("/common/images/gitbucket.png")" type="image/vnd.microsoft.icon" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="@helpers.assets/vendors/bootstrap-3.3.6/css/bootstrap.min.css" rel="stylesheet">
<link href="@helpers.assets/vendors/octicons-4.2.0/octicons.css" rel="stylesheet">
<link href="@helpers.assets/vendors/bootstrap-datetimepicker-4.17.44/css/bootstrap-datetimepicker.min.css" rel="stylesheet">
<link href="@helpers.assets/vendors/colorpicker/css/bootstrap-colorpicker.css" rel="stylesheet">
<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.8/css/AdminLTE.min.css" rel="stylesheet">
<link href="@helpers.assets/vendors/AdminLTE-2.3.8/css/skins/skin-blue.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/common/css/gitbucket.css" rel="stylesheet">
<script src="@helpers.assets/vendors/jquery/jquery-1.12.2.min.js"></script>
<script src="@helpers.assets/vendors/dropzone/dropzone.js"></script>
<script src="@helpers.assets/common/js/validation.js"></script>
<script src="@helpers.assets/common/js/gitbucket.js"></script>
<script src="@helpers.assets/vendors/bootstrap-3.3.6/js/bootstrap.js"></script>
<script src="@helpers.assets/vendors/bootstrap3-typeahead/bootstrap3-typeahead.js"></script>
<script src="@helpers.assets/vendors/bootstrap-datetimepicker-4.17.44/js/moment.min.js"></script>
<script src="@helpers.assets/vendors/bootstrap-datetimepicker-4.17.44/js/bootstrap-datetimepicker.min.js"></script>
<script src="@helpers.assets/vendors/colorpicker/js/bootstrap-colorpicker.js"></script>
<script src="@helpers.assets/vendors/google-code-prettify/prettify.js"></script>
<script src="@helpers.assets/vendors/elastic/jquery.elastic.source.js"></script>
<script src="@helpers.assets/vendors/facebox/facebox.js"></script>
<script src="@helpers.assets/vendors/jquery-hotkeys/jquery.hotkeys.js"></script>
<script src="@helpers.assets/vendors/jquery-textcomplete-1.6.2/jquery.textcomplete.js"></script>
<link href="@helpers.assets("/vendors/bootstrap-3.3.6/css/bootstrap.min.css")" rel="stylesheet">
<link href="@helpers.assets("/vendors/octicons-4.2.0/octicons.css")" rel="stylesheet">
<link href="@helpers.assets("/vendors/bootstrap-datetimepicker-4.17.44/css/bootstrap-datetimepicker.min.css")" rel="stylesheet">
<link href="@helpers.assets("/vendors/colorpicker/css/bootstrap-colorpicker.css")" rel="stylesheet">
<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.8/css/AdminLTE.min.css")" rel="stylesheet">
<link href="@helpers.assets("/vendors/AdminLTE-2.3.8/css/skins/skin-blue.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("/common/css/gitbucket.css")" rel="stylesheet">
<script src="@helpers.assets("/vendors/jquery/jquery-1.12.2.min.js")"></script>
<script src="@helpers.assets("/vendors/dropzone/dropzone.js")"></script>
<script src="@helpers.assets("/common/js/validation.js")"></script>
<script src="@helpers.assets("/common/js/gitbucket.js")"></script>
<script src="@helpers.assets("/vendors/bootstrap-3.3.6/js/bootstrap.js")"></script>
<script src="@helpers.assets("/vendors/bootstrap3-typeahead/bootstrap3-typeahead.js")"></script>
<script src="@helpers.assets("/vendors/bootstrap-datetimepicker-4.17.44/js/moment.min.js")"></script>
<script src="@helpers.assets("/vendors/bootstrap-datetimepicker-4.17.44/js/bootstrap-datetimepicker.min.js")"></script>
<script src="@helpers.assets("/vendors/colorpicker/js/bootstrap-colorpicker.js")"></script>
<script src="@helpers.assets("/vendors/google-code-prettify/prettify.js")"></script>
<script src="@helpers.assets("/vendors/elastic/jquery.elastic.source.js")"></script>
<script src="@helpers.assets("/vendors/facebox/facebox.js")"></script>
<script src="@helpers.assets("/vendors/jquery-hotkeys/jquery.hotkeys.js")"></script>
<script src="@helpers.assets("/vendors/jquery-textcomplete-1.6.2/jquery.textcomplete.js")"></script>
@repository.map { repository =>
<meta name="go-import" content="@context.baseUrl.replaceFirst("^https?://", "")/@repository.owner/@repository.name git @repository.httpUrl" />
}
<script src="@helpers.assets/vendors/AdminLTE-2.3.8/js/app.js" type="text/javascript"></script>
<script src="@helpers.assets("/vendors/AdminLTE-2.3.8/js/app.js")" type="text/javascript"></script>
</head>
<body class="skin-blue page-load @if(context.sidebarCollapse){sidebar-collapse}">
<div class="wrapper">
<header class="main-header">
<a href="@context.path/" class="logo">
<img src="@helpers.assets/common/images/gitbucket.png" style="width: 24px; height: 24px; display: inline;"/>
<img src="@helpers.assets("/common/images/gitbucket.png")" style="width: 24px; height: 24px; display: inline;"/>
GitBucket
<span class="header-version">@gitbucket.core.GitBucketCoreModule.getVersions.last.getVersion</span>
</a>

View File

@@ -43,7 +43,7 @@
}
</div>
<div class="check-conflict" style="display: none;">
<img src="@helpers.assets/common/images/indicator.gif"/> Checking...
<img src="@helpers.assets("/common/images/indicator.gif")"/> Checking...
</div>
</div>
@if(commits.nonEmpty && context.loginAccount.isDefined){

View File

@@ -21,7 +21,7 @@
<div class="check-conflict" style="display: none;">
<div class="issue-comment-box" style="background-color: #fbeed5">
<div class="box-content"class="issue-content" style="border: 1px solid #c09853; padding: 10px;">
<img src="@helpers.assets/common/images/indicator.gif"/> Checking...
<img src="@helpers.assets("/common/images/indicator.gif")"/> Checking...
</div>
</div>
</div>
@@ -63,4 +63,4 @@ $(function(){
});
}
});
</script>
</script>

View File

@@ -112,20 +112,19 @@
<p>
<span class="strong">Step 1:</span> From your project repository, check out a new branch and test the changes.
</p>
@defining(s"git checkout -b ${pullreq.requestUserName}-${pullreq.requestBranch} ${pullreq.branch}\n" +
s"git pull ${forkedRepository.httpUrl} ${pullreq.requestBranch}"){ command =>
@gitbucket.core.helper.html.copy("merge-command", "merge-command-copy-1", command, "position: absolute; right: 31px;")()
<pre style="font-size: 12px; border-radius: 3px;" id="merge-command">@Html(command)</pre>
@helpers.pre {
git checkout -b @{pullreq.requestUserName}-@{pullreq.requestBranch} @{pullreq.branch}
git pull @{forkedRepository.httpUrl} @{pullreq.requestBranch}
}
</div>
<div>
<p>
<span class="strong">Step 2:</span> Merge the changes and update on the server.
</p>
@defining(s"git checkout ${pullreq.branch}\ngit merge --no-ff ${pullreq.requestUserName}-${pullreq.requestBranch}\n" +
s"git push origin ${pullreq.branch}"){ command =>
@gitbucket.core.helper.html.copy("merge-command-2", "merge-command-copy-2", command, "position: absolute; right: 31px;")()
<pre style="font-size: 12px; border-radius: 3px;" id="merge-command-2">@command</pre>
@helpers.pre {
git checkout @{pullreq.branch}
git merge --no-ff @{pullreq.requestUserName}-@{pullreq.requestBranch}
git push origin @{pullreq.branch}
}
</div>
</div>

View File

@@ -50,10 +50,10 @@
</form>
}
}
<script type="text/javascript" src="@helpers.assets/vendors/jsdifflib/difflib.js"></script>
<link href="@helpers.assets/vendors/jsdifflib/diffview.css" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="@helpers.assets("/vendors/jsdifflib/difflib.js")"></script>
<link href="@helpers.assets("/vendors/jsdifflib/diffview.css")" type="text/css" rel="stylesheet" />
<script>
$(function(){
diffUsingJS('oldText', 'newText', 'diffText', 1);
});
</script>
</script>

View File

@@ -72,9 +72,9 @@
</form>
}
}
<script src="@helpers.assets/vendors/ace/ace.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" src="@helpers.assets/vendors/jsdifflib/difflib.js"></script>
<link href="@helpers.assets/vendors/jsdifflib/diffview.css" type="text/css" rel="stylesheet" />
<script src="@helpers.assets("/vendors/ace/ace.js")" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" src="@helpers.assets("/vendors/jsdifflib/difflib.js")"></script>
<link href="@helpers.assets("/vendors/jsdifflib/diffview.css")" type="text/css" rel="stylesheet" />
<script>
$(function(){
$('#editor').text($('#initial').val());
@@ -132,7 +132,7 @@ $(function(){
@if(fileName.map(helpers.isRenderable _).getOrElse(false)) {
// update preview
$('#preview').html('<img src="@helpers.assets/common/images/indicator.gif"> Previewing...');
$('#preview').html('<img src="@helpers.assets("/common/images/indicator.gif")"> Previewing...');
$.post('@helpers.url(repository)/_preview', {
content : editor.getValue(),
enableWikiLink : false,

View File

@@ -49,8 +49,8 @@
<script>
$(function(){
$('a[rel*=facebox]').facebox({
'loadingImage': '@helpers.assets/vendors/facebox/loading.gif',
'closeImage': '@helpers.assets/vendors/facebox/closelabel.png'
'loadingImage': '@helpers.assets("/vendors/facebox/loading.gif")',
'closeImage': '@helpers.assets("/vendors/facebox/closelabel.png")'
});
$(document).on("click", ".js-fork-owner-select-target", function() {

View File

@@ -46,14 +46,12 @@
</form>
</p>
<table class="table table-bordered table-hover branches">
<table class="table table-hover branches">
@protectedBranchList.map { branch =>
<tr>
<td>
<span class="branch-name">@branch</span>
<span class="branch-action">
<a href="@helpers.url(repository)/settings/branches/@helpers.encodeRefName(branch)" class="btn btn-small btn-default">Edit</a>
</span>
<a href="@helpers.url(repository)/settings/branches/@helpers.urlEncode(branch)" class="btn btn-sm btn-default pull-right">Edit</a>
</td>
</tr>
}
@@ -64,4 +62,4 @@
}
}
}
}
}

View File

@@ -33,30 +33,30 @@
Sorry, we couldnt find any status checks in the last week for this repository.<br />
Please create a commit status by API (<a href="https://developer.github.com/v3/repos/statuses/">Learn more about status checks on GitHub</a>)
</div>
}else{
<div class="js-has_required_statuses" style="display:none">
<div class="panel panel-default">
<div class="panel-heading">Choose which status checks must pass before branches can be merged into <b>@branch</b>.</div>
<div class="panel-body">
@knownContexts.map { context =>
<div class="checkbox">
<label>
<input type="checkbox" name="contexts" value="@context" onclick="update()" @check(protection.status.contexts.find(_ == context))>
<span>@context</span>
</label>
</div>
}
} else {
<div class="js-has_required_statuses" style="display:none">
<div class="panel panel-default">
<div class="panel-heading">Choose which status checks must pass before branches can be merged into <b>@branch</b>.</div>
<div class="panel-body">
@knownContexts.map { context =>
<div class="checkbox">
<label>
<input type="checkbox" name="contexts" value="@context" onclick="update()" @check(protection.status.contexts.find(_ == context))>
<span>@context</span>
</label>
</div>
}
</div>
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="enforce_for_admins" onclick="update()" @check(protection.status.enforcement_level.name=="everyone")>
<span class="strong">Include administrators</span>
</label>
<p class="help-block">Enforce required status checks for repository administrators.</p>
</div>
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="enforce_for_admins" onclick="update()" @check(protection.status.enforcement_level.name=="everyone")>
<span class="strong">Include administrators</span>
</label>
<p class="help-block">Enforce required status checks for repository administrators.</p>
</div>
</div>
}
</div>
<input class="btn btn-success js-submit-btn" type="submit" value="Save changes" />

View File

@@ -33,9 +33,9 @@
<fieldset class="form-group">
<label class="strong">Content type</label>
<div></div>
<select name="ctype">
<option value="@WebHookContentType.FORM.code" @if(webHook.ctype == WebHookContentType.FORM){selected}>@WebHookContentType.FORM.ctype</option>
<option value="@WebHookContentType.JSON.code" @if(webHook.ctype == WebHookContentType.JSON){selected}>@WebHookContentType.JSON.ctype</option>
<select name="ctype" class="form-control" style="width: 500px;">
<option value="@WebHookContentType.FORM.code" @if(webHook.ctype == WebHookContentType.FORM){selected}>@WebHookContentType.FORM.ctype</option>
<option value="@WebHookContentType.JSON.code" @if(webHook.ctype == WebHookContentType.JSON){selected}>@WebHookContentType.JSON.ctype</option>
</select>
</fieldset>
<fieldset class="form-group">

View File

@@ -469,20 +469,6 @@ table.branches>thead>tr>th, table.branches>tbody>tr>td{
color: #aaa;
line-height: 20px;
}
.branches .branch-action{
/*float: right;*/
text-align: right;
height: 20px;
}
.branches .branch-action a {
font-size: 100%;
}
.branches .branch-action a.btn {
font-size: 100%;
position: relative;
top: -6px;
}
.branches .branch-a-b-count{
color: rgba(0,0,0,0.5);
}
@@ -1141,7 +1127,7 @@ table.diff tbody tr.not-diff:hover td{
}
.not-diff > .comment-box-container {
white-space: initial;
white-space: normal;
line-height: initial;
padding: 10px;
}

View File

@@ -1,5 +1,6 @@
package gitbucket.core.view
import java.text.SimpleDateFormat
import java.util.Date
import gitbucket.core.model.Account
@@ -34,18 +35,22 @@ class AvatarImageProviderSpec extends FunSpec with MockitoSugar {
it("should show uploaded image even if gravatar integration is enabled") {
implicit val context = Context(createSystemSettings(true), None, request)
val provider = new AvatarImageProviderImpl(Some(createAccount(Some("icon.png"))))
val account = createAccount((Some("icon.png")))
val date = new SimpleDateFormat("yyyyMMddHHmmss").format(account.updatedDate)
val provider = new AvatarImageProviderImpl(Some(account))
assert(provider.toHtml("user", 32).toString ==
"<img src=\"/user/_avatar\" class=\"avatar\" style=\"width: 32px; height: 32px;\" />")
s"""<img src="/user/_avatar?${date}" class="avatar" style="width: 32px; height: 32px;" />""")
}
it("should show local image for no image account if gravatar integration is disabled") {
implicit val context = Context(createSystemSettings(false), None, request)
val provider = new AvatarImageProviderImpl(Some(createAccount(None)))
val account = createAccount(None)
val date = new SimpleDateFormat("yyyyMMddHHmmss").format(account.updatedDate)
val provider = new AvatarImageProviderImpl(Some(account))
assert(provider.toHtml("user", 32).toString ==
"<img src=\"/user/_avatar\" class=\"avatar\" style=\"width: 32px; height: 32px;\" />")
s"""<img src="/user/_avatar?${date}" class="avatar" style="width: 32px; height: 32px;" />""")
}
it("should show Gravatar image for specified mail address if gravatar integration is enabled") {