Separate complex JGit operation to JGitUtil from

RepositoryViewerServlet.
This commit is contained in:
takezoe
2013-04-29 04:06:11 +09:00
parent f180364f7f
commit c913b7b6c4
3 changed files with 217 additions and 110 deletions

View File

@@ -2,6 +2,7 @@ package app
import util.Directory._ import util.Directory._
import util.Implicits._ import util.Implicits._
import util.{JGitUtil, FileTypeUtil}
import org.scalatra._ import org.scalatra._
import java.io.File import java.io.File
import java.util.Date import java.util.Date
@@ -11,12 +12,11 @@ import org.apache.commons.io.FileUtils
import org.eclipse.jgit.treewalk._ import org.eclipse.jgit.treewalk._
import org.eclipse.jgit.revwalk.RevCommit import org.eclipse.jgit.revwalk.RevCommit
import org.eclipse.jgit.diff.DiffEntry.ChangeType import org.eclipse.jgit.diff.DiffEntry.ChangeType
import org.eclipse.jgit.errors.MissingObjectException import org.eclipse.jgit.revwalk.RevWalk
import org.apache.commons.io.FilenameUtils
case class RepositoryInfo(owner: String, name: String, url: String, branchList: List[String], tags: List[String]) case class RepositoryInfo(owner: String, name: String, url: String, branchList: List[String], tags: List[String])
case class FileInfo(isDirectory: Boolean, name: String, time: Date, message: String, committer: String) case class FileInfo(id: ObjectId, isDirectory: Boolean, name: String, time: Date, message: String, committer: String)
case class CommitInfo(id: String, time: Date, committer: String, message: String){ case class CommitInfo(id: String, time: Date, committer: String, message: String){
def this(rev: org.eclipse.jgit.revwalk.RevCommit) = def this(rev: org.eclipse.jgit.revwalk.RevCommit) =
@@ -37,7 +37,7 @@ class RepositoryViewerServlet extends ServletBase {
*/ */
get("/:owner") { get("/:owner") {
val owner = params("owner") val owner = params("owner")
html.user(owner, getRepositories(owner).map(getRepositoryInfo(owner, _))) html.user(owner, getRepositories(owner).map(JGitUtil.getRepositoryInfo(owner, _, servletContext)))
} }
/** /**
@@ -53,21 +53,21 @@ class RepositoryViewerServlet extends ServletBase {
/** /**
* Displays the file list of the repository root and the specified branch. * Displays the file list of the repository root and the specified branch.
*/ */
get("/:owner/:repository/tree/:branch") { get("/:owner/:repository/tree/:id") {
val owner = params("owner") val owner = params("owner")
val repository = params("repository") val repository = params("repository")
fileList(owner, repository, params("branch")) fileList(owner, repository, params("id"))
} }
/** /**
* Displays the file list of the specified path and branch. * Displays the file list of the specified path and branch.
*/ */
get("/:owner/:repository/tree/:branch/*") { get("/:owner/:repository/tree/:id/*") {
val owner = params("owner") val owner = params("owner")
val repository = params("repository") val repository = params("repository")
fileList(owner, repository, params("branch"), multiParams("splat").head) fileList(owner, repository, params("id"), multiParams("splat").head)
} }
/** /**
@@ -89,7 +89,7 @@ class RepositoryViewerServlet extends ServletBase {
val (logs, hasNext) = getCommitLog(Git.open(dir).log.call.iterator, 0, Nil) val (logs, hasNext) = getCommitLog(Git.open(dir).log.call.iterator, 0, Nil)
html.commits(branchName, getRepositoryInfo(owner, repository), html.commits(branchName, JGitUtil.getRepositoryInfo(owner, repository, servletContext),
logs.splitWith{ (commit1, commit2) => logs.splitWith{ (commit1, commit2) =>
view.helpers.date(commit1.time) == view.helpers.date(commit2.time) view.helpers.date(commit1.time) == view.helpers.date(commit2.time)
}, page, hasNext) }, page, hasNext)
@@ -104,7 +104,7 @@ class RepositoryViewerServlet extends ServletBase {
val id = params("id") // branch name or commit id val id = params("id") // branch name or commit id
val raw = params.get("raw").getOrElse("false").toBoolean val raw = params.get("raw").getOrElse("false").toBoolean
val path = multiParams("splat").head.replaceFirst("^tree/.+?/", "") val path = multiParams("splat").head.replaceFirst("^tree/.+?/", "")
val repositoryInfo = getRepositoryInfo(owner, repository) val repositoryInfo = JGitUtil.getRepositoryInfo(owner, repository, servletContext)
if(repositoryInfo.branchList.contains(id)){ if(repositoryInfo.branchList.contains(id)){
// id is branch name // id is branch name
@@ -119,7 +119,7 @@ class RepositoryViewerServlet extends ServletBase {
file file
} else { } else {
// Viewer // Viewer
val viewer = if(isImage(file.getName)) "image" else if(isLarge(file.length)) "large" else "text" val viewer = if(FileTypeUtil.isImage(file.getName)) "image" else if(FileTypeUtil.isLarge(file.length)) "large" else "text"
val content = ContentInfo( val content = ContentInfo(
viewer, if(viewer == "text") Some(FileUtils.readFileToString(file, "UTF-8")) else None viewer, if(viewer == "text") Some(FileUtils.readFileToString(file, "UTF-8")) else None
) )
@@ -127,7 +127,7 @@ class RepositoryViewerServlet extends ServletBase {
} }
} else { } else {
// id is commit id // id is commit id
val branch = getBranchNameFromCommitId(id, repositoryInfo) val branch = JGitUtil.getBranchNameFromCommitId(id, repositoryInfo)
val dir = getBranchDir(owner, repository, branch) val dir = getBranchDir(owner, repository, branch)
val git = Git.open(dir) val git = Git.open(dir)
val rev = git.log.add(ObjectId.fromString(id)).call.iterator.next val rev = git.log.add(ObjectId.fromString(id)).call.iterator.next
@@ -146,13 +146,13 @@ class RepositoryViewerServlet extends ServletBase {
if(raw){ if(raw){
// Download // Download
contentType = "application/octet-stream" contentType = "application/octet-stream"
getContent(git, objectId, false) JGitUtil.getContent(git, objectId, false)
} else { } else {
// Viewer // Viewer
val large = isLarge(git.getRepository.getObjectDatabase.open(objectId).getSize) val large = FileTypeUtil.isLarge(git.getRepository.getObjectDatabase.open(objectId).getSize)
val viewer = if(isImage(path)) "image" else if(large) "large" else "text" val viewer = if(FileTypeUtil.isImage(path)) "image" else if(large) "large" else "text"
val content = ContentInfo(viewer, if(viewer == "text") getContent(git, objectId, false).map(new String(_, "UTF-8")) else None) val content = ContentInfo(viewer, if(viewer == "text") JGitUtil.getContent(git, objectId, false).map(new String(_, "UTF-8")) else None)
html.blob(branch, repositoryInfo, path.split("/").toList, content, new CommitInfo(rev)) html.blob(branch, repositoryInfo, path.split("/").toList, content, new CommitInfo(rev))
} }
@@ -167,7 +167,7 @@ class RepositoryViewerServlet extends ServletBase {
val repository = params("repository") val repository = params("repository")
val id = params("id") val id = params("id")
val repositoryInfo = getRepositoryInfo(owner, repository) val repositoryInfo = JGitUtil.getRepositoryInfo(owner, repository, servletContext)
// get branch by commit id // get branch by commit id
val branch = repositoryInfo.branchList.find { branch => val branch = repositoryInfo.branchList.find { branch =>
@@ -194,8 +194,8 @@ class RepositoryViewerServlet extends ServletBase {
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
git.diff.setNewTree(newTreeIter).setOldTree(oldTreeIter).call.asScala.map { diff => git.diff.setNewTree(newTreeIter).setOldTree(oldTreeIter).call.asScala.map { diff =>
DiffInfo(diff.getChangeType, diff.getOldPath, diff.getNewPath, DiffInfo(diff.getChangeType, diff.getOldPath, diff.getNewPath,
getContent(git, diff.getOldId.toObjectId, false).map(new String(_, "UTF-8")), JGitUtil.getContent(git, diff.getOldId.toObjectId, false).map(new String(_, "UTF-8")),
getContent(git, diff.getNewId.toObjectId, false).map(new String(_, "UTF-8"))) JGitUtil.getContent(git, diff.getNewId.toObjectId, false).map(new String(_, "UTF-8")))
} }
} else { } else {
// initial commit // initial commit
@@ -204,8 +204,9 @@ class RepositoryViewerServlet extends ServletBase {
val buffer = new scala.collection.mutable.ListBuffer[DiffInfo]() val buffer = new scala.collection.mutable.ListBuffer[DiffInfo]()
while(walk.next){ while(walk.next){
buffer.append(DiffInfo(ChangeType.ADD, null, walk.getPathString, None, buffer.append(DiffInfo(ChangeType.ADD, null, walk.getPathString, None,
getContent(git, walk.getObjectId(0), false).map(new String(_, "UTF-8")))) JGitUtil.getContent(git, walk.getObjectId(0), false).map(new String(_, "UTF-8"))))
} }
walk.release
buffer.toList buffer.toList
} }
@@ -214,113 +215,47 @@ class RepositoryViewerServlet extends ServletBase {
repositoryInfo, diffs) repositoryInfo, diffs)
} }
///////////////////////////////////////////////////////////////////////////////////////////////
//
// TODO Helper methods should be separated to object?
//
//////////////////////////////////////////////////////////////////////////////////////////////
/**
* Get the branch name from the commit id.
*/
def getBranchNameFromCommitId(id: String, repositoryInfo: RepositoryInfo): String = {
repositoryInfo.branchList.find { branch =>
val git = Git.open(getBranchDir(repositoryInfo.owner, repositoryInfo.name, branch))
git.log.add(ObjectId.fromString(id)).call.iterator.hasNext
}.get
}
/**
* Get object content of the given id as String from the Git repository.
*
* @param git the Git object
* @param id the object id
* @param large if true then returns None for the large file
* @return the object or None if object does not exist
*/
def getContent(git: Git, id: ObjectId, large: Boolean): Option[Array[Byte]] = try {
val loader = git.getRepository.getObjectDatabase.open(id)
if(large == false && isLarge(loader.getSize)){
None
} else {
Some(git.getRepository.getObjectDatabase.open(id).getBytes)
}
} catch {
case e: MissingObjectException => None
}
/**
* Returns the repository information. It contains branch names and tag names.
*
* @param owner the repository owner
* @param repository the repository name
*/
def getRepositoryInfo(owner: String, repository: String): RepositoryInfo = {
val git = Git.open(getRepositoryDir(owner, repository))
RepositoryInfo(
owner, repository, "http://localhost:8080%s/git/%s/%s.git".format(servletContext.getContextPath, owner, repository),
// branches
git.branchList.call.toArray.map { ref =>
ref.asInstanceOf[Ref].getName.replaceFirst("^refs/heads/", "")
}.toList,
// tags
git.tagList.call.toArray.map { ref =>
ref.asInstanceOf[Ref].getName
}.toList
)
}
/** /**
* Provides HTML of the file list. * Provides HTML of the file list.
* *
* @param owner the repository owner * @param owner the repository owner
* @param repository the repository name * @param repository the repository name
* @param branch the branch name (optional) * @param rev the branch name or commit id(optional)
* @param path the directory path (optional) * @param path the directory path (optional)
* @return HTML of the file list * @return HTML of the file list
*/ */
def fileList(owner: String, repository: String, branch: String = "", path: String = ".") = { private def fileList(owner: String, repository: String, revstr: String = "", path: String = ".") = {
val branchName = if(branch.isEmpty){ val revision = if(revstr.isEmpty){
Git.open(getRepositoryDir(owner, repository)).getRepository.getBranch Git.open(getRepositoryDir(owner, repository)).getRepository.getBranch
} else { } else {
branch revstr
} }
val git = Git.open(getRepositoryDir(owner, repository))
// get latest commit
val revWalk = new RevWalk(git.getRepository)
val objectId = git.getRepository.resolve(revision)
val revCommit = revWalk.parseCommit(objectId)
val dir = getBranchDir(owner, repository, branchName) val files = JGitUtil.getFileList(owner, repository, revision, path)
val git = Git.open(dir)
val latestRev = {if(path == ".") git.log else git.log.addPath(path)}.call.iterator.next
val files = new File(dir, path).listFiles()
.filterNot{ file => file.getName == ".git" }
.sortWith { (file1, file2) => (file1.isDirectory, file2.isDirectory) match {
case (true , false) => true
case (false, true ) => false
case _ => file1.getName.compareTo(file2.getName) < 0
}}
.map { file =>
val rev = Git.open(dir).log.addPath(if(path == ".") file.getName else path + "/" + file.getName).call.iterator.next
if(rev == null){
None
} else {
Some(FileInfo(file.isDirectory, file.getName, rev.getCommitterIdent.getWhen, rev.getShortMessage, rev.getCommitterIdent.getName))
}
}
.flatten.toList
// process README.md // process README.md
val readme = files.find(_.name == "README.md").map { file => val readme = files.find(_.name == "README.md").map { file =>
import org.pegdown._ import org.pegdown._
new PegDownProcessor().markdownToHtml(FileUtils.readFileToString(new File(dir, path + "/" + file.name), "UTF-8")) val git = Git.open(getRepositoryDir(owner, repository))
new PegDownProcessor().markdownToHtml(new String(git.getRepository.open(file.id).getBytes, "UTF-8"))
} }
html.files( html.files(
// current branch // current branch
branchName, revision,
// repository // repository
getRepositoryInfo(owner, repository), JGitUtil.getRepositoryInfo(owner, repository, servletContext),
// current path // current path
if(path == ".") Nil else path.split("/").toList, if(path == ".") Nil else path.split("/").toList,
// latest commit // latest commit
CommitInfo(latestRev.getName, latestRev.getCommitterIdent.getWhen, latestRev.getCommitterIdent.getName, latestRev.getShortMessage), CommitInfo(revCommit.getName, revCommit.getCommitterIdent.getWhen, revCommit.getCommitterIdent.getName, revCommit.getShortMessage),
// file list // file list
files, files,
// readme // readme
@@ -328,11 +263,4 @@ class RepositoryViewerServlet extends ServletBase {
) )
} }
def isImage(name: String): Boolean = FilenameUtils.getExtension(name).toLowerCase match {
case "jpg"|"jpeg"|"bmp"|"gif"|"png" => true
case _ => false
}
def isLarge(size: Long): Boolean = (size > 1024 * 1000)
} }

View File

@@ -0,0 +1,14 @@
package util
import org.apache.commons.io.FilenameUtils
object FileTypeUtil {
def isImage(name: String): Boolean = FilenameUtils.getExtension(name).toLowerCase match {
case "jpg"|"jpeg"|"bmp"|"gif"|"png" => true
case _ => false
}
def isLarge(size: Long): Boolean = (size > 1024 * 1000)
}

View File

@@ -0,0 +1,165 @@
package util
import org.eclipse.jgit.api.Git
import app.{RepositoryInfo, FileInfo}
import util.Directory._
import scala.collection.JavaConverters._
import javax.servlet.ServletContext
import org.eclipse.jgit.lib.Ref
import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.errors.MissingObjectException
import org.eclipse.jgit.revwalk.RevCommit
import org.eclipse.jgit.diff.DiffFormatter
import org.eclipse.jgit.treewalk.TreeWalk
import org.eclipse.jgit.revwalk.RevWalk
import org.eclipse.jgit.diff.RawTextComparator
import org.eclipse.jgit.util.io.DisabledOutputStream
import org.eclipse.jgit.lib.Repository
import org.eclipse.jgit.revwalk.RevSort
import org.eclipse.jgit.diff.DiffEntry.ChangeType
import org.eclipse.jgit.lib.FileMode
import org.eclipse.jgit.treewalk.filter.PathFilter
object JGitUtil {
/**
* Returns the repository information. It contains branch names and tag names.
*/
def getRepositoryInfo(owner: String, repository: String, servletContext: ServletContext): RepositoryInfo = {
val git = Git.open(getRepositoryDir(owner, repository))
RepositoryInfo(
owner, repository, "http://localhost:8080%s/git/%s/%s.git".format(servletContext.getContextPath, owner, repository),
// branches
git.branchList.call.toArray.map { ref =>
ref.asInstanceOf[Ref].getName.replaceFirst("^refs/heads/", "")
}.toList,
// tags
git.tagList.call.toArray.map { ref =>
ref.asInstanceOf[Ref].getName
}.toList
)
}
/**
* Get the branch name from the commit id.
*/
def getBranchNameFromCommitId(id: String, repositoryInfo: RepositoryInfo): String = {
repositoryInfo.branchList.find { branch =>
val git = Git.open(getBranchDir(repositoryInfo.owner, repositoryInfo.name, branch))
git.log.add(ObjectId.fromString(id)).call.iterator.hasNext
}.get
}
/**
* Returns the file list of the specified path.
*
* @param owner the repository owner
* @param repository the repository name
* @param revstr the branch name or commit id
* @param path the directory path (optional)
* @return HTML of the file list
*/
def getFileList(owner: String, repository: String, revision: String, path: String = "."): List[FileInfo] = {
val git = Git.open(getRepositoryDir(owner, repository))
val revWalk = new RevWalk(git.getRepository)
val objectId = git.getRepository.resolve(revision)
val revCommit = revWalk.parseCommit(objectId)
val treeWalk = new TreeWalk(git.getRepository)
treeWalk.addTree(revCommit.getTree)
if(path != "."){
treeWalk.setRecursive(true)
treeWalk.setFilter(PathFilter.create(path))
}
val list = new scala.collection.mutable.ListBuffer[FileInfo]
while (treeWalk.next()) {
val fileCommit = JGitUtil.getLatestCommitFromPath(git.getRepository, treeWalk.getPathString, revision)
list.append(FileInfo(
treeWalk.getObjectId(0),
treeWalk.getFileMode(0) == FileMode.TREE,
treeWalk.getNameString,
fileCommit.getCommitterIdent.getWhen,
fileCommit.getShortMessage,
fileCommit.getCommitterIdent.getName)
)
}
treeWalk.release
revWalk.dispose
list.toList.sortWith { (file1, file2) => (file1.isDirectory, file2.isDirectory) match {
case (true , false) => true
case (false, true ) => false
case _ => file1.name.compareTo(file2.name) < 0
}}
}
/**
* Returns the latest RevCommit of the specified path.
*/
def getLatestCommitFromPath(repository: Repository, path: String, revision: String): RevCommit = {
val revWalk = new RevWalk(repository)
revWalk.markStart(revWalk.parseCommit(repository.resolve(revision)))
revWalk.sort(RevSort.REVERSE);
val i = revWalk.iterator
// TODO DON'T use var!
var result: RevCommit = null
while(i.hasNext){
val commit = i.next
if(commit.getParentCount == 0){
// Initial commit
val treeWalk = new TreeWalk(repository)
treeWalk.reset()
treeWalk.setRecursive(true)
treeWalk.addTree(commit.getTree)
while (treeWalk.next && result == null) {
if(treeWalk.getPathString.startsWith(path)){
result = commit
}
}
treeWalk.release
} else {
val parent = revWalk.parseCommit(commit.getParent(0).getId())
val df = new DiffFormatter(DisabledOutputStream.INSTANCE)
df.setRepository(repository)
df.setDiffComparator(RawTextComparator.DEFAULT)
df.setDetectRenames(true)
val diffs = df.scan(parent.getTree(), commit.getTree)
val find = diffs.asScala.find { diff =>
val objectId = diff.getNewId.name
(diff.getChangeType != ChangeType.DELETE && diff.getNewPath.startsWith(path))
}
if(find != None){
result = commit
}
}
revWalk.release
}
result
}
/**
* Get object content of the given id as String from the Git repository.
*
* @param git the Git object
* @param id the object id
* @param large if true then returns None for the large file
* @return the object or None if object does not exist
*/
def getContent(git: Git, id: ObjectId, large: Boolean): Option[Array[Byte]] = try {
val loader = git.getRepository.getObjectDatabase.open(id)
if(large == false && FileTypeUtil.isLarge(loader.getSize)){
None
} else {
Some(git.getRepository.getObjectDatabase.open(id).getBytes)
}
} catch {
case e: MissingObjectException => None
}
}