/*******************************************************************************
* Copyright (c) 2011 GitHub Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Kevin Sawicki (GitHub Inc.) - initial API and implementation
*******************************************************************************/
package org.eclipse.egit.ui.internal.search;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.egit.core.Activator;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.commit.RepositoryCommit;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.search.ui.ISearchQuery;
import org.eclipse.search.ui.ISearchResult;
/**
* Commit search query class that runs a {@link RevWalk} for all
* {@link Repository} objects included in the {@link CommitSearchSettings} and
* matches all {@link RevCommit} objects against the search settings.
*/
public class CommitSearchQuery implements ISearchQuery {
private abstract class SearchMatcher {
abstract boolean matches(Pattern pattern, RevCommit commit);
protected boolean matches(Pattern pattern, String input) {
return input != null && input.length() > 0
&& pattern.matcher(input).find();
}
}
private class AuthorMatcher extends SearchMatcher {
public boolean matches(Pattern pattern, RevCommit commit) {
PersonIdent author = commit.getAuthorIdent();
if (author != null)
return matches(pattern, author.getName())
|| matches(pattern, author.getEmailAddress());
else
return false;
}
}
private class CommitterMatcher extends SearchMatcher {
public boolean matches(Pattern pattern, RevCommit commit) {
PersonIdent committer = commit.getCommitterIdent();
if (committer != null)
return matches(pattern, committer.getName())
|| matches(pattern, committer.getEmailAddress());
else
return false;
}
}
private class MessageMatcher extends SearchMatcher {
public boolean matches(Pattern pattern, RevCommit commit) {
return matches(pattern, commit.getFullMessage());
}
}
private class CommitNameMatcher extends SearchMatcher {
public boolean matches(Pattern pattern, RevCommit commit) {
return matches(pattern, commit.name());
}
}
private class TreeMatcher extends SearchMatcher {
public boolean matches(Pattern pattern, RevCommit commit) {
RevTree tree = commit.getTree();
return tree != null ? matches(pattern, tree.name()) : false;
}
}
private class ParentMatcher extends SearchMatcher {
public boolean matches(Pattern pattern, RevCommit commit) {
for (RevCommit parent : commit.getParents())
if (matches(pattern, parent.name()))
return true;
return false;
}
}
private CommitSearchResult result = new CommitSearchResult(this);
private CommitSearchSettings settings;
private List<SearchMatcher> matchers = new LinkedList<SearchMatcher>();
/**
* Create git search query
*
* @param settings
*/
public CommitSearchQuery(CommitSearchSettings settings) {
this.settings = settings;
if (this.settings.isMatchAuthor())
matchers.add(new AuthorMatcher());
if (this.settings.isMatchCommitter())
matchers.add(new CommitterMatcher());
if (this.settings.isMatchMessage())
matchers.add(new MessageMatcher());
if (this.settings.isMatchCommit())
matchers.add(new CommitNameMatcher());
if (this.settings.isMatchTree())
matchers.add(new TreeMatcher());
if (this.settings.isMatchParents())
matchers.add(new ParentMatcher());
}
/**
* Get text pattern being searched for
*
* @return pattern
*/
public String getPattern() {
return this.settings.getTextPattern();
}
private Repository getRepository(String name) throws IOException {
Repository repository = null;
File path = new File(name);
if (path.exists())
repository = Activator.getDefault().getRepositoryCache()
.lookupRepository(path);
return repository;
}
/**
* @see org.eclipse.search.ui.ISearchQuery#run(org.eclipse.core.runtime.IProgressMonitor)
*/
public IStatus run(IProgressMonitor monitor)
throws OperationCanceledException {
this.result.removeAll();
Pattern pattern = PatternUtils.createPattern(
this.settings.getTextPattern(),
this.settings.isCaseSensitive(), this.settings.isRegExSearch());
List<String> paths = settings.getRepositories();
try {
for (String path : paths) {
if (monitor.isCanceled())
throw new OperationCanceledException();
Repository repo = getRepository(path);
if (repo == null)
continue;
monitor.setTaskName(MessageFormat.format(
UIText.CommitSearchQuery_TaskSearchCommits, repo
.getDirectory().getParentFile().getName()));
walkRepository(repo, pattern, monitor);
}
} catch (IOException e) {
org.eclipse.egit.ui.Activator.handleError(
"Error searching commits", e, true); //$NON-NLS-1$
}
return Status.OK_STATUS;
}
private void walkRepository(Repository repository, Pattern pattern,
IProgressMonitor monitor) throws IOException {
RevWalk walk = new RevWalk(repository);
try {
walk.setRetainBody(true);
List<RevCommit> commits = new LinkedList<RevCommit>();
if (this.settings.isAllBranches()) {
for (Ref ref : repository.getRefDatabase()
.getRefs(Constants.R_HEADS).values())
if (!ref.isSymbolic())
commits.add(walk.parseCommit(ref.getObjectId()));
for (Ref ref : repository.getRefDatabase()
.getRefs(Constants.R_REMOTES).values())
if (!ref.isSymbolic())
commits.add(walk.parseCommit(ref.getObjectId()));
} else {
ObjectId headCommit = repository.resolve(Constants.HEAD);
if (headCommit != null)
commits.add(walk.parseCommit(headCommit));
}
if (!commits.isEmpty()) {
walk.markStart(commits);
for (RevCommit commit : walk) {
if (monitor.isCanceled())
throw new OperationCanceledException();
for (SearchMatcher matcher : this.matchers)
if (matcher.matches(pattern, commit)) {
result.addResult(new RepositoryCommit(repository,
commit));
break;
}
}
}
} finally {
walk.dispose();
}
}
/**
* @see org.eclipse.search.ui.ISearchQuery#getLabel()
*/
public String getLabel() {
return UIText.CommitSearchQuery_Label;
}
/**
* @see org.eclipse.search.ui.ISearchQuery#canRerun()
*/
public boolean canRerun() {
return true;
}
/**
* @see org.eclipse.search.ui.ISearchQuery#canRunInBackground()
*/
public boolean canRunInBackground() {
return true;
}
/**
* @see org.eclipse.search.ui.ISearchQuery#getSearchResult()
*/
public ISearchResult getSearchResult() {
return this.result;
}
}