mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-12-26 10:19:55 +01:00
Merge pull request #1966 from kounoike/pr-editorconfig-for-repository
Support EditorConfig for online browser/editor.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
146
src/main/scala/gitbucket/core/util/EditorConfigUtil.scala
Normal file
146
src/main/scala/gitbucket/core/util/EditorConfigUtil.scala
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user