mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-01 11:06:06 +01:00
(refs #3)Issue search is temporary available.
This commit is contained in:
@@ -4,20 +4,18 @@ import util._
|
|||||||
import util.Directory._
|
import util.Directory._
|
||||||
import service._
|
import service._
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import jp.sf.amateras.scalatra.forms._
|
||||||
import org.eclipse.jgit.api.Git
|
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import org.eclipse.jgit.treewalk.TreeWalk
|
import org.eclipse.jgit.treewalk.TreeWalk
|
||||||
import org.eclipse.jgit.revwalk.RevWalk
|
import org.eclipse.jgit.revwalk.RevWalk
|
||||||
import scala.collection.mutable.ListBuffer
|
import scala.collection.mutable.ListBuffer
|
||||||
import org.eclipse.jgit.lib.FileMode
|
import org.eclipse.jgit.lib.FileMode
|
||||||
import java.util.regex.Pattern
|
|
||||||
|
|
||||||
class IndexController extends IndexControllerBase
|
class IndexController extends IndexControllerBase
|
||||||
with RepositoryService with AccountService with SystemSettingsService with ActivityService
|
with RepositoryService with AccountService with SystemSettingsService with ActivityService with IssuesService
|
||||||
with ReferrerAuthenticator
|
with ReferrerAuthenticator
|
||||||
|
|
||||||
trait IndexControllerBase extends ControllerBase { self: RepositoryService
|
trait IndexControllerBase extends ControllerBase { self: RepositoryService
|
||||||
with SystemSettingsService with ActivityService
|
with SystemSettingsService with ActivityService with IssuesService
|
||||||
with ReferrerAuthenticator =>
|
with ReferrerAuthenticator =>
|
||||||
|
|
||||||
val searchForm = mapping(
|
val searchForm = mapping(
|
||||||
@@ -44,21 +42,36 @@ trait IndexControllerBase extends ControllerBase { self: RepositoryService
|
|||||||
|
|
||||||
// TODO readable only
|
// TODO readable only
|
||||||
get("/:owner/:repository/search")(referrersOnly { repository =>
|
get("/:owner/:repository/search")(referrersOnly { repository =>
|
||||||
val owner = params("owner")
|
|
||||||
val name = params("repository")
|
|
||||||
val query = params("q")
|
val query = params("q")
|
||||||
val target = params.getOrElse("type", "Code")
|
val target = params.getOrElse("type", "Code")
|
||||||
|
|
||||||
target.toLowerCase match {
|
target.toLowerCase match {
|
||||||
case "issue" => {
|
case "issue" => {
|
||||||
// TODO search issue
|
// TODO search issue
|
||||||
|
val lowerQueries = query.toLowerCase.split("[ \\t ]+")
|
||||||
|
|
||||||
|
search.html.issues(queryIssues(repository.owner, repository.name, query).map { case (issue, commentCount, content) =>
|
||||||
|
val lowerText = content.toLowerCase
|
||||||
|
val indices = lowerQueries.map { lowerQuery =>
|
||||||
|
lowerText.indexOf(lowerQuery)
|
||||||
|
}
|
||||||
|
val highlightText = if(!indices.exists(_ < 0)){
|
||||||
|
val lineNumber = content.substring(0, indices.min).split("\n").size - 1
|
||||||
|
StringUtil.escapeHtml(content.split("\n").drop(lineNumber).take(5).mkString("\n"))
|
||||||
|
.replaceAll("(?i)(" + lowerQueries.map("\\Q" + _ + "\\E").mkString("|") + ")",
|
||||||
|
"<span style=\"background-color: yellow;\">$1</span>")
|
||||||
|
} else content.split("\n").take(5).mkString("\n")
|
||||||
|
|
||||||
|
IssueSearchResult(issue.issueId, issue.title, issue.openedUserName, issue.registeredDate, commentCount, highlightText)
|
||||||
|
}, query, repository)
|
||||||
|
|
||||||
}
|
}
|
||||||
case _ => {
|
case _ => {
|
||||||
JGitUtil.withGit(getRepositoryDir(owner, name)){ git =>
|
JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git =>
|
||||||
val revWalk = new RevWalk(git.getRepository)
|
val revWalk = new RevWalk(git.getRepository)
|
||||||
val objectId = git.getRepository.resolve("HEAD")
|
val objectId = git.getRepository.resolve("HEAD")
|
||||||
val revCommit = revWalk.parseCommit(objectId)
|
val revCommit = revWalk.parseCommit(objectId)
|
||||||
val treeWalk = new TreeWalk(git.getRepository)
|
val treeWalk = new TreeWalk(git.getRepository)
|
||||||
treeWalk.setRecursive(true)
|
treeWalk.setRecursive(true)
|
||||||
treeWalk.addTree(revCommit.getTree)
|
treeWalk.addTree(revCommit.getTree)
|
||||||
|
|
||||||
@@ -79,7 +92,7 @@ trait IndexControllerBase extends ControllerBase { self: RepositoryService
|
|||||||
.replaceAll("(?i)(" + lowerQueries.map("\\Q" + _ + "\\E").mkString("|") + ")",
|
.replaceAll("(?i)(" + lowerQueries.map("\\Q" + _ + "\\E").mkString("|") + ")",
|
||||||
"<span style=\"background-color: yellow;\">$1</span>")
|
"<span style=\"background-color: yellow;\">$1</span>")
|
||||||
list.append((treeWalk.getPathString, highlightText))
|
list.append((treeWalk.getPathString, highlightText))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,4 +120,8 @@ trait IndexControllerBase extends ControllerBase { self: RepositoryService
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
case class FileSearchResult(path: String, lastModified: java.util.Date, highlightText: String)
|
||||||
@@ -8,6 +8,7 @@ import Q.interpolation
|
|||||||
import model._
|
import model._
|
||||||
import util.StringUtil._
|
import util.StringUtil._
|
||||||
import util.Implicits._
|
import util.Implicits._
|
||||||
|
import scala.concurrent.duration.durationToPair
|
||||||
|
|
||||||
trait IssuesService {
|
trait IssuesService {
|
||||||
import IssuesService._
|
import IssuesService._
|
||||||
@@ -234,6 +235,44 @@ trait IssuesService {
|
|||||||
}
|
}
|
||||||
.update (closed, currentDate)
|
.update (closed, currentDate)
|
||||||
|
|
||||||
|
def queryIssues(owner: String, repository: String, query: String): List[(Issue, Int, String)] = {
|
||||||
|
val lowerQueries = query.toLowerCase.split("[ \\t ]+")
|
||||||
|
|
||||||
|
val issues = Query(Issues).filter { t =>
|
||||||
|
lowerQueries.map { query =>
|
||||||
|
(t.title.toLowerCase startsWith query) || (t.content.toLowerCase startsWith query)
|
||||||
|
} .reduceLeft { (a, b) =>
|
||||||
|
a && b
|
||||||
|
}
|
||||||
|
}.map { t => (t, 0, t.content) }
|
||||||
|
|
||||||
|
val comments = Query(IssueComments).innerJoin(Issues).on { case (t1, t2) =>
|
||||||
|
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
|
||||||
|
}.filter { case (t1, t2) =>
|
||||||
|
lowerQueries.map { query =>
|
||||||
|
t1.content.toLowerCase startsWith query
|
||||||
|
}.reduceLeft { (a, b) =>
|
||||||
|
a && b
|
||||||
|
}
|
||||||
|
}.map { case (t1, t2) => (t2, t1.commentId, t1.content) }
|
||||||
|
|
||||||
|
def getCommentCount(issue: Issue): Int = {
|
||||||
|
Query(IssueComments)
|
||||||
|
.filter(_.byIssue(issue.userName, issue.repositoryName, issue.issueId))
|
||||||
|
.map(_.issueId)
|
||||||
|
.list.length
|
||||||
|
}
|
||||||
|
|
||||||
|
issues.union(comments).sortBy { case (issue, commentId, _) =>
|
||||||
|
issue.issueId ~ commentId
|
||||||
|
}.list.splitWith { case ((issue1, _, _), (issue2, _, _)) =>
|
||||||
|
issue1.issueId == issue2.issueId
|
||||||
|
}.map { result =>
|
||||||
|
val (issue, _, content) = result.head
|
||||||
|
(issue, getCommentCount(issue) , content)
|
||||||
|
}.toList
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object IssuesService {
|
object IssuesService {
|
||||||
@@ -279,4 +318,5 @@ object IssuesService {
|
|||||||
param(request, "sort", Seq("created", "comments", "updated")).getOrElse("created"),
|
param(request, "sort", Seq("created", "comments", "updated")).getOrElse("created"),
|
||||||
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"))
|
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
}
|
}
|
||||||
@files.map { file =>
|
@files.map { file =>
|
||||||
<div>
|
<div>
|
||||||
<div><a href="@url(repository)/blob/@repository.repository.defaultBranch/@file.path">@file.path</a></div>
|
<h5><a href="@url(repository)/blob/@repository.repository.defaultBranch/@file.path">@file.path</a></h5>
|
||||||
<div class="muted">@datetime(file.lastModified)</div>
|
<div class="muted">@datetime(file.lastModified)</div>
|
||||||
<pre>@Html(file.highlightText)</pre>
|
<pre>@Html(file.highlightText)</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
26
src/main/twirl/search/issues.scala.html
Normal file
26
src/main/twirl/search/issues.scala.html
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
@(issues: List[app.IssueSearchResult], query: String, repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
||||||
|
@import context._
|
||||||
|
@import view.helpers._
|
||||||
|
@html.main("Search Results", Some(repository)){
|
||||||
|
@menu("issue", query, repository){
|
||||||
|
@if(issues.isEmpty){
|
||||||
|
<h4>We couldn't find any code matching '@query'</h4>
|
||||||
|
} else {
|
||||||
|
<h4>We've found @issues.size code @plural(issues.size, "result")</h4>
|
||||||
|
}
|
||||||
|
@issues.map { issue =>
|
||||||
|
<div class="block">
|
||||||
|
<div class="pull-right muted">#@issue.issueId</div>
|
||||||
|
<h4><a href="@url(repository)/issues/@issue.issueId">@issue.title</a></h4>
|
||||||
|
<pre>@Html(issue.highlightText)</pre>
|
||||||
|
<div class="small muted">
|
||||||
|
Opened by <a href="@url(issue.openedUserName)" class="username">@issue.openedUserName</a>
|
||||||
|
at @datetime(issue.registeredDate)
|
||||||
|
@if(issue.commentCount > 0){
|
||||||
|
<i class="icon-comment"></i><strong>@issue.commentCount</strong> @plural(issue.commentCount, "comment")
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user