mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-02 11:36:05 +01:00
Add paginator and separate search code in controller to service.
This commit is contained in:
@@ -1,21 +1,16 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import util._
|
import util._
|
||||||
import util.Directory._
|
|
||||||
import service._
|
import service._
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import jp.sf.amateras.scalatra.forms._
|
||||||
import org.eclipse.jgit.treewalk.TreeWalk
|
|
||||||
import org.eclipse.jgit.revwalk.RevWalk
|
|
||||||
import scala.collection.mutable.ListBuffer
|
|
||||||
import org.eclipse.jgit.lib.FileMode
|
|
||||||
import model.Issue
|
|
||||||
|
|
||||||
class IndexController extends IndexControllerBase
|
class IndexController extends IndexControllerBase
|
||||||
with RepositoryService with AccountService with SystemSettingsService with ActivityService with IssuesService
|
with RepositoryService with AccountService with SystemSettingsService with ActivityService
|
||||||
|
with RepositorySearchService with IssuesService
|
||||||
with ReferrerAuthenticator
|
with ReferrerAuthenticator
|
||||||
|
|
||||||
trait IndexControllerBase extends ControllerBase { self: RepositoryService
|
trait IndexControllerBase extends ControllerBase { self: RepositoryService
|
||||||
with SystemSettingsService with ActivityService with IssuesService
|
with SystemSettingsService with ActivityService with RepositorySearchService
|
||||||
with ReferrerAuthenticator =>
|
with ReferrerAuthenticator =>
|
||||||
|
|
||||||
val searchForm = mapping(
|
val searchForm = mapping(
|
||||||
@@ -41,7 +36,6 @@ trait IndexControllerBase extends ControllerBase { self: RepositoryService
|
|||||||
}
|
}
|
||||||
|
|
||||||
get("/:owner/:repository/search")(referrersOnly { repository =>
|
get("/:owner/:repository/search")(referrersOnly { repository =>
|
||||||
import RepositorySearch._
|
|
||||||
val query = params("q").trim
|
val query = params("q").trim
|
||||||
val target = params.getOrElse("type", "code")
|
val target = params.getOrElse("type", "code")
|
||||||
val page = try {
|
val page = try {
|
||||||
@@ -51,114 +45,17 @@ trait IndexControllerBase extends ControllerBase { self: RepositoryService
|
|||||||
case e: NumberFormatException => 1
|
case e: NumberFormatException => 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val SearchResult(files, issues) = searchRepository(repository.owner, repository.name, query)
|
|
||||||
|
|
||||||
target.toLowerCase match {
|
target.toLowerCase match {
|
||||||
case "issue" =>
|
case "issue" => search.html.issues(
|
||||||
search.html.issues(issues.map { case (issue, commentCount, content) =>
|
searchIssues(repository.owner, repository.name, query),
|
||||||
IssueSearchResult(
|
countFiles(repository.owner, repository.name, query),
|
||||||
issue.issueId,
|
query, page, repository)
|
||||||
issue.title,
|
|
||||||
issue.openedUserName,
|
|
||||||
issue.registeredDate,
|
|
||||||
commentCount,
|
|
||||||
getHighlightText(content, query)._1)
|
|
||||||
}, files.size, query, page, repository)
|
|
||||||
case _ =>
|
|
||||||
JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git =>
|
|
||||||
val commits = JGitUtil.getLatestCommitFromPaths(git, files.toList.map(_._1), "HEAD")
|
|
||||||
|
|
||||||
search.html.code(files.toList.map { case (path, text) =>
|
case _ => search.html.code(
|
||||||
val (highlightText, lineNumber) = getHighlightText(text, query)
|
searchFiles(repository.owner, repository.name, query),
|
||||||
FileSearchResult(
|
countIssues(repository.owner, repository.name, query),
|
||||||
path,
|
query, page, repository)
|
||||||
commits(path).getCommitterIdent.getWhen,
|
|
||||||
highlightText,
|
|
||||||
lineNumber)
|
|
||||||
}, issues.size, query, page, repository)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
case class SearchResult(
|
|
||||||
files: List[(String, String)],
|
|
||||||
issues: List[(Issue, Int, String)]
|
|
||||||
)
|
|
||||||
|
|
||||||
def searchRepository(owner: String, repository: String, query: String): SearchResult = {
|
|
||||||
val issues = if(query.isEmpty) Nil else searchIssuesByKeyword(owner, repository, query)
|
|
||||||
val files = if(query.isEmpty) Nil else searchRepositoryFiles(owner, repository, query)
|
|
||||||
SearchResult(files, issues)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def searchRepositoryFiles(owner: String, repository: String, query: String): List[(String, String)] = {
|
|
||||||
JGitUtil.withGit(getRepositoryDir(owner, repository)){ git =>
|
|
||||||
val revWalk = new RevWalk(git.getRepository)
|
|
||||||
val objectId = git.getRepository.resolve("HEAD")
|
|
||||||
val revCommit = revWalk.parseCommit(objectId)
|
|
||||||
val treeWalk = new TreeWalk(git.getRepository)
|
|
||||||
treeWalk.setRecursive(true)
|
|
||||||
treeWalk.addTree(revCommit.getTree)
|
|
||||||
|
|
||||||
val keywords = StringUtil.splitWords(query.toLowerCase)
|
|
||||||
val list = new ListBuffer[(String, String)]
|
|
||||||
|
|
||||||
while (treeWalk.next()) {
|
|
||||||
if(treeWalk.getFileMode(0) != FileMode.TREE){
|
|
||||||
JGitUtil.getContent(git, treeWalk.getObjectId(0), false).foreach { bytes =>
|
|
||||||
if(FileUtil.isText(bytes)){
|
|
||||||
val text = new String(bytes, "UTF-8")
|
|
||||||
val lowerText = text.toLowerCase
|
|
||||||
val indices = keywords.map(lowerText.indexOf _)
|
|
||||||
if(!indices.exists(_ < 0)){
|
|
||||||
list.append((treeWalk.getPathString, text))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
treeWalk.release
|
|
||||||
revWalk.release
|
|
||||||
|
|
||||||
list.toList
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private def getHighlightText(content: String, query: String): (String, Int) = {
|
|
||||||
val keywords = StringUtil.splitWords(query.toLowerCase)
|
|
||||||
val lowerText = content.toLowerCase
|
|
||||||
val indices = keywords.map(lowerText.indexOf _)
|
|
||||||
|
|
||||||
if(!indices.exists(_ < 0)){
|
|
||||||
val lineNumber = content.substring(0, indices.min).split("\n").size - 1
|
|
||||||
val highlightText = StringUtil.escapeHtml(content.split("\n").drop(lineNumber).take(5).mkString("\n"))
|
|
||||||
.replaceAll("(?i)(" + keywords.map("\\Q" + _ + "\\E").mkString("|") + ")",
|
|
||||||
"<span style=\"background-color: #ffff88;;\">$1</span>")
|
|
||||||
(highlightText, lineNumber + 1)
|
|
||||||
} else {
|
|
||||||
(content.split("\n").take(5).mkString("\n"), 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case class IssueSearchResult(
|
|
||||||
issueId: Int,
|
|
||||||
title: String,
|
|
||||||
openedUserName: String,
|
|
||||||
registeredDate: java.util.Date,
|
|
||||||
commentCount: Int,
|
|
||||||
highlightText: String)
|
|
||||||
|
|
||||||
case class FileSearchResult(
|
|
||||||
path: String,
|
|
||||||
lastModified: java.util.Date,
|
|
||||||
highlightText: String,
|
|
||||||
highlightLineNumber: Int)
|
|
||||||
|
|
||||||
object RepositorySearch extends IssuesService {
|
|
||||||
val CodeLimit = 10
|
|
||||||
val IssueLimit = 10
|
|
||||||
}
|
|
||||||
|
|||||||
121
src/main/scala/service/RepositorySearchService.scala
Normal file
121
src/main/scala/service/RepositorySearchService.scala
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import model.Issue
|
||||||
|
import util.{FileUtil, StringUtil, JGitUtil}
|
||||||
|
import util.Directory._
|
||||||
|
import model.Issue
|
||||||
|
import org.eclipse.jgit.revwalk.RevWalk
|
||||||
|
import org.eclipse.jgit.treewalk.TreeWalk
|
||||||
|
import scala.collection.mutable.ListBuffer
|
||||||
|
import org.eclipse.jgit.lib.FileMode
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
|
||||||
|
trait RepositorySearchService { self: IssuesService =>
|
||||||
|
import RepositorySearchService._
|
||||||
|
|
||||||
|
def countIssues(owner: String, repository: String, query: String): Int =
|
||||||
|
searchIssuesByKeyword(owner, repository, query).length
|
||||||
|
|
||||||
|
def searchIssues(owner: String, repository: String, query: String): List[IssueSearchResult] =
|
||||||
|
searchIssuesByKeyword(owner, repository, query).map { case (issue, commentCount, content) =>
|
||||||
|
IssueSearchResult(
|
||||||
|
issue.issueId,
|
||||||
|
issue.title,
|
||||||
|
issue.openedUserName,
|
||||||
|
issue.registeredDate,
|
||||||
|
commentCount,
|
||||||
|
getHighlightText(content, query)._1)
|
||||||
|
}
|
||||||
|
|
||||||
|
def countFiles(owner: String, repository: String, query: String): Int =
|
||||||
|
JGitUtil.withGit(getRepositoryDir(owner, repository)){ git =>
|
||||||
|
searchRepositoryFiles(git, query).length
|
||||||
|
}
|
||||||
|
|
||||||
|
def searchFiles(owner: String, repository: String, query: String): List[FileSearchResult] =
|
||||||
|
JGitUtil.withGit(getRepositoryDir(owner, repository)){ git =>
|
||||||
|
val files = searchRepositoryFiles(git, query)
|
||||||
|
val commits = JGitUtil.getLatestCommitFromPaths(git, files.toList.map(_._1), "HEAD")
|
||||||
|
files.map { case (path, text) =>
|
||||||
|
val (highlightText, lineNumber) = getHighlightText(text, query)
|
||||||
|
FileSearchResult(
|
||||||
|
path,
|
||||||
|
commits(path).getCommitterIdent.getWhen,
|
||||||
|
highlightText,
|
||||||
|
lineNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def searchRepositoryFiles(git: Git, query: String): List[(String, String)] = {
|
||||||
|
val revWalk = new RevWalk(git.getRepository)
|
||||||
|
val objectId = git.getRepository.resolve("HEAD")
|
||||||
|
val revCommit = revWalk.parseCommit(objectId)
|
||||||
|
val treeWalk = new TreeWalk(git.getRepository)
|
||||||
|
treeWalk.setRecursive(true)
|
||||||
|
treeWalk.addTree(revCommit.getTree)
|
||||||
|
|
||||||
|
val keywords = StringUtil.splitWords(query.toLowerCase)
|
||||||
|
val list = new ListBuffer[(String, String)]
|
||||||
|
|
||||||
|
while (treeWalk.next()) {
|
||||||
|
if(treeWalk.getFileMode(0) != FileMode.TREE){
|
||||||
|
JGitUtil.getContent(git, treeWalk.getObjectId(0), false).foreach { bytes =>
|
||||||
|
if(FileUtil.isText(bytes)){
|
||||||
|
val text = new String(bytes, "UTF-8")
|
||||||
|
val lowerText = text.toLowerCase
|
||||||
|
val indices = keywords.map(lowerText.indexOf _)
|
||||||
|
if(!indices.exists(_ < 0)){
|
||||||
|
list.append((treeWalk.getPathString, text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
treeWalk.release
|
||||||
|
revWalk.release
|
||||||
|
|
||||||
|
list.toList
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object RepositorySearchService {
|
||||||
|
|
||||||
|
val CodeLimit = 10
|
||||||
|
val IssueLimit = 10
|
||||||
|
|
||||||
|
def getHighlightText(content: String, query: String): (String, Int) = {
|
||||||
|
val keywords = StringUtil.splitWords(query.toLowerCase)
|
||||||
|
val lowerText = content.toLowerCase
|
||||||
|
val indices = keywords.map(lowerText.indexOf _)
|
||||||
|
|
||||||
|
if(!indices.exists(_ < 0)){
|
||||||
|
val lineNumber = content.substring(0, indices.min).split("\n").size - 1
|
||||||
|
val highlightText = StringUtil.escapeHtml(content.split("\n").drop(lineNumber).take(5).mkString("\n"))
|
||||||
|
.replaceAll("(?i)(" + keywords.map("\\Q" + _ + "\\E").mkString("|") + ")",
|
||||||
|
"<span style=\"background-color: #ffff88;;\">$1</span>")
|
||||||
|
(highlightText, lineNumber + 1)
|
||||||
|
} else {
|
||||||
|
(content.split("\n").take(5).mkString("\n"), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class SearchResult(
|
||||||
|
files : List[(String, String)],
|
||||||
|
issues: List[(Issue, Int, String)])
|
||||||
|
|
||||||
|
case class IssueSearchResult(
|
||||||
|
issueId: Int,
|
||||||
|
title: String,
|
||||||
|
openedUserName: String,
|
||||||
|
registeredDate: java.util.Date,
|
||||||
|
commentCount: Int,
|
||||||
|
highlightText: String)
|
||||||
|
|
||||||
|
case class FileSearchResult(
|
||||||
|
path: String,
|
||||||
|
lastModified: java.util.Date,
|
||||||
|
highlightText: String,
|
||||||
|
highlightLineNumber: Int)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
@(files: List[app.FileSearchResult],
|
@(files: List[service.RepositorySearchService.FileSearchResult],
|
||||||
issueCount: Int,
|
issueCount: Int,
|
||||||
query: String,
|
query: String,
|
||||||
page: Int,
|
page: Int,
|
||||||
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
|
@import service.RepositorySearchService._
|
||||||
@html.main("Search Results", Some(repository)){
|
@html.main("Search Results", Some(repository)){
|
||||||
@menu("code", files.size, issueCount, query, repository){
|
@menu("code", files.size, issueCount, query, repository){
|
||||||
@if(files.isEmpty){
|
@if(files.isEmpty){
|
||||||
@@ -12,14 +13,14 @@
|
|||||||
} else {
|
} else {
|
||||||
<h4>We've found @files.size code @plural(files.size, "result")</h4>
|
<h4>We've found @files.size code @plural(files.size, "result")</h4>
|
||||||
}
|
}
|
||||||
@files.drop((page - 1) * app.RepositorySearch.CodeLimit).take(app.RepositorySearch.CodeLimit).map { file =>
|
@files.drop((page - 1) * CodeLimit).take(CodeLimit).map { file =>
|
||||||
<div>
|
<div>
|
||||||
<h5><a href="@url(repository)/blob/@repository.repository.defaultBranch/@file.path">@file.path</a></h5>
|
<h5><a href="@url(repository)/blob/@repository.repository.defaultBranch/@file.path">@file.path</a></h5>
|
||||||
<div class="small muted">Latest commit at @datetime(file.lastModified)</div>
|
<div class="small muted">Latest commit at @datetime(file.lastModified)</div>
|
||||||
<pre class="prettyprint linenums:@file.highlightLineNumber" style="padding-left: 25px;">@Html(file.highlightText)</pre>
|
<pre class="prettyprint linenums:@file.highlightLineNumber" style="padding-left: 25px;">@Html(file.highlightText)</pre>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@helper.html.paginator(page, files.size, app.RepositorySearch.CodeLimit, 10,
|
@helper.html.paginator(page, files.size, CodeLimit, 10,
|
||||||
s"${url(repository)}/search?q=${urlEncode(query)}&type=code")
|
s"${url(repository)}/search?q=${urlEncode(query)}&type=code")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
@(issues: List[app.IssueSearchResult],
|
@(issues: List[service.RepositorySearchService.IssueSearchResult],
|
||||||
fileCount: Int,
|
fileCount: Int,
|
||||||
query: String,
|
query: String,
|
||||||
page: Int,
|
page: Int,
|
||||||
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
|
@import service.RepositorySearchService._
|
||||||
@html.main("Search Results", Some(repository)){
|
@html.main("Search Results", Some(repository)){
|
||||||
@menu("issue", fileCount, issues.size, query, repository){
|
@menu("issue", fileCount, issues.size, query, repository){
|
||||||
@if(issues.isEmpty){
|
@if(issues.isEmpty){
|
||||||
@@ -12,7 +13,7 @@
|
|||||||
} else {
|
} else {
|
||||||
<h4>We've found @issues.size code @plural(issues.size, "result")</h4>
|
<h4>We've found @issues.size code @plural(issues.size, "result")</h4>
|
||||||
}
|
}
|
||||||
@issues.drop((page - 1) * app.RepositorySearch.IssueLimit).take(app.RepositorySearch.IssueLimit).map { issue =>
|
@issues.drop((page - 1) * IssueLimit).take(IssueLimit).map { issue =>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<div class="pull-right muted">#@issue.issueId</div>
|
<div class="pull-right muted">#@issue.issueId</div>
|
||||||
<h4 style="margin-top: 0px;"><a href="@url(repository)/issues/@issue.issueId">@issue.title</a></h4>
|
<h4 style="margin-top: 0px;"><a href="@url(repository)/issues/@issue.issueId">@issue.title</a></h4>
|
||||||
@@ -26,7 +27,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@helper.html.paginator(page, issues.size, app.RepositorySearch.IssueLimit, 10,
|
@helper.html.paginator(page, issues.size, IssueLimit, 10,
|
||||||
s"${url(repository)}/search?q=${urlEncode(query)}&type=issue")
|
s"${url(repository)}/search?q=${urlEncode(query)}&type=issue")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user