diff --git a/src/main/scala/util/JGitUtil.scala b/src/main/scala/util/JGitUtil.scala index 75cf83c..6cdca44 100644 --- a/src/main/scala/util/JGitUtil.scala +++ b/src/main/scala/util/JGitUtil.scala @@ -1,235 +1,235 @@ -package util - -import org.eclipse.jgit.api.Git -import app.{RepositoryInfo, FileInfo, CommitInfo, DiffInfo} -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 -import org.eclipse.jgit.treewalk.CanonicalTreeParser - -/** +package util + +import org.eclipse.jgit.api.Git +import app.{RepositoryInfo, FileInfo, CommitInfo, DiffInfo} +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 +import org.eclipse.jgit.treewalk.CanonicalTreeParser + +/** * Provides complex JGit operations. - */ -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 - ) - } - - /** - * Returns the file list of the specified path. - * - * @param git the Git object - * @param revision the branch name or commit id - * @param path the directory path (optional) - * @return HTML of the file list - */ - def getFileList(git: Git, revision: String, path: String = "."): List[FileInfo] = { - 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, 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 commit list of the specified branch. - * - * @param git the Git object - * @param revision the branch name or commit id - * @param page the page number (1-) - * @return a tuple of the commit list and whether has next - */ - def getCommitLog(git: Git, revision: String, page: Int): (List[CommitInfo], Boolean) = { - @scala.annotation.tailrec - def getCommitLog(i: java.util.Iterator[RevCommit], count: Int, logs: List[CommitInfo]): (List[CommitInfo], Boolean) = - i.hasNext match { - case true if(logs.size < 30) => getCommitLog(i, count + 1, if((page - 1) * 30 < count) logs :+ new CommitInfo(i.next) else logs) - case _ => (logs, i.hasNext) - } - - val revWalk = new RevWalk(git.getRepository) - revWalk.markStart(revWalk.parseCommit(git.getRepository.resolve(revision))) - - val commits = getCommitLog(revWalk.iterator, 0, Nil) - revWalk.release - - commits - } - - /** - * Returns the latest RevCommit of the specified path. + */ +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 + ) + } + + /** + * Returns the file list of the specified path. * - * @param git the Git object - * @param path the path - * @param revision the branch name or commit id - * @return the latest commit - */ - def getLatestCommitFromPath(git: Git, path: String, revision: String): RevCommit = { - val revWalk = new RevWalk(git.getRepository) - revWalk.markStart(revWalk.parseCommit(git.getRepository.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(git.getRepository) - 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(git.getRepository) - 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 - } - - def getDiffs(git: Git, id: String): List[DiffInfo] = { - @scala.annotation.tailrec - def getCommitLog(i: java.util.Iterator[RevCommit], logs: List[RevCommit]): List[RevCommit] = - i.hasNext match { - case true if(logs.size < 2) => getCommitLog(i, logs :+ i.next) - case _ => logs - } - - val revWalk = new RevWalk(git.getRepository) - revWalk.markStart(revWalk.parseCommit(git.getRepository.resolve(id))) - - val commits = getCommitLog(revWalk.iterator, Nil) - revWalk.release - - val revCommit = commits(0) - - if(commits.length >= 2){ - // not initial commit - val oldCommit = commits(1) - - // get diff between specified commit and its previous commit - val reader = git.getRepository.newObjectReader - - val oldTreeIter = new CanonicalTreeParser - oldTreeIter.reset(reader, git.getRepository.resolve(oldCommit.name + "^{tree}")) - - val newTreeIter = new CanonicalTreeParser - newTreeIter.reset(reader, git.getRepository.resolve(id + "^{tree}")) - - import scala.collection.JavaConverters._ - git.diff.setNewTree(newTreeIter).setOldTree(oldTreeIter).call.asScala.map { diff => - DiffInfo(diff.getChangeType, diff.getOldPath, diff.getNewPath, - JGitUtil.getContent(git, diff.getOldId.toObjectId, false).map(new String(_, "UTF-8")), - JGitUtil.getContent(git, diff.getNewId.toObjectId, false).map(new String(_, "UTF-8"))) - }.toList - } else { - // initial commit - val walk = new TreeWalk(git.getRepository) - walk.addTree(revCommit.getTree) - val buffer = new scala.collection.mutable.ListBuffer[DiffInfo]() - while(walk.next){ - buffer.append(DiffInfo(ChangeType.ADD, null, walk.getPathString, None, - JGitUtil.getContent(git, walk.getObjectId(0), false).map(new String(_, "UTF-8")))) - } - walk.release - buffer.toList - } - } - + * @param git the Git object + * @param revision the branch name or commit id + * @param path the directory path (optional) + * @return HTML of the file list + */ + def getFileList(git: Git, revision: String, path: String = "."): List[FileInfo] = { + 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, 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 commit list of the specified branch. + * + * @param git the Git object + * @param revision the branch name or commit id + * @param page the page number (1-) + * @return a tuple of the commit list and whether has next + */ + def getCommitLog(git: Git, revision: String, page: Int): (List[CommitInfo], Boolean) = { + @scala.annotation.tailrec + def getCommitLog(i: java.util.Iterator[RevCommit], count: Int, logs: List[CommitInfo]): (List[CommitInfo], Boolean) = + i.hasNext match { + case true if(logs.size < 30) => getCommitLog(i, count + 1, if((page - 1) * 30 < count) logs :+ new CommitInfo(i.next) else logs) + case _ => (logs, i.hasNext) + } + + val revWalk = new RevWalk(git.getRepository) + revWalk.markStart(revWalk.parseCommit(git.getRepository.resolve(revision))) + + val commits = getCommitLog(revWalk.iterator, 0, Nil) + revWalk.release + + commits + } + + /** + * Returns the latest RevCommit of the specified path. + * + * @param git the Git object + * @param path the path + * @param revision the branch name or commit id + * @return the latest commit + */ + def getLatestCommitFromPath(git: Git, path: String, revision: String): RevCommit = { + val revWalk = new RevWalk(git.getRepository) + revWalk.markStart(revWalk.parseCommit(git.getRepository.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(git.getRepository) + 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(git.getRepository) + 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 false 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 + } + + def getDiffs(git: Git, id: String): List[DiffInfo] = { + @scala.annotation.tailrec + def getCommitLog(i: java.util.Iterator[RevCommit], logs: List[RevCommit]): List[RevCommit] = + i.hasNext match { + case true if(logs.size < 2) => getCommitLog(i, logs :+ i.next) + case _ => logs + } + + val revWalk = new RevWalk(git.getRepository) + revWalk.markStart(revWalk.parseCommit(git.getRepository.resolve(id))) + + val commits = getCommitLog(revWalk.iterator, Nil) + revWalk.release + + val revCommit = commits(0) + + if(commits.length >= 2){ + // not initial commit + val oldCommit = commits(1) + + // get diff between specified commit and its previous commit + val reader = git.getRepository.newObjectReader + + val oldTreeIter = new CanonicalTreeParser + oldTreeIter.reset(reader, git.getRepository.resolve(oldCommit.name + "^{tree}")) + + val newTreeIter = new CanonicalTreeParser + newTreeIter.reset(reader, git.getRepository.resolve(id + "^{tree}")) + + import scala.collection.JavaConverters._ + git.diff.setNewTree(newTreeIter).setOldTree(oldTreeIter).call.asScala.map { diff => + DiffInfo(diff.getChangeType, diff.getOldPath, diff.getNewPath, + JGitUtil.getContent(git, diff.getOldId.toObjectId, false).map(new String(_, "UTF-8")), + JGitUtil.getContent(git, diff.getNewId.toObjectId, false).map(new String(_, "UTF-8"))) + }.toList + } else { + // initial commit + val walk = new TreeWalk(git.getRepository) + walk.addTree(revCommit.getTree) + val buffer = new scala.collection.mutable.ListBuffer[DiffInfo]() + while(walk.next){ + buffer.append(DiffInfo(ChangeType.ADD, null, walk.getPathString, None, + JGitUtil.getContent(git, walk.getObjectId(0), false).map(new String(_, "UTF-8")))) + } + walk.release + buffer.toList + } + } + } \ No newline at end of file