diff --git a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala
index aaa848c..a6c8793 100644
--- a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala
+++ b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala
@@ -928,8 +928,9 @@
defining(JGitUtil.getRevCommitFromId(git, objectId)) { revCommit =>
val lastModifiedCommit =
if (path == ".") revCommit else JGitUtil.getLastModifiedCommit(git, revCommit, path)
+ val commitCount = JGitUtil.getCommitCount(git, lastModifiedCommit.getName)
// get files
- val files = JGitUtil.getFileList(git, revision, path, context.settings.baseUrl)
+ val files = JGitUtil.getFileList(git, revision, path, context.settings.baseUrl, commitCount)
val parentPath = if (path == ".") Nil else path.split("/").toList
// process README.md or README.markdown
val readme = files
@@ -950,7 +951,7 @@
repository,
if (path == ".") Nil else path.split("/").toList, // current path
new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit
- JGitUtil.getCommitCount(git, lastModifiedCommit.getName),
+ commitCount,
files,
readme,
hasDeveloperRole(repository.owner, repository.name, context.loginAccount),
diff --git a/src/main/scala/gitbucket/core/util/JGitUtil.scala b/src/main/scala/gitbucket/core/util/JGitUtil.scala
index 8f1df6d..fc1663e 100644
--- a/src/main/scala/gitbucket/core/util/JGitUtil.scala
+++ b/src/main/scala/gitbucket/core/util/JGitUtil.scala
@@ -290,6 +290,11 @@
.entryCapacity(10000)
.build()
+ private val objectCommitCache = new Cache2kBuilder[ObjectId, RevCommit]() {}
+ .name("object-commit")
+ .entryCapacity(10000)
+ .build()
+
def removeCache(git: Git): Unit = {
val dir = git.getRepository.getDirectory
val keyPrefix = dir.getAbsolutePath + "@"
@@ -368,9 +373,16 @@
* @param revision the branch name or commit id
* @param path the directory path (optional)
* @param baseUrl the base url of GitBucket instance. This parameter is used to generate links of submodules (optional)
- * @return HTML of the file list
+ * @param commitCount the number of commit of this repository (optional). If this number is greater than threshold, the commit info is cached in memory.
+ * @return The list of files in the specified directory. If the number of files are greater than threshold, the returned file list won't include the commit info.
*/
- def getFileList(git: Git, revision: String, path: String = ".", baseUrl: Option[String] = None): List[FileInfo] = {
+ def getFileList(
+ git: Git,
+ revision: String,
+ path: String = ".",
+ baseUrl: Option[String] = None,
+ commitCount: Int = 0
+ ): List[FileInfo] = {
Using.resource(new RevWalk(git.getRepository)) { revWalk =>
val objectId = git.getRepository.resolve(revision)
if (objectId == null) return Nil
@@ -388,104 +400,85 @@
Using.resource(treeWalk)(f)
}
}
+
@tailrec
def simplifyPath(
- tuple: (ObjectId, FileMode, String, String, Option[String], RevCommit)
- ): (ObjectId, FileMode, String, String, Option[String], RevCommit) = tuple match {
- case (oid, FileMode.TREE, name, path, _, commit) =>
- (Using.resource(new TreeWalk(git.getRepository)) { walk =>
- walk.addTree(oid)
- // single tree child, or None
- if (walk.next() && walk.getFileMode(0) == FileMode.TREE) {
- Some(
- (
- walk.getObjectId(0),
- walk.getFileMode(0),
- name + "/" + walk.getNameString,
- path + "/" + walk.getNameString,
- None,
- commit
- )
- ).filterNot(_ => walk.next())
- } else {
- None
+ tuple: (ObjectId, FileMode, String, String, Option[String], Option[RevCommit])
+ ): (ObjectId, FileMode, String, String, Option[String], Option[RevCommit]) =
+ tuple match {
+ case (oid, FileMode.TREE, name, path, _, commit) =>
+ (Using.resource(new TreeWalk(git.getRepository)) { walk =>
+ walk.addTree(oid)
+ // single tree child, or None
+ if (walk.next() && walk.getFileMode(0) == FileMode.TREE) {
+ Some(
+ (
+ walk.getObjectId(0),
+ walk.getFileMode(0),
+ name + "/" + walk.getNameString,
+ path + "/" + walk.getNameString,
+ None,
+ commit
+ )
+ ).filterNot(_ => walk.next())
+ } else {
+ None
+ }
+ }) match {
+ case Some(child) => simplifyPath(child)
+ case _ => tuple
}
- }) match {
- case Some(child) => simplifyPath(child)
- case _ => tuple
- }
- case _ => tuple
- }
+ case _ => tuple
+ }
- def tupleAdd(tuple: (ObjectId, FileMode, String, String, Option[String]), rev: RevCommit) = tuple match {
- case (oid, fmode, name, path, opt) => (oid, fmode, name, path, opt, rev)
- }
-
- @tailrec
- def findLastCommits(
- result: List[(ObjectId, FileMode, String, String, Option[String], RevCommit)],
- restList: List[((ObjectId, FileMode, String, String, Option[String]), Map[RevCommit, RevCommit])],
- revIterator: java.util.Iterator[RevCommit]
- ): List[(ObjectId, FileMode, String, String, Option[String], RevCommit)] = {
- if (restList.isEmpty) {
- result
- } else if (!revIterator.hasNext) { // maybe, revCommit has only 1 log. other case, restList be empty
- result ++ restList.map { case (tuple, map) => tupleAdd(tuple, map.values.headOption.getOrElse(revCommit)) }
- } else {
- val newCommit = revIterator.next
- val (thisTimeChecks, skips) = restList.partition {
- case (tuple, parentsMap) => parentsMap.contains(newCommit)
- }
- if (thisTimeChecks.isEmpty) {
- findLastCommits(result, restList, revIterator)
- } else {
- var nextRest = skips
- var nextResult = result
- // Map[(name, oid), (tuple, parentsMap)]
- val rest = scala.collection.mutable.Map(thisTimeChecks.map { t =>
- (t._1._3 -> t._1._1) -> t
- }: _*)
- lazy val newParentsMap = newCommit.getParents.map(_ -> newCommit).toMap
- useTreeWalk(newCommit) { walk =>
- while (walk.next) {
- rest.remove(walk.getNameString -> walk.getObjectId(0)).foreach {
- case (tuple, _) =>
- if (newParentsMap.isEmpty) {
- nextResult +:= tupleAdd(tuple, newCommit)
- } else {
- nextRest +:= tuple -> newParentsMap
- }
- }
+ def appendLastCommits(
+ fileList: List[(ObjectId, FileMode, String, String, Option[String])]
+ ): List[(ObjectId, FileMode, String, String, Option[String], Option[RevCommit])] = {
+ fileList.map {
+ case (id, mode, name, path, opt) =>
+ // Don't attempt to get the last commit if the number of files is very large.
+ if (fileList.size >= 100) {
+ (id, mode, name, path, opt, None)
+ } else if (commitCount < 10000) {
+ val i = git
+ (id, mode, name, path, opt, Some(getCommit(path)))
+ } else {
+ val cached = objectCommitCache.getEntry(id)
+ if (cached == null) {
+ val commit = getCommit(path)
+ objectCommitCache.put(id, commit)
+ (id, mode, name, path, opt, Some(commit))
+ } else {
+ (id, mode, name, path, opt, Some(cached.getValue))
}
}
- rest.values.foreach {
- case (tuple, parentsMap) =>
- val restParentsMap = parentsMap - newCommit
- if (restParentsMap.isEmpty) {
- nextResult +:= tupleAdd(tuple, parentsMap(newCommit))
- } else {
- nextRest +:= tuple -> restParentsMap
- }
- }
- findLastCommits(nextResult, nextRest, revIterator)
- }
}
}
+ def getCommit(path: String): RevCommit = {
+ git
+ .log()
+ .addPath(path)
+ .add(revCommit)
+ .setMaxCount(1)
+ .call()
+ .iterator()
+ .next()
+ }
+
var fileList: List[(ObjectId, FileMode, String, String, Option[String])] = Nil
useTreeWalk(revCommit) { treeWalk =>
while (treeWalk.next()) {
val linkUrl = if (treeWalk.getFileMode(0) == FileMode.GITLINK) {
getSubmodules(git, revCommit.getTree, baseUrl).find(_.path == treeWalk.getPathString).map(_.viewerUrl)
} else None
- fileList +:= (treeWalk.getObjectId(0), treeWalk.getFileMode(0), treeWalk.getNameString, treeWalk.getPathString, linkUrl)
+ fileList +:= (treeWalk.getObjectId(0), treeWalk.getFileMode(
+ 0
+ ), treeWalk.getNameString, treeWalk.getPathString, linkUrl)
}
}
- revWalk.markStart(revCommit)
- val it = revWalk.iterator
- val lastCommit = it.next
- val nextParentsMap = Option(lastCommit).map(_.getParents.map(_ -> lastCommit).toMap).getOrElse(Map())
- findLastCommits(List.empty, fileList.map(a => a -> nextParentsMap), it)
+
+ appendLastCommits(fileList)
.map(simplifyPath)
.map {
case (objectId, fileMode, name, path, linkUrl, commit) =>
@@ -494,11 +487,14 @@
fileMode == FileMode.TREE || fileMode == FileMode.GITLINK,
name,
path,
- getSummaryMessage(commit.getFullMessage, commit.getShortMessage),
- commit.getName,
- commit.getAuthorIdent.getWhen,
- commit.getAuthorIdent.getName,
- commit.getAuthorIdent.getEmailAddress,
+ getSummaryMessage(
+ commit.map(_.getFullMessage).getOrElse(""),
+ commit.map(_.getShortMessage).getOrElse("")
+ ),
+ commit.map(_.getName).getOrElse(""),
+ commit.map(_.getAuthorIdent.getWhen).orNull,
+ commit.map(_.getAuthorIdent.getName).getOrElse(""),
+ commit.map(_.getAuthorIdent.getEmailAddress).getOrElse(""),
linkUrl
)
}
@@ -1004,11 +1000,12 @@
*/
def getContentFromPath(git: Git, revTree: RevTree, path: String, fetchLargeFile: Boolean): Option[Array[Byte]] = {
@scala.annotation.tailrec
- def getPathObjectId(path: String, walk: TreeWalk): Option[ObjectId] = walk.next match {
- case true if (walk.getPathString == path) => Some(walk.getObjectId(0))
- case true => getPathObjectId(path, walk)
- case false => None
- }
+ def getPathObjectId(path: String, walk: TreeWalk): Option[ObjectId] =
+ walk.next match {
+ case true if (walk.getPathString == path) => Some(walk.getObjectId(0))
+ case true => getPathObjectId(path, walk)
+ case false => None
+ }
Using.resource(new TreeWalk(git.getRepository)) { treeWalk =>
treeWalk.addTree(revTree)
diff --git a/src/main/twirl/gitbucket/core/helper/datetimeago.scala.html b/src/main/twirl/gitbucket/core/helper/datetimeago.scala.html
index 6d33d1c..3031fd2 100644
--- a/src/main/twirl/gitbucket/core/helper/datetimeago.scala.html
+++ b/src/main/twirl/gitbucket/core/helper/datetimeago.scala.html
@@ -1,10 +1,12 @@
@(latestUpdatedDate: java.util.Date,
recentOnly: Boolean = true)
@import gitbucket.core.view.helpers
-
- @if(recentOnly){
- @helpers.datetimeAgoRecentOnly(latestUpdatedDate)
- } else {
- @helpers.datetimeAgo(latestUpdatedDate)
- }
-
+@if(latestUpdatedDate != null){
+
+ @if(recentOnly){
+ @helpers.datetimeAgoRecentOnly(latestUpdatedDate)
+ } else {
+ @helpers.datetimeAgo(latestUpdatedDate)
+ }
+
+}