import java.util.TreeSet;\r
import java.util.concurrent.ConcurrentHashMap;\r
\r
+import org.apache.lucene.analysis.Analyzer;\r
+import org.apache.lucene.analysis.TokenStream;\r
import org.apache.lucene.analysis.standard.StandardAnalyzer;\r
import org.apache.lucene.document.DateTools;\r
import org.apache.lucene.document.DateTools.Resolution;\r
import org.apache.lucene.search.Query;\r
import org.apache.lucene.search.ScoreDoc;\r
import org.apache.lucene.search.TopScoreDocCollector;\r
+import org.apache.lucene.search.highlight.Fragmenter;\r
+import org.apache.lucene.search.highlight.Highlighter;\r
+import org.apache.lucene.search.highlight.InvalidTokenOffsetsException;\r
+import org.apache.lucene.search.highlight.QueryScorer;\r
+import org.apache.lucene.search.highlight.SimpleHTMLFormatter;\r
+import org.apache.lucene.search.highlight.SimpleSpanFragmenter;\r
+import org.apache.lucene.search.highlight.TokenSources;\r
import org.apache.lucene.store.Directory;\r
import org.apache.lucene.store.FSDirectory;\r
import org.apache.lucene.util.Version;\r
private static final int INDEX_VERSION = 1;\r
\r
private static final String FIELD_OBJECT_TYPE = "type";\r
- private static final String FIELD_OBJECT_ID = "id";\r
+ private static final String FIELD_ISSUE = "issue";\r
+ private static final String FIELD_PATH = "path";\r
+ private static final String FIELD_COMMIT = "commit";\r
private static final String FIELD_BRANCH = "branch";\r
private static final String FIELD_REPOSITORY = "repository";\r
private static final String FIELD_SUMMARY = "summary";\r
doc.add(new Field(FIELD_OBJECT_TYPE, ObjectType.blob.name(), Store.YES, Index.NOT_ANALYZED_NO_NORMS));\r
doc.add(new Field(FIELD_REPOSITORY, repositoryName, Store.YES, Index.ANALYZED));\r
doc.add(new Field(FIELD_BRANCH, branchName, Store.YES, Index.ANALYZED));\r
- doc.add(new Field(FIELD_OBJECT_ID, path, Store.YES, Index.ANALYZED));\r
+ doc.add(new Field(FIELD_COMMIT, commit.getName(), Store.YES, Index.ANALYZED));\r
+ doc.add(new Field(FIELD_PATH, path, Store.YES, Index.ANALYZED));\r
doc.add(new Field(FIELD_DATE, blobDate, Store.YES, Index.NO));\r
doc.add(new Field(FIELD_AUTHOR, blobAuthor, Store.YES, Index.ANALYZED));\r
doc.add(new Field(FIELD_COMMITTER, blobCommitter, Store.YES, Index.ANALYZED)); \r
in.close();\r
byte[] content = os.toByteArray();\r
String str = new String(content, Constants.CHARACTER_ENCODING);\r
- doc.add(new Field(FIELD_CONTENT, str, Store.NO, Index.ANALYZED));\r
+ doc.add(new Field(FIELD_CONTENT, str, Store.YES, Index.ANALYZED));\r
os.reset();\r
} \r
\r
protected static RevTree getTree(final RevWalk walk, final RevCommit commit)\r
throws IOException {\r
final RevTree tree = commit.getTree();\r
- if (tree != null)\r
+ if (tree != null) {\r
return tree;\r
+ }\r
walk.parseHeaders(commit);\r
return commit.getTree();\r
}\r
IndexWriter writer = getIndexWriter(repository, false);\r
writer.deleteDocuments(\r
new Term(FIELD_OBJECT_TYPE, ObjectType.issue.name()), new Term(\r
- FIELD_OBJECT_ID, issueId));\r
+ FIELD_ISSUE, issueId));\r
writer.commit();\r
result.success = true;\r
return result;\r
for (PathChangeModel path : changedPaths) {\r
// delete the indexed blob\r
writer.deleteDocuments(new Term(FIELD_OBJECT_TYPE, ObjectType.blob.name()),\r
- new Term(FIELD_BRANCH, branch), new Term(FIELD_OBJECT_ID, path.path));\r
+ new Term(FIELD_BRANCH, branch), new Term(FIELD_PATH, path.path));\r
\r
// re-index the blob\r
if (!ChangeType.DELETE.equals(path.changeType)) {\r
Index.NOT_ANALYZED));\r
doc.add(new Field(FIELD_REPOSITORY, repositoryName, Store.YES, Index.ANALYZED));\r
doc.add(new Field(FIELD_BRANCH, branch, Store.YES, Index.ANALYZED));\r
- doc.add(new Field(FIELD_OBJECT_ID, path.path, Store.YES, Index.ANALYZED));\r
+ doc.add(new Field(FIELD_COMMIT, commit.getName(), Store.YES, Index.ANALYZED));\r
+ doc.add(new Field(FIELD_PATH, path.path, Store.YES, Index.ANALYZED));\r
doc.add(new Field(FIELD_DATE, revDate, Store.YES, Index.NO));\r
doc.add(new Field(FIELD_AUTHOR, getAuthor(commit), Store.YES, Index.ANALYZED));\r
doc.add(new Field(FIELD_COMMITTER, getCommitter(commit), Store.YES, Index.ANALYZED));\r
// read the blob content\r
String str = JGitUtils.getStringContent(repository, commit.getTree(),\r
path.path);\r
- doc.add(new Field(FIELD_CONTENT, str, Store.NO, Index.ANALYZED));\r
+ doc.add(new Field(FIELD_CONTENT, str, Store.YES, Index.ANALYZED));\r
writer.addDocument(doc);\r
}\r
}\r
// delete the old issue from the index, if exists\r
IndexWriter writer = getIndexWriter(repository, false);\r
writer.deleteDocuments(new Term(FIELD_OBJECT_TYPE, ObjectType.issue.name()), new Term(\r
- FIELD_OBJECT_ID, String.valueOf(issue.id)));\r
+ FIELD_ISSUE, String.valueOf(issue.id)));\r
writer.commit();\r
\r
Document doc = createDocument(issue);\r
Document doc = new Document();\r
doc.add(new Field(FIELD_OBJECT_TYPE, ObjectType.issue.name(), Store.YES,\r
Field.Index.NOT_ANALYZED));\r
- doc.add(new Field(FIELD_OBJECT_ID, issue.id, Store.YES, Index.ANALYZED));\r
+ doc.add(new Field(FIELD_ISSUE, issue.id, Store.YES, Index.ANALYZED));\r
doc.add(new Field(FIELD_BRANCH, IssueUtils.GB_ISSUES, Store.YES, Index.ANALYZED));\r
doc.add(new Field(FIELD_DATE, DateTools.dateToString(issue.created, Resolution.MINUTE),\r
Store.YES, Field.Index.NO));\r
Document doc = new Document();\r
doc.add(new Field(FIELD_OBJECT_TYPE, ObjectType.commit.name(), Store.YES,\r
Index.NOT_ANALYZED));\r
- doc.add(new Field(FIELD_OBJECT_ID, commit.getName(), Store.YES, Index.ANALYZED));\r
+ doc.add(new Field(FIELD_COMMIT, commit.getName(), Store.YES, Index.ANALYZED));\r
doc.add(new Field(FIELD_DATE, DateTools.timeToString(commit.getCommitTime() * 1000L,\r
Resolution.MINUTE), Store.YES, Index.NO));\r
doc.add(new Field(FIELD_AUTHOR, getAuthor(commit), Store.YES, Index.ANALYZED));\r
SearchResult result = new SearchResult();\r
result.score = score;\r
result.date = DateTools.stringToDate(doc.get(FIELD_DATE));\r
- result.summary = doc.get(FIELD_SUMMARY);\r
- result.content = doc.get(FIELD_CONTENT);\r
+ result.summary = doc.get(FIELD_SUMMARY); \r
result.author = doc.get(FIELD_AUTHOR);\r
result.committer = doc.get(FIELD_COMMITTER);\r
result.type = ObjectType.fromName(doc.get(FIELD_OBJECT_TYPE));\r
result.repository = doc.get(FIELD_REPOSITORY);\r
result.branch = doc.get(FIELD_BRANCH);\r
- result.id = doc.get(FIELD_OBJECT_ID);\r
+ result.commitId = doc.get(FIELD_COMMIT);\r
+ result.issueId = doc.get(FIELD_ISSUE);\r
+ result.path = doc.get(FIELD_PATH);\r
if (doc.get(FIELD_TAG) != null) {\r
result.tags = StringUtils.getStringsFromValue(doc.get(FIELD_TAG));\r
}\r
int docId = hits[i].doc;\r
Document doc = searcher.doc(docId);\r
SearchResult result = createSearchResult(doc, hits[i].score);\r
+ String content = doc.get(FIELD_CONTENT);\r
+ result.fragment = getHighlightedFragment(analyzer, query, content);\r
results.add(result);\r
}\r
} catch (Exception e) {\r
}\r
return new ArrayList<SearchResult>(results);\r
}\r
+ \r
+ private static String getHighlightedFragment(Analyzer analyzer, Query query,\r
+ String content) throws IOException, InvalidTokenOffsetsException {\r
+ content = content == null ? "":StringUtils.escapeForHtml(content, false); \r
+ TokenStream stream = TokenSources.getTokenStream("content", content, analyzer);\r
+ QueryScorer scorer = new QueryScorer(query, "content");\r
+ Fragmenter fragmenter = new SimpleSpanFragmenter(scorer, 150);\r
+\r
+ SimpleHTMLFormatter formatter = new SimpleHTMLFormatter("<span class=\"highlight\">", "</span>");\r
+ Highlighter highlighter = new Highlighter(formatter, scorer);\r
+ \r
+ highlighter.setTextFragmenter(fragmenter);\r
+ String [] fragments = highlighter.getBestFragments(stream, content, 5);\r
+ if (ArrayUtils.isEmpty(fragments)) {\r
+ return content;\r
+ }\r
+ if (fragments.length == 1) {\r
+ return "<pre>" + fragments[0] + "</pre>";\r
+ }\r
+ StringBuilder sb = new StringBuilder();\r
+ for (int i = 0, len = fragments.length; i < len; i++) {\r
+ String fragment = fragments[i].trim(); \r
+ sb.append("<pre>");\r
+ sb.append(fragment);\r
+ sb.append("</pre>");\r
+ if (i < len - 1) {\r
+ sb.append("<span class=\"ellipses\">...</span><br/>");\r
+ }\r
+ }\r
+ return sb.toString();\r
+ }\r
\r
/**\r
* Close all the index writers and searchers\r
Label icon = WicketUtils.newIcon("type", "icon-refresh");\r
WicketUtils.setHtmlTooltip(icon, "commit");\r
item.add(icon);\r
- item.add(new LinkPanel("summary", null, sr.summary, CommitPage.class, WicketUtils.newObjectParameter(sr.repository, sr.id)));\r
+ item.add(new LinkPanel("summary", null, sr.summary, CommitPage.class, WicketUtils.newObjectParameter(sr.repository, sr.commitId)));\r
break;\r
}\r
case blob: {\r
Label icon = WicketUtils.newIcon("type", "icon-file");\r
WicketUtils.setHtmlTooltip(icon, "blob");\r
item.add(icon);\r
- item.add(new LinkPanel("summary", null, sr.id, BlobPage.class, WicketUtils.newPathParameter(sr.repository, sr.branch, sr.id)));\r
+ item.add(new LinkPanel("summary", null, sr.path, BlobPage.class, WicketUtils.newPathParameter(sr.repository, sr.branch, sr.path)));\r
break;\r
}\r
case issue: {\r
Label icon = WicketUtils.newIcon("type", "icon-file");\r
WicketUtils.setHtmlTooltip(icon, "issue");\r
item.add(icon);\r
- item.add(new Label("summary", "issue: " + sr.id));\r
+ item.add(new Label("summary", "issue: " + sr.issueId));\r
break;\r
}\r
}\r
+ item.add(new Label("fragment", sr.fragment).setEscapeModelStrings(false).setVisible(!StringUtils.isEmpty(sr.fragment)));\r
item.add(new LinkPanel("repository", null, sr.repository, SummaryPage.class, WicketUtils.newRepositoryParameter(sr.repository)));\r
item.add(new LinkPanel("branch", "branch", StringUtils.getRelativePath(Constants.R_HEADS, sr.branch), LogPage.class, WicketUtils.newObjectParameter(sr.repository, sr.branch)));\r
item.add(new Label("author", sr.author));\r
}\r
};\r
ListMultipleChoice<String> selections = new ListMultipleChoice<String>("repositories", repositories, GitBlit.self().getRepositoryList());\r
- selections.setMaxRows(11);\r
+ selections.setMaxRows(10);\r
form.add(selections);\r
form.add(new TextField<String>("fragment", fragment));\r
add(form);\r
String name = StringUtils.getRelativePath(GitBlitSuite.REPOSITORIES.getAbsolutePath(),\r
repository.getDirectory().getAbsolutePath());\r
LuceneUtils.reindex(name, repository);\r
- SearchResult result = LuceneUtils.search("type:blob AND id:bit.bit", 1, repository).get(0); \r
+ SearchResult result = LuceneUtils.search("type:blob AND path:bit.bit", 1, repository).get(0); \r
assertEquals("Mike Donaghy", result.author);\r
- result = LuceneUtils.search("type:blob AND id:clipper.prg", 1, repository).get(0); \r
+ result = LuceneUtils.search("type:blob AND path:clipper.prg", 1, repository).get(0); \r
assertEquals("tinogomes", result.author);\r
repository.close();\r
\r
// blob test\r
results = LuceneUtils.search("type: blob AND \"import std.stdio\"", 10, repository);\r
assertEquals(1, results.size());\r
- assertEquals("d.D", results.get(0).id);\r
+ assertEquals("d.D", results.get(0).path);\r
\r
// 1 occurrence on the gh-pages branch\r
repository = GitBlitSuite.getTheoreticalPhysicsRepository();\r
results = LuceneUtils.search("\"add the .nojekyll file\"", 10, repository);\r
assertEquals(1, results.size());\r
assertEquals("Ondrej Certik", results.get(0).author);\r
- assertEquals("2648c0c98f2101180715b4d432fc58d0e21a51d7", results.get(0).id);\r
+ assertEquals("2648c0c98f2101180715b4d432fc58d0e21a51d7", results.get(0).commitId);\r
assertEquals("refs/heads/gh-pages", results.get(0).branch);\r
\r
results = LuceneUtils.search("type:blob AND \"src/intro.rst\"", 10, repository);\r
assertEquals(4, results.size());\r
\r
// hash id tests\r
- results = LuceneUtils.search("id:57c4f26f157ece24b02f4f10f5f68db1d2ce7ff5", 10, repository);\r
+ results = LuceneUtils.search("commit:57c4f26f157ece24b02f4f10f5f68db1d2ce7ff5", 10, repository);\r
assertEquals(1, results.size());\r
\r
- results = LuceneUtils.search("id:57c4f26f157*", 10, repository);\r
+ results = LuceneUtils.search("commit:57c4f26f157*", 10, repository);\r
assertEquals(1, results.size());\r
\r
repository.close();\r