Merge pull request #1966 from kounoike/pr-editorconfig-for-repository

Support EditorConfig for online browser/editor.
This commit is contained in:
Naoki Takezoe
2018-07-01 22:46:53 +09:00
committed by GitHub
6 changed files with 233 additions and 15 deletions

View File

@@ -68,7 +68,8 @@ libraryDependencies ++= Seq(
"com.wix" % "wix-embedded-mysql" % "3.0.0" % "test",
"ru.yandex.qatools.embed" % "postgresql-embedded" % "2.6" % "test",
"net.i2p.crypto" % "eddsa" % "0.2.0",
"is.tagomor.woothee" % "woothee-java" % "1.7.0"
"is.tagomor.woothee" % "woothee-java" % "1.7.0",
"org.ec4j.core" % "ec4j-core" % "0.0.1"
)
// Compiler settings

View File

@@ -26,6 +26,7 @@ import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream
import org.apache.commons.compress.utils.IOUtils
import org.scalatra.forms._
import org.apache.commons.io.FileUtils
import org.ec4j.core.model.PropertyType
import org.eclipse.jgit.api.{ArchiveCommand, Git}
import org.eclipse.jgit.archive.{TgzFormat, ZipFormat}
import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder}
@@ -330,17 +331,23 @@ trait RepositoryViewerControllerBase extends ControllerBase {
git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
getPathObjectId(git, path, revCommit).map { objectId =>
val paths = path.split("/")
html.editor(
branch = branch,
repository = repository,
pathList = paths.take(paths.size - 1).toList,
fileName = Some(paths.last),
content = JGitUtil.getContentInfo(git, path, objectId),
protectedBranch = protectedBranch,
commit = revCommit.getName
)
getPathObjectId(git, path, revCommit).map {
objectId =>
val paths = path.split("/")
val info = EditorConfigUtil.getEditorConfigInfo(git, branch, path)
html.editor(
branch = branch,
repository = repository,
pathList = paths.take(paths.size - 1).toList,
fileName = Some(paths.last),
content = JGitUtil.getContentInfo(git, path, objectId),
protectedBranch = protectedBranch,
commit = revCommit.getName,
newLineMode = info.newLineMode,
useSoftTabs = info.useSoftTabs,
tabSize = info.tabSize
)
} getOrElse NotFound()
}
})
@@ -451,6 +458,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
// Download (This route is left for backword compatibility)
responseRawFile(git, objectId, path, repository)
} else {
val info = EditorConfigUtil.getEditorConfigInfo(git, id, path)
html.blob(
branch = id,
repository = repository,
@@ -459,7 +467,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
latestCommit = new JGitUtil.CommitInfo(JGitUtil.getLastModifiedCommit(git, revCommit, path)),
hasWritePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount),
isBlame = request.paths(2) == "blame",
isLfsFile = isLfsFile(git, objectId)
isLfsFile = isLfsFile(git, objectId),
tabSize = info.tabSize
)
}
} getOrElse NotFound()

View File

@@ -0,0 +1,146 @@
package gitbucket.core.util
import java.io.{IOException, InputStreamReader, Reader}
import java.nio.charset.StandardCharsets
import org.ec4j.core.Resource.Resources.StringRandomReader
import org.ec4j.core.model.PropertyType.{EndOfLineValue, IndentStyleValue}
import org.ec4j.core.model.{Ec4jPath, PropertyType, Version}
import org.ec4j.core.parser.ParseException
import org.ec4j.core._
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.{ObjectReader, Repository}
import org.eclipse.jgit.revwalk.{RevTree, RevWalk}
import org.eclipse.jgit.treewalk.TreeWalk
import gitbucket.core.util.SyntaxSugars._
object EditorConfigUtil {
private class JGitResource(repo: Repository, revStr: String, path: Ec4jPath) extends Resource {
private def removeInitialSlash(path: Ec4jPath) = Ec4jPath.Ec4jPaths.root.relativize(path).toString
def this(git: Git, revStr: String, path: String) = {
this(git.getRepository, revStr, Ec4jPath.Ec4jPaths.of(if (path.startsWith("/")) path else "/" + path))
}
def this(repo: Repository, revStr: String, path: String) = {
this(repo, revStr, Ec4jPath.Ec4jPaths.of(if (path.startsWith("/")) path else "/" + path))
}
private def getRevTree: RevTree = {
using(repo.newObjectReader()) { reader: ObjectReader =>
val revWalk = new RevWalk(reader)
val id = repo.resolve(revStr)
val commit = revWalk.parseCommit(id)
commit.getTree
}
}
override def exists(): Boolean = {
using(repo.newObjectReader()) { reader: ObjectReader =>
try {
val treeWalk = Option(TreeWalk.forPath(reader, removeInitialSlash(path), getRevTree))
treeWalk.isDefined
} catch {
case e: IOException => false
}
}
}
override def getPath: Ec4jPath = {
path
}
override def getParent: ResourcePath = {
Option(path.getParentPath).map { new JGitResourcePath(repo, revStr, _) }.getOrElse(null)
}
override def openRandomReader(): Resource.RandomReader = {
StringRandomReader.ofReader(openReader())
}
override def openReader(): Reader = {
using(repo.newObjectReader) { reader: ObjectReader =>
val treeWalk = TreeWalk.forPath(reader, removeInitialSlash(path), getRevTree)
new InputStreamReader(reader.open(treeWalk.getObjectId(0)).openStream, StandardCharsets.UTF_8)
}
}
}
private class JGitResourcePath(repo: Repository, revStr: String, path: Ec4jPath) extends ResourcePath {
override def getParent: ResourcePath = {
Option(path.getParentPath).map { new JGitResourcePath(repo, revStr, _) }.getOrElse(null)
}
override def getPath: Ec4jPath = {
path
}
override def hasParent: Boolean = {
Option(path.getParentPath).isDefined
}
override def relativize(resource: Resource): Resource = {
resource match {
case r: JGitResource =>
new JGitResource(repo, revStr, path.relativize(r.getPath).toString)
}
}
override def resolve(name: String): Resource = {
Option(path)
.map { p =>
new JGitResource(repo, revStr, p.resolve(name))
}
.getOrElse {
new JGitResource(repo, revStr, name)
}
}
}
private object JGitResourcePath {
def RootDirectory(git: Git, revStr: String) =
new JGitResourcePath(git.getRepository, revStr, Ec4jPath.Ec4jPaths.of("/"))
}
private val TabSizeDefault: Int = 8
private val NewLineModeDefault: String = "auto"
private val UseSoftTabsDefault = false
case class EditorConfigInfo(
tabSize: Int,
newLineMode: String,
useSoftTabs: Boolean
)
def getEditorConfigInfo(git: Git, rev: String, path: String): EditorConfigInfo = {
try {
val resourcePropertiesService = ResourcePropertiesService
.builder()
.configFileName(EditorConfigConstants.EDITORCONFIG)
.rootDirectory(JGitResourcePath.RootDirectory(git, rev))
.loader(EditorConfigLoader.of(Version.CURRENT))
.keepUnset(true)
.build()
val props = resourcePropertiesService.queryProperties(new JGitResource(git, rev, path))
EditorConfigInfo(
tabSize = props.getValue[Integer](PropertyType.tab_width, TabSizeDefault, false),
newLineMode = props.getValue[EndOfLineValue](PropertyType.end_of_line, null, false) match {
case EndOfLineValue.cr => "cr"
case EndOfLineValue.lf => "lf"
case EndOfLineValue.crlf => "crlf"
case _ => "auto"
},
props.getValue[IndentStyleValue](PropertyType.indent_style, null, false) match {
case IndentStyleValue.space => true
case IndentStyleValue.tab => false
case _ => false
}
)
} catch {
case e: ParseException => EditorConfigInfo(TabSizeDefault, NewLineModeDefault, UseSoftTabsDefault)
case e: IOException => EditorConfigInfo(TabSizeDefault, NewLineModeDefault, UseSoftTabsDefault)
}
}
}

View File

@@ -5,10 +5,16 @@
latestCommit: gitbucket.core.util.JGitUtil.CommitInfo,
hasWritePermission: Boolean,
isBlame: Boolean,
isLfsFile: Boolean)(implicit context: gitbucket.core.controller.Context)
isLfsFile: Boolean,
tabSize: Int)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@gitbucket.core.html.main(s"${(repository.name :: pathList).mkString("/")} at ${branch} - ${repository.owner}/${repository.name}", Some(repository)) {
@gitbucket.core.html.menu("files", repository){
<style>
.prettyprint {
tab-size: @tabSize
}
</style>
<div class="head">
<div class="pull-right hide-if-blame"><div class="btn-group">
<a href="@helpers.url(repository)/blob/@helpers.encodeRefName((latestCommit.id :: pathList).mkString("/"))" data-hotkey="y" style="display: none;">Transfer to URL with SHA</a>

View File

@@ -4,7 +4,11 @@
fileName: Option[String],
content: gitbucket.core.util.JGitUtil.ContentInfo,
protectedBranch: Boolean,
commit: String)(implicit context: gitbucket.core.controller.Context)
commit: String,
newLineMode: String = "auto",
useSoftTabs: Boolean = false,
tabSize: Int = 8
)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@gitbucket.core.html.main(if(fileName.isEmpty) "New File" else s"Editing ${fileName.get} at ${branch} - ${repository.owner}/${repository.name}", Some(repository)) {
@gitbucket.core.html.menu("files", repository){
@@ -97,6 +101,9 @@ $(function(){
@if(protectedBranch){
editor.setReadOnly(true);
}
editor.getSession().setOption("tabSize", @tabSize);
editor.getSession().setOption("newLineMode", "@newLineMode");
editor.getSession().setOption("useSoftTabs", @useSoftTabs);
editor.on('change', function(){
updateCommitButtonStatus();

View File

@@ -0,0 +1,49 @@
package gitbucket.core.util
import org.scalatest.FunSuite
import GitSpecUtil._
class EditorConfigUtilSpec extends FunSuite {
val simpleConfig =
"""[*.txt]
|indent_style = tab
|indent_size = 4""".stripMargin
test("no EditorConfig file") {
withTestRepository { git =>
createFile(git, "master", "README.md", "body", message = "commit1")
val info = EditorConfigUtil.getEditorConfigInfo(git, "master", "test.txt")
assert(info.tabSize == 8)
assert(info.useSoftTabs == false)
assert(info.newLineMode == "auto")
val subdirInfo = EditorConfigUtil.getEditorConfigInfo(git, "master", "dir1/dir2/dir3/dir4/test.txt")
assert(subdirInfo.tabSize == 8)
assert(subdirInfo.useSoftTabs == false)
assert(subdirInfo.newLineMode == "auto")
}
}
test("simple EditorConfig") {
withTestRepository { git =>
createFile(git, "master", ".editorconfig", simpleConfig, message = "commit1")
val info = EditorConfigUtil.getEditorConfigInfo(git, "master", "test.txt")
assert(info.tabSize == 4)
val subdirInfo = EditorConfigUtil.getEditorConfigInfo(git, "master", "dir1/dir2/dir3/dir4/test.txt")
assert(subdirInfo.tabSize == 4)
}
}
test(".editorconfig parse error") {
withTestRepository { git =>
createFile(git, "master", ".editorconfig", "equal_missing", message = "commit1")
val info = EditorConfigUtil.getEditorConfigInfo(git, "master", "test.txt")
assert(info.tabSize == 8)
assert(info.useSoftTabs == false)
assert(info.newLineMode == "auto")
}
}
}