Merge "Document how to send file content without conversion using curl"
diff --git a/.buckversion b/.buckversion
index 582e61d..2c4c008 100644
--- a/.buckversion
+++ b/.buckversion
@@ -1 +1 @@
-b01f03b82e23614965c2ddc13900ccc6dc4d8361
+410fcf3420cb06e62cbe9ee93eff931fa9a9b1a2
diff --git a/.gitignore b/.gitignore
index 8411c8b..8e92321 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,5 +7,8 @@
/gerrit-parent.iml
*.sublime-*
/gerrit-package-plugins
+/.buckconfig.local
+/.buckd
+/buck-cache
/buck-out
/local.properties
diff --git a/BUCK b/BUCK
index d467528..ee5ba75 100644
--- a/BUCK
+++ b/BUCK
@@ -8,11 +8,13 @@
genrule(
name = 'api',
- cmd = 'echo',
+ cmd = '',
srcs = [],
deps = [
':extension-api',
+ ':extension-api-src',
':plugin-api',
+ ':plugin-api-src',
],
out = '__fake.api__',
)
@@ -29,18 +31,33 @@
export_deps = True,
visibility = ['PUBLIC'],
)
+genrule(
+ name = 'extension-api-src',
+ cmd = 'ln -s $DEPS $OUT',
+ srcs = [],
+ deps = ['//gerrit-extension-api:api-src'],
+ out = 'extension-api-src.jar',
+)
+
+PLUGIN_API = [
+ '//gerrit-server:server',
+ '//gerrit-sshd:sshd',
+ '//gerrit-httpd:httpd',
+]
java_binary(name = 'plugin-api', deps = [':plugin-lib'])
java_library(
name = 'plugin-lib',
- deps = [
- '//gerrit-server:server',
- '//gerrit-sshd:sshd',
- '//gerrit-httpd:httpd',
- ],
+ deps = PLUGIN_API,
export_deps = True,
visibility = ['PUBLIC'],
)
+java_binary(
+ name = 'plugin-api-src',
+ deps = [
+ '//gerrit-extension-api:api-src',
+ ] + [d + '-src' for d in PLUGIN_API],
+)
genrule(
name = 'download',
diff --git a/Documentation/asciidoc.defs b/Documentation/asciidoc.defs
index c6df544..389e0ca 100644
--- a/Documentation/asciidoc.defs
+++ b/Documentation/asciidoc.defs
@@ -46,7 +46,7 @@
)
genrule(
name = name,
- cmd = '',
+ cmd = ':>$OUT',
srcs = [],
deps = [':' + o for o in outs],
out = name + '__done',
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt
index f5bdbea..329459d 100644
--- a/Documentation/dev-buck.txt
+++ b/Documentation/dev-buck.txt
@@ -216,6 +216,23 @@
repository has precedence.
+Caching Build Results
+~~~~~~~~~~~~~~~~~~~~~
+
+Build results can be locally cached, saving rebuild time when
+switching between Git branches. Buck's documentation covers
+caching in link:http://facebook.github.io/buck/concept/buckconfig.html[buckconfig].
+The trivial case using a local directory is:
+
+----
+ cat >.buckconfig.local <<EOF
+ [cache]
+ mode = dir
+ dir = buck-cache
+ EOF
+----
+
+
Build Process Switch Exit Criteria
----------------------------------
diff --git a/gerrit-antlr/BUCK b/gerrit-antlr/BUCK
index 0e19320..2071656 100644
--- a/gerrit-antlr/BUCK
+++ b/gerrit-antlr/BUCK
@@ -3,7 +3,6 @@
'QueryParser.java',
]
PARSER_DEPS = [
- ':query_antlr',
':query_exception',
'//lib/antlr:java_runtime',
]
@@ -24,7 +23,7 @@
java_library(
name = 'lib',
srcs = [genfile(f) for f in ANTLR_OUTS],
- deps = PARSER_DEPS,
+ deps = PARSER_DEPS + [':' + f for f in ANTLR_OUTS],
)
genrule(
diff --git a/gerrit-extension-api/BUCK b/gerrit-extension-api/BUCK
index 75539f6..4145636 100644
--- a/gerrit-extension-api/BUCK
+++ b/gerrit-extension-api/BUCK
@@ -1,6 +1,14 @@
+SRCS = glob(['src/main/java/com/google/gerrit/extensions/**/*.java'])
+
java_library2(
name = 'api',
- srcs = glob(['src/main/java/com/google/gerrit/extensions/**/*.java']),
+ srcs = SRCS,
compile_deps = ['//lib/guice:guice'],
visibility = ['PUBLIC'],
)
+
+java_sources(
+ name = 'api-src',
+ srcs = SRCS,
+ visibility = ['PUBLIC'],
+)
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSet.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSet.java
index ec34887..b2f19e5 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSet.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSet.java
@@ -26,6 +26,7 @@
import com.google.inject.util.Types;
import java.util.Collection;
+import java.util.Collections;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -127,6 +128,11 @@
return binder.bind(type).annotatedWith(name);
}
+ public static <T> DynamicSet<T> emptySet() {
+ return new DynamicSet<T>(
+ Collections.<AtomicReference<Provider<T>>> emptySet());
+ }
+
private final CopyOnWriteArrayList<AtomicReference<Provider<T>>> items;
DynamicSet(Collection<AtomicReference<Provider<T>>> base) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CodeMirrorDemo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CodeMirrorDemo.java
index 70b9ff9..a68b7a2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CodeMirrorDemo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CodeMirrorDemo.java
@@ -22,6 +22,7 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
@@ -31,7 +32,6 @@
import net.codemirror.lib.CodeMirror;
import net.codemirror.lib.CodeMirror.LineClassWhere;
import net.codemirror.lib.Configuration;
-import net.codemirror.lib.LineCharacter;
import net.codemirror.lib.ModeInjector;
public class CodeMirrorDemo extends Screen {
@@ -140,7 +140,7 @@
private CodeMirror displaySide(DiffInfo.FileMeta meta, String contents,
Element ele) {
if (meta == null) {
- return null; // TODO: Handle empty contents
+ contents = "";
}
Configuration cfg = Configuration.create()
.set("readOnly", true)
@@ -155,20 +155,8 @@
return cm;
}
- private void addPadding(CodeMirror cm, int line) {
- Element div = DOM.createDiv();
- div.setClassName(diffTable.style.padding());
- cm.addLineWidget(line, div, null);
- }
-
private void render(DiffInfo diff) {
JsArray<Region> regions = diff.content();
- Configuration insertOpt = Configuration.create()
- .set("className", diffTable.style.insert())
- .set("readOnly", true);
- Configuration deleteOpt = Configuration.create()
- .set("className", diffTable.style.delete())
- .set("readOnly", true);
int lineA = 0, lineB = 0;
for (int i = 0; i < regions.length(); i++) {
Region current = regions.get(i);
@@ -177,35 +165,43 @@
lineB += current.ab().length();
} else if (current.a() == null && current.b() != null) {
int delta = current.b().length();
- for (int j = 0; j < delta; j++) {
- addPadding(cmA, lineA - 1);
- }
- for (int j = 0; j < delta; j++) {
- cmB.addLineClass(lineB, LineClassWhere.WRAP,
- diffTable.style.insert());
- LineCharacter from = LineCharacter.create(lineB, 0);
- cmB.markText(from, from, insertOpt);
- lineB++;
- }
+ insertEmptyLines(cmA, lineA, delta);
+ lineB = colorLines(cmB, lineB, delta);
} else if (current.a() != null && current.b() == null) {
int delta = current.a().length();
- for (int j = 0; j < delta; j++) {
- addPadding(cmB, lineB - 1);
+ insertEmptyLines(cmB, lineB, delta);
+ lineA = colorLines(cmA, lineA, delta);
+ } else { // TODO: Implement intraline
+ int aLength = current.a().length();
+ int bLength = current.b().length();
+ lineA = colorLines(cmA, lineA, aLength);
+ lineB = colorLines(cmB, lineB, bLength);
+ if (aLength < bLength) {
+ insertEmptyLines(cmA, lineA, bLength - aLength);
+ } else if (aLength > bLength) {
+ insertEmptyLines(cmB, lineB, aLength - bLength);
}
- for (int j = 0; j < delta; j++) {
- cmA.addLineClass(lineA, LineClassWhere.WRAP,
- diffTable.style.delete());
- LineCharacter from = LineCharacter.create(lineA, 0);
- cmA.markText(from, from, deleteOpt);
- lineA++;
- }
- } else { // TODO: Handle intraline edit.
- lineA += current.a().length();
- lineB += current.a().length();
}
}
}
+ private void insertEmptyLines(CodeMirror cm, int line, int cnt) {
+ Element div = DOM.createDiv();
+ div.setClassName(diffTable.style.padding());
+ div.getStyle().setHeight(cnt, Unit.EM);
+ Configuration config = Configuration.create()
+ .set("coverGutter", true)
+ .set("above", line == 0);
+ cm.addLineWidget(line == 0 ? 0 : (line - 1), div, config);
+ }
+
+ private int colorLines(CodeMirror cm, int line, int cnt) {
+ for (int i = 0; i < cnt; i++) {
+ cm.addLineClass(line + i, LineClassWhere.WRAP, diffTable.style.diff());
+ }
+ return line + cnt;
+ }
+
private static String getContentType(DiffInfo.FileMeta meta) {
return meta != null && meta.content_type() != null
? ModeInjector.getContentType(meta.content_type())
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
index e681630..2aa83b6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
@@ -31,8 +31,7 @@
private static Binder uiBinder = GWT.create(Binder.class);
interface LineStyle extends CssResource {
- String insert();
- String delete();
+ String diff();
String intraline();
String padding();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
index 5e4cf0d..301ddf1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
@@ -16,8 +16,9 @@
-->
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'>
- <ui:style field='css'>
+ <ui:style type='com.google.gerrit.client.diff.DiffTable.LineStyle'>
@external .CodeMirror, .CodeMirror-selectedtext, .CodeMirror-scroll;
+ @external .CodeMirror-linenumber;
.difftable .CodeMirror {
border: 1px solid #eee;
@@ -35,34 +36,26 @@
overflow-x: hidden;
overflow-y: hidden;
}
- <!--.difftable .CodeMirror-vscrollbar {
- display: none !important;
- }-->
- </ui:style>
- <ui:style type='com.google.gerrit.client.diff.DiffTable.LineStyle'>
- @external .CodeMirror-linenumber;
-
- .insert,
- .insert .CodeMirror-linenumber {
- background-color: #dfd;
- }
- .delete,
- .delete .CodeMirror-linenumber {
+ .a .diff,
+ .a .diff .CodeMirror-linenumber {
background-color: #fee;
}
- .intraline {
+ .b .diff,
+ .b .diff .CodeMirror-linenumber {
+ background-color: #dfd;
+ }
+ .b .intraline {
background-color: #9f9;
}
.padding {
background-color: #eee;
- height: 1em;
}
</ui:style>
- <g:HTMLPanel styleName='{css.difftable}'>
+ <g:HTMLPanel styleName='{style.difftable}'>
<table>
<tr>
- <td><div ui:field='cmA'></div></td>
- <td><div ui:field='cmB'></div></td>
+ <td><div ui:field='cmA' class='{style.a}'></div></td>
+ <td><div ui:field='cmB' class='{style.b}'></div></td>
</tr>
</table>
</g:HTMLPanel>
diff --git a/gerrit-httpd/BUCK b/gerrit-httpd/BUCK
index 51f951d..c906459 100644
--- a/gerrit-httpd/BUCK
+++ b/gerrit-httpd/BUCK
@@ -1,7 +1,10 @@
+SRCS = glob(['src/main/java/**/*.java'])
+RESOURCES = glob(['src/main/resources/**/*'])
+
java_library2(
name = 'httpd',
- srcs = glob(['src/main/java/**/*.java']),
- resources = glob(['src/main/resources/**/*']),
+ srcs = SRCS,
+ resources = RESOURCES,
deps = [
'//gerrit-antlr:query_exception',
'//gerrit-common:server',
@@ -33,6 +36,12 @@
visibility = ['PUBLIC'],
)
+java_sources(
+ name = 'httpd-src',
+ srcs = SRCS + RESOURCES,
+ visibility = ['PUBLIC'],
+)
+
java_test(
name = 'httpd_tests',
srcs = glob(['src/test/java/**/*.java']),
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java
index 2fe3124..0a9ed28 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java
@@ -154,7 +154,7 @@
if (filter != null) {
try {
ChangeQueryBuilder builder = queryBuilder.create(currentUser.get());
- builder.setAllowFile(true);
+ builder.setAllowFileRegex(true);
builder.parse(filter);
} catch (QueryParseException badFilter) {
throw new InvalidQueryException(badFilter.getMessage(), filter);
diff --git a/gerrit-lucene/BUCK b/gerrit-lucene/BUCK
new file mode 100644
index 0000000..4e6503e
--- /dev/null
+++ b/gerrit-lucene/BUCK
@@ -0,0 +1,18 @@
+java_library(
+ name = 'lucene',
+ srcs = glob(['src/main/java/**/*.java']),
+ deps = [
+ '//gerrit-antlr:query_exception',
+ '//gerrit-extension-api:api',
+ '//gerrit-reviewdb:client',
+ '//gerrit-server:server',
+ '//lib:guava',
+ '//lib:gwtorm',
+ '//lib:lucene-analyzers-common',
+ '//lib:lucene-core',
+ '//lib/guice:guice',
+ '//lib/jgit:jgit',
+ '//lib/log:api',
+ ],
+ visibility = ['PUBLIC'],
+)
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/IndexVersionCheck.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/IndexVersionCheck.java
new file mode 100644
index 0000000..4fe6def
--- /dev/null
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/IndexVersionCheck.java
@@ -0,0 +1,93 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.package com.google.gerrit.server.git;
+
+package com.google.gerrit.lucene;
+
+import static com.google.gerrit.lucene.LuceneChangeIndex.LUCENE_VERSION;
+
+import static org.apache.lucene.util.Version.LUCENE_CURRENT;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.inject.Inject;
+import com.google.inject.ProvisionException;
+
+import org.apache.lucene.util.Version;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+public class IndexVersionCheck implements LifecycleListener {
+ public static final Map<String, Integer> SCHEMA_VERSIONS = ImmutableMap.of(
+ "changes_open", ChangeField.SCHEMA_VERSION,
+ "changes_closed", ChangeField.SCHEMA_VERSION);
+
+ public static File gerritIndexConfig(SitePaths sitePaths) {
+ return new File(sitePaths.index_dir, "gerrit_index.config");
+ }
+
+ private final SitePaths sitePaths;
+
+ @Inject
+ IndexVersionCheck(SitePaths sitePaths) {
+ this.sitePaths = sitePaths;
+ }
+
+ @Override
+ public void start() {
+ File file = gerritIndexConfig(sitePaths);
+ try {
+ FileBasedConfig cfg = new FileBasedConfig(file, FS.detect());
+ cfg.load();
+ for (Map.Entry<String, Integer> e : SCHEMA_VERSIONS.entrySet()) {
+ int schemaVersion = cfg.getInt("index", e.getKey(), "schemaVersion", 0);
+ if (schemaVersion != e.getValue()) {
+ throw new ProvisionException(String.format(
+ "wrong index schema version for \"%s\": expected %d, found %d%s",
+ e.getKey(), e.getValue(), schemaVersion, upgrade()));
+ }
+ }
+ @SuppressWarnings("deprecation")
+ Version luceneVersion =
+ cfg.getEnum("lucene", null, "version", LUCENE_CURRENT);
+ if (luceneVersion != LUCENE_VERSION) {
+ throw new ProvisionException(String.format(
+ "wrong Lucene version: expected %d, found %d%s",
+ luceneVersion, LUCENE_VERSION, upgrade()));
+
+ }
+ } catch (IOException e) {
+ throw new ProvisionException("unable to read " + file);
+ } catch (ConfigInvalidException e) {
+ throw new ProvisionException("invalid config file " + file);
+ }
+ }
+
+ @Override
+ public void stop() {
+ // Do nothing.
+ }
+
+ private final String upgrade() {
+ return "\nRun reindex to rebuild the index:\n"
+ + "$ java -jar gerrit.war reindex -d "
+ + sitePaths.site_path.getAbsolutePath();
+ }
+}
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
new file mode 100644
index 0000000..ce54b35
--- /dev/null
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -0,0 +1,310 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.package com.google.gerrit.server.git;
+
+package com.google.gerrit.lucene;
+
+import static com.google.gerrit.server.query.change.ChangeQueryBuilder.FIELD_CHANGE;
+
+import static org.apache.lucene.search.BooleanClause.Occur.MUST;
+import static org.apache.lucene.search.BooleanClause.Occur.MUST_NOT;
+import static org.apache.lucene.search.BooleanClause.Occur.SHOULD;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.ChangeIndex;
+import com.google.gerrit.server.index.FieldDef;
+import com.google.gerrit.server.index.FieldDef.FillArgs;
+import com.google.gerrit.server.index.FieldType;
+import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gerrit.server.query.AndPredicate;
+import com.google.gerrit.server.query.NotPredicate;
+import com.google.gerrit.server.query.OrPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangeDataSource;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
+
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.IntField;
+import org.apache.lucene.document.StringField;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.IndexWriterConfig.OpenMode;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.SearcherManager;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.NumericUtils;
+import org.apache.lucene.util.Version;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Secondary index implementation using Apache Lucene.
+ * <p>
+ * Writes are managed using a single {@link IndexWriter} per process, committed
+ * aggressively. Reads use {@link SearcherManager} and periodically refresh,
+ * though there may be some lag between a committed write and it showing up to
+ * other threads' searchers.
+ */
+public class LuceneChangeIndex implements ChangeIndex {
+ private static final Logger log =
+ LoggerFactory.getLogger(LuceneChangeIndex.class);
+
+ public static final Version LUCENE_VERSION = Version.LUCENE_43;
+
+ private final FillArgs fillArgs;
+ private final Directory dir;
+ private final IndexWriter writer;
+ private final SearcherManager searcherManager;
+
+ LuceneChangeIndex(File file, FillArgs fillArgs) throws IOException {
+ this.fillArgs = fillArgs;
+ dir = FSDirectory.open(file);
+ IndexWriterConfig writerConfig =
+ new IndexWriterConfig(LUCENE_VERSION, new StandardAnalyzer(LUCENE_VERSION));
+ writerConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);
+ writer = new IndexWriter(dir, writerConfig);
+ searcherManager = new SearcherManager(writer, true, null);
+ }
+
+ void close() {
+ try {
+ searcherManager.close();
+ } catch (IOException e) {
+ log.warn("error closing Lucene searcher", e);
+ }
+ try {
+ writer.close(true);
+ } catch (IOException e) {
+ log.warn("error closing Lucene writer", e);
+ }
+ try {
+ dir.close();
+ } catch (IOException e) {
+ log.warn("error closing Lucene directory", e);
+ }
+ }
+
+ @Override
+ public void insert(ChangeData cd) throws IOException {
+ writer.addDocument(toDocument(cd));
+ commit();
+ }
+
+ @Override
+ public void replace(ChangeData cd) throws IOException {
+ writer.updateDocument(intTerm(FIELD_CHANGE, cd.getId().get()),
+ toDocument(cd));
+ commit();
+ }
+
+ @Override
+ public void delete(ChangeData cd) throws IOException {
+ writer.deleteDocuments(intTerm(FIELD_CHANGE, cd.getId().get()));
+ commit();
+ }
+
+ @Override
+ public ChangeDataSource getSource(Predicate<ChangeData> p)
+ throws QueryParseException {
+ return new QuerySource(toQuery(p));
+ }
+
+ public Directory getDirectory() {
+ return dir;
+ }
+
+ public IndexWriter getWriter() {
+ return writer;
+ }
+
+ private void commit() throws IOException {
+ writer.commit();
+ searcherManager.maybeRefresh();
+ }
+
+ private Query toQuery(Predicate<ChangeData> p) throws QueryParseException {
+ if (p.getClass() == AndPredicate.class) {
+ return booleanQuery(p, MUST);
+ } else if (p.getClass() == OrPredicate.class) {
+ return booleanQuery(p, SHOULD);
+ } else if (p.getClass() == NotPredicate.class) {
+ return booleanQuery(p, MUST_NOT);
+ } else if (p instanceof IndexPredicate) {
+ return fieldQuery((IndexPredicate<ChangeData>) p);
+ } else {
+ throw new QueryParseException("Cannot convert to index predicate: " + p);
+ }
+ }
+
+ private Query booleanQuery(Predicate<ChangeData> p, BooleanClause.Occur o)
+ throws QueryParseException {
+ BooleanQuery q = new BooleanQuery();
+ for (int i = 0; i < p.getChildCount(); i++) {
+ q.add(toQuery(p.getChild(i)), o);
+ }
+ return q;
+ }
+
+ private Query fieldQuery(IndexPredicate<ChangeData> p)
+ throws QueryParseException {
+ if (p.getType() == FieldType.INTEGER) {
+ return intQuery(p);
+ } else if (p.getType() == FieldType.EXACT) {
+ return exactQuery(p);
+ } else {
+ throw badFieldType(p.getType());
+ }
+ }
+
+ private Term intTerm(String name, int value) {
+ BytesRef bytes = new BytesRef(NumericUtils.BUF_SIZE_INT);
+ NumericUtils.intToPrefixCodedBytes(value, 0, bytes);
+ return new Term(name, bytes);
+ }
+
+ private Query intQuery(IndexPredicate<ChangeData> p)
+ throws QueryParseException {
+ int value;
+ try {
+ // Can't use IntPredicate because it and IndexPredicate are different
+ // subclasses of OperatorPredicate.
+ value = Integer.valueOf(p.getValue());
+ } catch (IllegalArgumentException e) {
+ throw new QueryParseException("not an integer: " + p.getValue());
+ }
+ return new TermQuery(intTerm(p.getOperator(), value));
+ }
+
+ private Query exactQuery(IndexPredicate<ChangeData> p) {
+ return new TermQuery(new Term(p.getOperator(), p.getValue()));
+ }
+
+ private class QuerySource implements ChangeDataSource {
+ // TODO(dborowitz): Push limit down from predicate tree.
+ private static final int LIMIT = 1000;
+
+ private final Query query;
+
+ public QuerySource(Query query) {
+ this.query = query;
+ }
+
+ @Override
+ public int getCardinality() {
+ return 10; // TODO(dborowitz): estimate from Lucene?
+ }
+
+ @Override
+ public boolean hasChange() {
+ return false;
+ }
+
+ @Override
+ public ResultSet<ChangeData> read() throws OrmException {
+ try {
+ IndexSearcher searcher = searcherManager.acquire();
+ try {
+ ScoreDoc[] docs = searcher.search(query, LIMIT).scoreDocs;
+ List<ChangeData> result = Lists.newArrayListWithCapacity(docs.length);
+ for (ScoreDoc sd : docs) {
+ Document doc = searcher.doc(sd.doc);
+ Number v = doc.getField(FIELD_CHANGE).numericValue();
+ result.add(new ChangeData(new Change.Id(v.intValue())));
+ }
+ final List<ChangeData> r = Collections.unmodifiableList(result);
+
+ return new ResultSet<ChangeData>() {
+ @Override
+ public Iterator<ChangeData> iterator() {
+ return r.iterator();
+ }
+
+ @Override
+ public List<ChangeData> toList() {
+ return r;
+ }
+
+ @Override
+ public void close() {
+ // Do nothing.
+ }
+ };
+ } finally {
+ searcherManager.release(searcher);
+ }
+ } catch (IOException e) {
+ throw new OrmException(e);
+ }
+ }
+ }
+
+ private Document toDocument(ChangeData cd) throws IOException {
+ try {
+ Document result = new Document();
+ for (FieldDef<ChangeData, ?> f : ChangeField.ALL.values()) {
+ if (f.isRepeatable()) {
+ add(result, f, (Iterable<?>) f.get(cd, fillArgs));
+ } else {
+ add(result, f, Collections.singleton(f.get(cd, fillArgs)));
+ }
+ }
+ return result;
+ } catch (OrmException e) {
+ throw new IOException(e);
+ }
+ }
+
+ private void add(Document doc, FieldDef<ChangeData, ?> f,
+ Iterable<?> values) throws OrmException {
+ if (f.getType() == FieldType.INTEGER) {
+ for (Object value : values) {
+ doc.add(new IntField(f.getName(), (Integer) value, store(f)));
+ }
+ } else if (f.getType() == FieldType.EXACT) {
+ for (Object value : values) {
+ doc.add(new StringField(f.getName(), (String) value, store(f)));
+ }
+ } else {
+ throw badFieldType(f.getType());
+ }
+ }
+
+ private static Field.Store store(FieldDef<?, ?> f) {
+ return f.isStored() ? Field.Store.YES : Field.Store.NO;
+ }
+
+ private static IllegalArgumentException badFieldType(FieldType<?> t) {
+ return new IllegalArgumentException("unknown index field type " + t);
+ }
+}
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndexManager.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndexManager.java
new file mode 100644
index 0000000..1681914
--- /dev/null
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndexManager.java
@@ -0,0 +1,70 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.package com.google.gerrit.server.git;
+
+package com.google.gerrit.lucene;
+
+import com.google.common.base.Throwables;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.index.ChangeIndex;
+import com.google.gerrit.server.index.FieldDef.FillArgs;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+
+@Singleton
+class LuceneChangeIndexManager implements ChangeIndex.Manager,
+ LifecycleListener {
+ private final LoadingCache<String, LuceneChangeIndex> indexes;
+
+ @Inject
+ LuceneChangeIndexManager(final SitePaths sitePaths, final FillArgs fillArgs) {
+ indexes = CacheBuilder.newBuilder().build(
+ new CacheLoader<String, LuceneChangeIndex>() {
+ @Override
+ public LuceneChangeIndex load(String key) throws IOException {
+ return new LuceneChangeIndex(
+ new File(sitePaths.index_dir, key), fillArgs);
+ }
+ });
+ }
+
+ @Override
+ public void start() {
+ // Do nothing.
+ }
+
+ @Override
+ public void stop() {
+ for (LuceneChangeIndex index : indexes.asMap().values()) {
+ index.close();
+ }
+ }
+
+ @Override
+ public LuceneChangeIndex get(String name) throws IOException {
+ try {
+ return indexes.get(name);
+ } catch (ExecutionException e) {
+ Throwables.propagateIfInstanceOf(e.getCause(), IOException.class);
+ throw new IOException(e);
+ }
+ }
+}
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
new file mode 100644
index 0000000..e1fbd76
--- /dev/null
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
@@ -0,0 +1,55 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.package com.google.gerrit.server.git;
+
+package com.google.gerrit.lucene;
+
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.index.ChangeIndex;
+import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.index.ChangeIndexerImpl;
+import com.google.gerrit.server.query.change.IndexRewrite;
+import com.google.gerrit.server.query.change.IndexRewriteImpl;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+
+import org.eclipse.jgit.lib.Config;
+
+public class LuceneIndexModule extends LifecycleModule {
+ public static boolean isEnabled(Injector injector) {
+ return injector.getInstance(Key.get(Config.class, GerritServerConfig.class))
+ .getBoolean("index", null, "enabled", false);
+ }
+
+ private final boolean checkVersion;
+
+ public LuceneIndexModule() {
+ this(true);
+ }
+
+ public LuceneIndexModule(boolean checkVersion) {
+ this.checkVersion = checkVersion;
+ }
+
+ @Override
+ protected void configure() {
+ bind(ChangeIndex.Manager.class).to(LuceneChangeIndexManager.class);
+ bind(ChangeIndexer.class).to(ChangeIndexerImpl.class);
+ bind(IndexRewrite.class).to(IndexRewriteImpl.class);
+ listener().to(LuceneChangeIndexManager.class);
+ if (checkVersion) {
+ listener().to(IndexVersionCheck.class);
+ }
+ }
+}
diff --git a/gerrit-pgm/BUCK b/gerrit-pgm/BUCK
index 0746bdf..dd665db 100644
--- a/gerrit-pgm/BUCK
+++ b/gerrit-pgm/BUCK
@@ -8,6 +8,7 @@
'//gerrit-extension-api:api',
'//gerrit-gwtexpui:server',
'//gerrit-httpd:httpd',
+ '//gerrit-lucene:lucene',
'//gerrit-openid:openid',
'//gerrit-server:common_rules',
'//gerrit-reviewdb:server',
@@ -30,6 +31,7 @@
'//lib/jgit:jgit',
'//lib/log:api',
'//lib/log:log4j',
+ '//lib:lucene-core',
'//lib/mina:sshd',
'//lib/prolog:prolog-cafe',
],
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index ca98a84..221c4a2 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -28,6 +28,7 @@
import com.google.gerrit.httpd.auth.openid.OpenIdModule;
import com.google.gerrit.httpd.plugins.HttpPluginModule;
import com.google.gerrit.lifecycle.LifecycleManager;
+import com.google.gerrit.lucene.LuceneIndexModule;
import com.google.gerrit.pgm.http.jetty.GetUserFilter;
import com.google.gerrit.pgm.http.jetty.JettyEnv;
import com.google.gerrit.pgm.http.jetty.JettyModule;
@@ -50,6 +51,7 @@
import com.google.gerrit.server.contact.HttpContactStoreConnection;
import com.google.gerrit.server.git.ReceiveCommitsExecutorModule;
import com.google.gerrit.server.git.WorkQueue;
+import com.google.gerrit.server.index.NoIndexModule;
import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
import com.google.gerrit.server.mail.SmtpEmailSender;
import com.google.gerrit.server.patch.IntraLineWorkerPool;
@@ -319,6 +321,11 @@
modules.add(new SmtpEmailSender.Module());
modules.add(new SignedTokenEmailTokenVerifier.Module());
modules.add(new PluginModule());
+ if (LuceneIndexModule.isEnabled(cfgInjector)) {
+ modules.add(new LuceneIndexModule());
+ } else {
+ modules.add(new NoIndexModule());
+ }
if (httpd) {
modules.add(new CanonicalWebUrlModule() {
@Override
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
new file mode 100644
index 0000000..97812e4
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
@@ -0,0 +1,152 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.package com.google.gerrit.server.git;
+
+package com.google.gerrit.pgm;
+
+import static com.google.gerrit.lucene.IndexVersionCheck.SCHEMA_VERSIONS;
+import static com.google.gerrit.lucene.IndexVersionCheck.gerritIndexConfig;
+import static com.google.gerrit.lucene.LuceneChangeIndex.LUCENE_VERSION;
+import static com.google.gerrit.server.schema.DataSourceProvider.Context.SINGLE_USER;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.lifecycle.LifecycleManager;
+import com.google.gerrit.lucene.LuceneIndexModule;
+import com.google.gerrit.pgm.util.SiteProgram;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.cache.CacheRemovalListener;
+import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.patch.PatchListCacheImpl;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.AbstractModule;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.Provider;
+import com.google.inject.TypeLiteral;
+
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class Reindex extends SiteProgram {
+ private final LifecycleManager manager = new LifecycleManager();
+ private final AtomicReference<ReviewDb> dbRef =
+ new AtomicReference<ReviewDb>();
+ private Injector dbInjector;
+ private Injector sysInjector;
+ private SitePaths sitePaths;
+
+ @Override
+ public int run() throws Exception {
+ mustHaveValidSite();
+ dbInjector = createDbInjector(SINGLE_USER);
+ if (!LuceneIndexModule.isEnabled(dbInjector)) {
+ throw die("Secondary index not enabled");
+ }
+
+ sitePaths = dbInjector.getInstance(SitePaths.class);
+ deleteAll();
+
+ sysInjector = createSysInjector();
+ manager.add(dbInjector);
+ manager.add(sysInjector);
+ manager.start();
+
+ SchemaFactory<ReviewDb> schema = dbInjector.getInstance(
+ Key.get(new TypeLiteral<SchemaFactory<ReviewDb>>() {}));
+ ReviewDb db = schema.open();
+ dbRef.set(db);
+
+ ChangeIndexer indexer = sysInjector.getInstance(ChangeIndexer.class);
+
+ Stopwatch sw = new Stopwatch().start();
+ int i = 0;
+ for (Change change : db.changes().all()) {
+ indexer.index(change).get();
+ i++;
+ }
+ double elapsed = sw.elapsed(TimeUnit.MILLISECONDS) / 1000d;
+ System.out.format("Reindexed %d changes in %.02fms", i, elapsed);
+ writeVersion();
+
+ manager.stop();
+ return 0;
+ }
+
+ private Injector createSysInjector() {
+ List<Module> modules = Lists.newArrayList();
+ modules.add(PatchListCacheImpl.module());
+ modules.add(new LuceneIndexModule(false));
+ modules.add(new AbstractModule() {
+ @SuppressWarnings("rawtypes")
+ @Override
+ protected void configure() {
+ bind(ReviewDb.class).toProvider(new Provider<ReviewDb>() {
+ @Override
+ public ReviewDb get() {
+ return dbRef.get();
+ }
+ });
+ // Plugins are not loaded and we're just running through each change
+ // once, so don't worry about cache removal.
+ bind(new TypeLiteral<DynamicSet<CacheRemovalListener>>() {})
+ .toInstance(DynamicSet.<CacheRemovalListener> emptySet());
+ install(new DefaultCacheFactory.Module());
+ }
+ });
+ return dbInjector.createChildInjector(modules);
+ }
+
+ private void deleteAll() throws IOException {
+ for (String index : SCHEMA_VERSIONS.keySet()) {
+ File file = new File(sitePaths.index_dir, index);
+ if (file.exists()) {
+ Directory dir = FSDirectory.open(file);
+ try {
+ for (String name : dir.listAll()) {
+ dir.deleteFile(name);
+ }
+ } finally {
+ dir.close();
+ }
+ }
+ }
+ }
+
+ private void writeVersion() throws IOException, ConfigInvalidException {
+ FileBasedConfig cfg =
+ new FileBasedConfig(gerritIndexConfig(sitePaths), FS.detect());
+ cfg.load();
+
+ for (Map.Entry<String, Integer> e : SCHEMA_VERSIONS.entrySet()) {
+ cfg.setInt("index", e.getKey(), "schemaVersion", e.getValue());
+ }
+ cfg.setEnum("lucene", null, "version", LUCENE_VERSION);
+ cfg.save();
+ }
+}
diff --git a/gerrit-server/BUCK b/gerrit-server/BUCK
index 1d676a0..95f5b11 100644
--- a/gerrit-server/BUCK
+++ b/gerrit-server/BUCK
@@ -1,10 +1,13 @@
include_defs('//lib/prolog/DEFS')
+SRCS = glob(['src/main/java/**/*.java'])
+RESOURCES = glob(['src/main/resources/**/*'])
+
# TODO(sop) break up gerrit-server java_library(), its too big
java_library2(
name = 'server',
- srcs = glob(['src/main/java/**/*.java']),
- resources = glob(['src/main/resources/**/*']),
+ srcs = SRCS,
+ resources = RESOURCES,
deps = [
'//gerrit-antlr:query_exception',
'//gerrit-antlr:query_parser',
@@ -51,6 +54,12 @@
visibility = ['PUBLIC'],
)
+java_sources(
+ name = 'server-src',
+ srcs = SRCS + RESOURCES,
+ visibility = ['PUBLIC'],
+)
+
prolog_cafe_library(
name = 'common_rules',
srcs = ['src/main/prolog/gerrit_common.pl'],
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
index f766297..20e7681 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
@@ -28,6 +28,7 @@
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.Abandon.Input;
+import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.mail.AbandonedSender;
import com.google.gerrit.server.mail.ReplyToChangeSender;
import com.google.gerrit.server.project.ChangeControl;
@@ -48,6 +49,7 @@
private final AbandonedSender.Factory abandonedSenderFactory;
private final Provider<ReviewDb> dbProvider;
private final ChangeJson json;
+ private final ChangeIndexer indexer;
public static class Input {
@DefaultInput
@@ -58,11 +60,13 @@
Abandon(ChangeHooks hooks,
AbandonedSender.Factory abandonedSenderFactory,
Provider<ReviewDb> dbProvider,
- ChangeJson json) {
+ ChangeJson json,
+ ChangeIndexer indexer) {
this.hooks = hooks;
this.abandonedSenderFactory = abandonedSenderFactory;
this.dbProvider = dbProvider;
this.json = json;
+ this.indexer = indexer;
}
@Override
@@ -107,6 +111,7 @@
db.rollback();
}
+ indexer.index(change);
try {
ReplyToChangeSender cm = abandonedSenderFactory.create(change);
cm.setFrom(caller.getAccountId());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
index 8a169b8..cc32134 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -29,8 +29,10 @@
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.config.TrackingFooters;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.RefControl;
+import com.google.gerrit.server.util.RequestScopePropagator;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -52,6 +54,7 @@
private final ChangeHooks hooks;
private final ApprovalsUtil approvalsUtil;
private final TrackingFooters trackingFooters;
+ private final ChangeIndexer indexer;
private final RefControl refControl;
private final Change change;
@@ -59,6 +62,7 @@
private final RevCommit commit;
private final PatchSetInfo patchSetInfo;
+ private RequestScopePropagator requestScopePropagator;
private ChangeMessage changeMessage;
private Set<Account.Id> reviewers;
private boolean draft;
@@ -70,6 +74,7 @@
ChangeHooks hooks,
ApprovalsUtil approvalsUtil,
TrackingFooters trackingFooters,
+ ChangeIndexer indexer,
@Assisted RefControl refControl,
@Assisted Change change,
@Assisted RevCommit commit) {
@@ -78,6 +83,7 @@
this.hooks = hooks;
this.approvalsUtil = approvalsUtil;
this.trackingFooters = trackingFooters;
+ this.indexer = indexer;
this.refControl = refControl;
this.change = change;
this.commit = commit;
@@ -98,6 +104,11 @@
ChangeUtil.computeSortKey(change);
}
+ public ChangeInserter setRequestScopePropagator(RequestScopePropagator rsp) {
+ requestScopePropagator = rsp;
+ return this;
+ }
+
public ChangeInserter setMessage(ChangeMessage changeMessage) {
this.changeMessage = changeMessage;
return this;
@@ -140,6 +151,7 @@
db.changeMessages().insert(Collections.singleton(changeMessage));
}
+ indexer.index(change, requestScopePropagator);
gitRefUpdated.fire(change.getProject(), patchSet.getRefName(),
ObjectId.zeroId(), commit);
hooks.doPatchsetCreatedHook(change, patchSet, db);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
index 50e395e..ebee110 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
@@ -30,6 +30,7 @@
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.validators.CommitValidationException;
import com.google.gerrit.server.git.validators.CommitValidators;
+import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.RefControl;
@@ -66,6 +67,7 @@
private final IdentifiedUser user;
private final GitReferenceUpdated gitRefUpdated;
private final CommitValidators.Factory commitValidatorsFactory;
+ private final ChangeIndexer indexer;
private boolean validateForReceiveCommits;
private final Repository git;
@@ -87,6 +89,7 @@
IdentifiedUser user,
GitReferenceUpdated gitRefUpdated,
CommitValidators.Factory commitValidatorsFactory,
+ ChangeIndexer indexer,
@Assisted Repository git,
@Assisted RevWalk revWalk,
@Assisted RefControl refControl,
@@ -99,6 +102,7 @@
this.user = user;
this.gitRefUpdated = gitRefUpdated;
this.commitValidatorsFactory = commitValidatorsFactory;
+ this.indexer = indexer;
this.git = git;
this.revWalk = revWalk;
@@ -212,6 +216,7 @@
db.changeMessages().insert(Collections.singleton(changeMessage));
}
+ indexer.index(change);
hooks.doPatchsetCreatedHook(change, patchSet, db);
} finally {
db.rollback();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
index afb58f9..001ed03 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
@@ -28,6 +28,7 @@
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.Restore.Input;
+import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.mail.ReplyToChangeSender;
import com.google.gerrit.server.mail.RestoredSender;
import com.google.gerrit.server.project.ChangeControl;
@@ -48,6 +49,7 @@
private final RestoredSender.Factory restoredSenderFactory;
private final Provider<ReviewDb> dbProvider;
private final ChangeJson json;
+ private final ChangeIndexer indexer;
public static class Input {
@DefaultInput
@@ -58,11 +60,13 @@
Restore(ChangeHooks hooks,
RestoredSender.Factory restoredSenderFactory,
Provider<ReviewDb> dbProvider,
- ChangeJson json) {
+ ChangeJson json,
+ ChangeIndexer indexer) {
this.hooks = hooks;
this.restoredSenderFactory = restoredSenderFactory;
this.dbProvider = dbProvider;
this.json = json;
+ this.indexer = indexer;
}
@Override
@@ -98,6 +102,7 @@
throw new ResourceConflictException("change is "
+ status(db.changes().get(req.getChange().getId())));
}
+ indexer.index(change);
message = newMessage(input, caller, change);
db.changeMessages().insert(Collections.singleton(message));
new ApprovalsUtil(db).syncChangeStatus(change);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
index 2b51b0a..e236abb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
@@ -36,6 +36,7 @@
import com.google.gerrit.server.change.Submit.Input;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MergeQueue;
+import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gwtorm.server.AtomicUpdate;
import com.google.gwtorm.server.OrmException;
@@ -72,14 +73,17 @@
private final Provider<ReviewDb> dbProvider;
private final GitRepositoryManager repoManager;
private final MergeQueue mergeQueue;
+ private final ChangeIndexer indexer;
@Inject
Submit(Provider<ReviewDb> dbProvider,
GitRepositoryManager repoManager,
- MergeQueue mergeQueue) {
+ MergeQueue mergeQueue,
+ ChangeIndexer indexer) {
this.dbProvider = dbProvider;
this.repoManager = repoManager;
this.mergeQueue = mergeQueue;
+ this.indexer = indexer;
}
@Override
@@ -187,6 +191,7 @@
} finally {
db.rollback();
}
+ indexer.index(change);
return change;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java
index 22eae2d..6ece448 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java
@@ -33,6 +33,7 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.mail.CreateChangeSender;
import com.google.gerrit.server.mail.MailUtil.MailRecipients;
import com.google.gerrit.server.mail.ReplacePatchSetSender;
@@ -40,6 +41,7 @@
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.util.RequestScopePropagator;
import com.google.gwtorm.server.AtomicUpdate;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -75,6 +77,8 @@
private final AccountResolver accountResolver;
private final CreateChangeSender.Factory createChangeSenderFactory;
private final ReplacePatchSetSender.Factory replacePatchSetFactory;
+ private final ChangeIndexer indexer;
+ private final RequestScopePropagator requestScopePropagator;
private final PatchSet.Id patchSetId;
@@ -87,6 +91,8 @@
final AccountResolver accountResolver,
final CreateChangeSender.Factory createChangeSenderFactory,
final ReplacePatchSetSender.Factory replacePatchSetFactory,
+ final ChangeIndexer indexer,
+ final RequestScopePropagator requestScopePropagator,
@Assisted final PatchSet.Id patchSetId) {
this.changeControlFactory = changeControlFactory;
this.db = db;
@@ -97,6 +103,8 @@
this.accountResolver = accountResolver;
this.createChangeSenderFactory = createChangeSenderFactory;
this.replacePatchSetFactory = replacePatchSetFactory;
+ this.indexer = indexer;
+ this.requestScopePropagator = requestScopePropagator;
this.patchSetId = patchSetId;
}
@@ -146,6 +154,7 @@
});
if (!updatedPatchSet.isDraft() || updatedChange.getStatus() == Change.Status.NEW) {
+ indexer.index(updatedChange, requestScopePropagator);
hooks.doDraftPublishedHook(updatedChange, updatedPatchSet, db);
sendNotifications(control.getChange().getStatus() == Change.Status.DRAFT,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
index 3b43fb0..0db8019 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
@@ -35,6 +35,7 @@
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MergeUtil;
+import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.mail.RebasedPatchSetSender;
import com.google.gerrit.server.mail.ReplacePatchSetSender;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
@@ -75,6 +76,7 @@
private final ChangeHookRunner hooks;
private final MergeUtil.Factory mergeUtilFactory;
private final ProjectCache projectCache;
+ private final ChangeIndexer indexer;
@Inject
RebaseChange(final ChangeControl.GenericFactory changeControlFactory,
@@ -85,7 +87,8 @@
final RebasedPatchSetSender.Factory rebasedPatchSetSenderFactory,
final ChangeHookRunner hooks,
final MergeUtil.Factory mergeUtilFactory,
- final ProjectCache projectCache) {
+ final ProjectCache projectCache,
+ final ChangeIndexer changeIndexer) {
this.changeControlFactory = changeControlFactory;
this.patchSetInfoFactory = patchSetInfoFactory;
this.db = db;
@@ -96,6 +99,7 @@
this.hooks = hooks;
this.mergeUtilFactory = mergeUtilFactory;
this.projectCache = projectCache;
+ this.indexer = changeIndexer;
}
/**
@@ -156,10 +160,10 @@
uploader.newCommitterIdent(myIdent.getWhen(),
myIdent.getTimeZone());
- final PatchSet newPatchSet =
- rebase(git, rw, inserter, patchSetId, change, uploader.getAccountId(), baseCommit,
- mergeUtilFactory.create(
- changeControl.getProjectControl().getProjectState(), true), committerIdent);
+ final PatchSet newPatchSet = rebase(git, rw, inserter, patchSetId, change,
+ uploader.getAccountId(), baseCommit, mergeUtilFactory.create(
+ changeControl.getProjectControl().getProjectState(), true),
+ committerIdent, indexer);
final Set<Account.Id> oldReviewers = Sets.newHashSet();
final Set<Account.Id> oldCC = Sets.newHashSet();
@@ -301,6 +305,7 @@
* @param uploader the user that creates the rebased patch set
* @param baseCommit the commit that should be the new base
* @param mergeUtil merge utilities for the destination project
+ * @param indexer helper for indexing the change
* @return the new patch set which is based on the given base commit
* @throws NoSuchChangeException thrown if the change to which the patch set
* belongs does not exist or is not visible to the user
@@ -311,7 +316,8 @@
public PatchSet rebase(final Repository git, final RevWalk revWalk,
final ObjectInserter inserter, final PatchSet.Id patchSetId,
final Change chg, final Account.Id uploader, final RevCommit baseCommit,
- final MergeUtil mergeUtil, PersonIdent committerIdent) throws NoSuchChangeException,
+ final MergeUtil mergeUtil, PersonIdent committerIdent,
+ final ChangeIndexer indexer) throws NoSuchChangeException,
OrmException, IOException, InvalidChangeOperationException,
PathConflictException {
Change change = chg;
@@ -383,6 +389,7 @@
"Change %s was modified", change.getId()));
}
+ indexer.index(change);
ApprovalsUtil.copyLabels(db, projectCache.get(change.getProject())
.getLabelTypes(), patchSetId, change.currentPatchSetId());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java
index 2116c0c..9d7c54a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java
@@ -40,6 +40,7 @@
public final File hooks_dir;
public final File static_dir;
public final File themes_dir;
+ public final File index_dir;
public final File gerrit_sh;
public final File gerrit_war;
@@ -77,6 +78,7 @@
hooks_dir = new File(site_path, "hooks");
static_dir = new File(site_path, "static");
themes_dir = new File(site_path, "themes");
+ index_dir = new File(site_path, "index");
gerrit_sh = new File(bin_dir, "gerrit.sh");
gerrit_war = new File(bin_dir, "gerrit.war");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
index a59a216..bce3d37 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.git;
import static com.google.gerrit.server.git.MergeUtil.getSubmitter;
+
import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
@@ -43,6 +44,7 @@
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.mail.MergeFailSender;
import com.google.gerrit.server.mail.MergedSender;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
@@ -153,6 +155,7 @@
private final WorkQueue workQueue;
private final RequestScopePropagator requestScopePropagator;
private final AllProjectsName allProjectsName;
+ private final ChangeIndexer indexer;
@Inject
MergeOp(final GitRepositoryManager grm, final SchemaFactory<ReviewDb> sf,
@@ -168,7 +171,8 @@
final SubmoduleOp.Factory subOpFactory,
final WorkQueue workQueue,
final RequestScopePropagator requestScopePropagator,
- final AllProjectsName allProjectsName) {
+ final AllProjectsName allProjectsName,
+ final ChangeIndexer indexer) {
repoManager = grm;
schemaFactory = sf;
labelNormalizer = fs;
@@ -188,6 +192,7 @@
this.workQueue = workQueue;
this.requestScopePropagator = requestScopePropagator;
this.allProjectsName = allProjectsName;
+ this.indexer = indexer;
destBranch = branch;
toMerge = ArrayListMultimap.create();
potentiallyStillSubmittable = new ArrayList<CodeReviewCommit>();
@@ -758,6 +763,7 @@
} catch (OrmException err) {
log.warn("Error updating change status for " + c.getId(), err);
}
+ indexer.index(c);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseIfNecessary.java
index 77c4863..0518349 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseIfNecessary.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseIfNecessary.java
@@ -80,7 +80,7 @@
rebaseChange.rebase(args.repo, args.rw, args.inserter,
n.patchsetId, n.change,
args.mergeUtil.getSubmitter(n.patchsetId).getAccountId(),
- newMergeTip, args.mergeUtil, committerIdent);
+ newMergeTip, args.mergeUtil, committerIdent, args.indexer);
List<PatchSetApproval> approvals = Lists.newArrayList();
for (PatchSetApproval a : args.mergeUtil.getApprovalsForCommit(n)) {
approvals.add(new PatchSetApproval(newPatchSet.getId(), a));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index 482a211..8e4ba0c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -74,6 +74,7 @@
import com.google.gerrit.server.git.validators.CommitValidationException;
import com.google.gerrit.server.git.validators.CommitValidationMessage;
import com.google.gerrit.server.git.validators.CommitValidators;
+import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.mail.CreateChangeSender;
import com.google.gerrit.server.mail.MailUtil.MailRecipients;
import com.google.gerrit.server.mail.MergedSender;
@@ -263,6 +264,7 @@
private final WorkQueue workQueue;
private final ListeningExecutorService changeUpdateExector;
private final RequestScopePropagator requestScopePropagator;
+ private final ChangeIndexer indexer;
private final SshInfo sshInfo;
private final AllProjectsName allProjectsName;
private final ReceiveConfig receiveConfig;
@@ -323,6 +325,7 @@
final WorkQueue workQueue,
@ChangeUpdateExecutor ListeningExecutorService changeUpdateExector,
final RequestScopePropagator requestScopePropagator,
+ final ChangeIndexer indexer,
final SshInfo sshInfo,
final AllProjectsName allProjectsName,
ReceiveConfig config,
@@ -353,6 +356,7 @@
this.workQueue = workQueue;
this.changeUpdateExector = changeUpdateExector;
this.requestScopePropagator = requestScopePropagator;
+ this.indexer = indexer;
this.sshInfo = sshInfo;
this.allProjectsName = allProjectsName;
this.receiveConfig = config;
@@ -1470,7 +1474,8 @@
currentUser.getAccountId(),
magicBranch.dest);
change.setTopic(magicBranch.topic);
- ins = changeInserterFactory.create(ctl, change, c);
+ ins = changeInserterFactory.create(ctl, change, c)
+ .setRequestScopePropagator(requestScopePropagator);
cmd = new ReceiveCommand(ObjectId.zeroId(), c,
ins.getPatchSet().getRefName());
}
@@ -1904,6 +1909,7 @@
if (cmd.getResult() == NOT_ATTEMPTED) {
cmd.execute(rp);
}
+ indexer.index(change, requestScopePropagator);
gitRefUpdated.fire(project.getNameKey(), newPatchSet.getRefName(),
ObjectId.zeroId(), newCommit);
hooks.doPatchsetCreatedHook(change, newPatchSet, db);
@@ -2206,7 +2212,7 @@
private void markChangeMergedByPush(final ReviewDb db,
final ReplaceRequest result) throws OrmException {
- final Change change = result.change;
+ Change change = result.change;
final String mergedIntoRef = result.mergedIntoRef;
change.setCurrentPatchSet(result.info);
@@ -2234,17 +2240,19 @@
db.changeMessages().insert(Collections.singleton(msg));
- db.changes().atomicUpdate(change.getId(), new AtomicUpdate<Change>() {
- @Override
- public Change update(Change change) {
- if (change.getStatus().isOpen()) {
- change.setCurrentPatchSet(result.info);
- change.setStatus(Change.Status.MERGED);
- ChangeUtil.updated(change);
- }
- return change;
- }
- });
+ change = db.changes().atomicUpdate(
+ change.getId(), new AtomicUpdate<Change>() {
+ @Override
+ public Change update(Change change) {
+ if (change.getStatus().isOpen()) {
+ change.setCurrentPatchSet(result.info);
+ change.setStatus(Change.Status.MERGED);
+ ChangeUtil.updated(change);
+ }
+ return change;
+ }
+ });
+ indexer.index(change, requestScopePropagator);
}
private void sendMergedEmail(final ReplaceRequest result) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategy.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategy.java
index 7c2ba86..9626e23 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategy.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategy.java
@@ -20,6 +20,7 @@
import com.google.gerrit.reviewdb.client.Project.SubmitType;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.index.ChangeIndexer;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
@@ -55,13 +56,15 @@
protected final Set<RevCommit> alreadyAccepted;
protected final Branch.NameKey destBranch;
protected final MergeUtil mergeUtil;
+ protected final ChangeIndexer indexer;
protected final MergeSorter mergeSorter;
Arguments(final IdentifiedUser.GenericFactory identifiedUserFactory,
final PersonIdent myIdent, final ReviewDb db, final Repository repo,
final RevWalk rw, final ObjectInserter inserter,
final RevFlag canMergeFlag, final Set<RevCommit> alreadyAccepted,
- final Branch.NameKey destBranch, final MergeUtil mergeUtil) {
+ final Branch.NameKey destBranch, final MergeUtil mergeUtil,
+ final ChangeIndexer indexer) {
this.identifiedUserFactory = identifiedUserFactory;
this.myIdent = myIdent;
this.db = db;
@@ -73,6 +76,7 @@
this.alreadyAccepted = alreadyAccepted;
this.destBranch = destBranch;
this.mergeUtil = mergeUtil;
+ this.indexer = indexer;
this.mergeSorter = new MergeSorter(rw, alreadyAccepted, canMergeFlag);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategyFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategyFactory.java
index d43a756..845139d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategyFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategyFactory.java
@@ -22,6 +22,7 @@
import com.google.gerrit.server.changedetail.RebaseChange;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
@@ -54,6 +55,7 @@
private final RebaseChange rebaseChange;
private final ProjectCache projectCache;
private final MergeUtil.Factory mergeUtilFactory;
+ private final ChangeIndexer indexer;
@Inject
SubmitStrategyFactory(
@@ -63,7 +65,8 @@
@CanonicalWebUrl @Nullable final Provider<String> urlProvider,
final GitReferenceUpdated gitRefUpdated, final RebaseChange rebaseChange,
final ProjectCache projectCache,
- final MergeUtil.Factory mergeUtilFactory) {
+ final MergeUtil.Factory mergeUtilFactory,
+ final ChangeIndexer indexer) {
this.identifiedUserFactory = identifiedUserFactory;
this.myIdent = myIdent;
this.patchSetInfoFactory = patchSetInfoFactory;
@@ -71,6 +74,7 @@
this.rebaseChange = rebaseChange;
this.projectCache = projectCache;
this.mergeUtilFactory = mergeUtilFactory;
+ this.indexer = indexer;
}
public SubmitStrategy create(final SubmitType submitType, final ReviewDb db,
@@ -82,7 +86,7 @@
final SubmitStrategy.Arguments args =
new SubmitStrategy.Arguments(identifiedUserFactory, myIdent, db, repo,
rw, inserter, canMergeFlag, alreadyAccepted, destBranch,
- mergeUtilFactory.create(project));
+ mergeUtilFactory.create(project), indexer);
switch (submitType) {
case CHERRY_PICK:
return new CherryPick(args, patchSetInfoFactory, gitRefUpdated);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
new file mode 100644
index 0000000..e52f80d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
@@ -0,0 +1,107 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.package com.google.gerrit.server.git;
+
+package com.google.gerrit.server.index;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder;
+import com.google.gerrit.server.query.change.ChangeStatusPredicate;
+import com.google.gwtorm.server.OrmException;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.util.Map;
+
+/**
+ * Fields indexed on change documents.
+ * <p>
+ * Each field corresponds to both a field name supported by
+ * {@link ChangeQueryBuilder} for querying that field, and a method on
+ * {@link ChangeData} used for populating the corresponding document fields in
+ * the secondary index.
+ * <p>
+ * Used to generate a schema for index implementations that require one.
+ */
+public class ChangeField {
+ /** Increment whenever making schema changes. */
+ public static final int SCHEMA_VERSION = 1;
+
+ /** Legacy change ID. */
+ public static final FieldDef<ChangeData, Integer> CHANGE_ID =
+ new FieldDef.Single<ChangeData, Integer>(ChangeQueryBuilder.FIELD_CHANGE,
+ FieldType.INTEGER, true) {
+ @Override
+ public Integer get(ChangeData input, FillArgs args) {
+ return input.getId().get();
+ }
+ };
+
+ /** Change status string, in the same format as {@code status:}. */
+ public static final FieldDef<ChangeData, String> STATUS =
+ new FieldDef.Single<ChangeData, String>(ChangeQueryBuilder.FIELD_STATUS,
+ FieldType.EXACT, false) {
+ @Override
+ public String get(ChangeData input, FillArgs args)
+ throws OrmException {
+ return ChangeStatusPredicate.VALUES.get(
+ input.change(args.db).getStatus());
+ }
+ };
+
+ /** List of filenames modified in the current patch set. */
+ public static final FieldDef<ChangeData, Iterable<String>> FILE =
+ new FieldDef.Repeatable<ChangeData, String>(
+ ChangeQueryBuilder.FIELD_FILE, FieldType.EXACT, false) {
+ @Override
+ public Iterable<String> get(ChangeData input, FillArgs args)
+ throws OrmException {
+ return input.currentFilePaths(args.db, args.patchListCache);
+ }
+ };
+
+ public static final ImmutableMap<String, FieldDef<ChangeData, ?>> ALL;
+
+ static {
+ Map<String, FieldDef<ChangeData, ?>> fields = Maps.newHashMap();
+ for (Field f : ChangeField.class.getFields()) {
+ if (Modifier.isPublic(f.getModifiers())
+ && Modifier.isStatic(f.getModifiers())
+ && Modifier.isFinal(f.getModifiers())
+ && FieldDef.class.isAssignableFrom(f.getType())) {
+ ParameterizedType t = (ParameterizedType) f.getGenericType();
+ if (t.getActualTypeArguments()[0] == ChangeData.class) {
+ try {
+ @SuppressWarnings("unchecked")
+ FieldDef<ChangeData, ?> fd = (FieldDef<ChangeData, ?>) f.get(null);
+ fields.put(fd.getName(), fd);
+ } catch (IllegalArgumentException e) {
+ throw new ExceptionInInitializerError(e);
+ } catch (IllegalAccessException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ } else {
+ throw new ExceptionInInitializerError(
+ "non-ChangeData ChangeField: " + f);
+ }
+ }
+ }
+ if (fields.isEmpty()) {
+ throw new ExceptionInInitializerError("no ChangeFields found");
+ }
+ ALL = ImmutableMap.copyOf(fields);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java
new file mode 100644
index 0000000..da2e451
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java
@@ -0,0 +1,118 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.package com.google.gerrit.server.git;
+
+package com.google.gerrit.server.index;
+
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangeDataSource;
+
+import java.io.IOException;
+
+/**
+ * Secondary index implementation for change documents.
+ * <p>
+ * {@link ChangeData} objects are inserted into the index and are queried by
+ * converting special {@link com.google.gerrit.server.query.Predicate} instances
+ * into index-aware predicates that use the index search results as a source.
+ * <p>
+ * Implementations must be thread-safe and should batch inserts/updates where
+ * appropriate.
+ */
+public interface ChangeIndex {
+ public static interface Manager {
+ /** Instance indicating secondary index is disabled. */
+ public static final Manager DISABLED = new Manager() {
+ @Override
+ public ChangeIndex get(String name) throws IOException {
+ return new ChangeIndex() {
+ @Override
+ public void insert(ChangeData cd) throws IOException {
+ // Do nothing.
+ }
+
+ @Override
+ public void replace(ChangeData cd) throws IOException {
+ // Do nothing.
+ }
+
+ @Override
+ public void delete(ChangeData cd) throws IOException {
+ // Do nothing.
+ }
+
+ @Override
+ public ChangeDataSource getSource(Predicate<ChangeData> p)
+ throws QueryParseException {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ };
+
+ ChangeIndex get(String name) throws IOException;
+ }
+
+ /**
+ * Insert a change document into the index.
+ * <p>
+ * Results may not be immediately visible to searchers, but should be visible
+ * within a reasonable amount of time.
+ *
+ * @param cd change document with all index fields prepopulated; see
+ * {@link ChangeData#fillIndexFields}.
+ *
+ * @throws IOException if the change could not be inserted.
+ */
+ public void insert(ChangeData cd) throws IOException;
+
+ /**
+ * Update a change document in the index.
+ * <p>
+ * Semantically equivalent to deleting the document and reinserting it with
+ * new field values. Results may not be immediately visible to searchers, but
+ * should be visible within a reasonable amount of time.
+ *
+ * @param cd change document with all index fields prepopulated; see
+ * {@link ChangeData#fillIndexFields}.
+ *
+ * @throws IOException
+ */
+ public void replace(ChangeData cd) throws IOException;
+
+ /**
+ * Delete a change document from the index.
+ *
+ * @param cd change document.
+ *
+ * @throws IOException
+ */
+ public void delete(ChangeData cd) throws IOException;
+
+ /**
+ * Convert the given operator predicate into a source searching the index and
+ * returning only the documents matching that predicate.
+ *
+ * @param p the predicate to match. Must be a tree containing only AND, OR,
+ * or NOT predicates as internal nodes, and {@link IndexPredicate}s as
+ * leaves.
+ * @return a source of documents matching the predicate.
+ *
+ * @throws QueryParseException if the predicate could not be converted to an
+ * indexed data source.
+ */
+ public ChangeDataSource getSource(Predicate<ChangeData> p)
+ throws QueryParseException;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexer.java
new file mode 100644
index 0000000..a76f6ae
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexer.java
@@ -0,0 +1,57 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.package com.google.gerrit.server.git;
+
+package com.google.gerrit.server.index;
+
+import com.google.common.util.concurrent.Futures;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.util.RequestScopePropagator;
+
+import java.util.concurrent.Future;
+
+/**
+ * Helper for (re)indexing a change document.
+ * <p>
+ * Indexing is run in the background, as it may require substantial work to
+ * compute some of the fields and/or update the index.
+ */
+public interface ChangeIndexer {
+ /** Instance indicating secondary index is disabled. */
+ public static final ChangeIndexer DISABLED = new ChangeIndexer() {
+ @Override
+ public Future<?> index(Change change) {
+ return Futures.immediateFuture(null);
+ }
+
+ @Override
+ public Future<?> index(Change change, RequestScopePropagator prop) {
+ return Futures.immediateFuture(null);
+ }
+ };
+
+ /**
+ * Start indexing a change.
+ *
+ * @param change change to index.
+ */
+ public Future<?> index(Change change);
+
+ /**
+ * Start indexing a change.
+ *
+ * @param change change to index.
+ * @param prop propagator to wrap any created runnables in.
+ */
+ public Future<?> index(Change change, RequestScopePropagator prop);
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexerImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexerImpl.java
new file mode 100644
index 0000000..43760f9
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexerImpl.java
@@ -0,0 +1,93 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.package com.google.gerrit.server.git;
+
+package com.google.gerrit.server.index;
+
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.git.WorkQueue;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.util.RequestScopePropagator;
+import com.google.inject.Inject;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.concurrent.Future;
+
+/**
+ * Helper for (re)indexing a change document.
+ * <p>
+ * Indexing is run in the background, as it may require substantial work to
+ * compute some of the fields and/or update the index.
+ */
+public class ChangeIndexerImpl implements ChangeIndexer {
+ private static final Logger log =
+ LoggerFactory.getLogger(ChangeIndexerImpl.class);
+
+ private final WorkQueue workQueue;
+ private final ChangeIndex openIndex;
+ private final ChangeIndex closedIndex;
+
+ @Inject
+ ChangeIndexerImpl(WorkQueue workQueue,
+ ChangeIndex.Manager indexManager) throws IOException {
+ this.workQueue = workQueue;
+ this.openIndex = indexManager.get("changes_open");
+ this.closedIndex = indexManager.get("changes_closed");
+ }
+
+ @Override
+ public Future<?> index(Change change) {
+ return index(change, null);
+ }
+
+ @Override
+ public Future<?> index(Change change, RequestScopePropagator prop) {
+ Runnable task = new Task(change);
+ if (prop != null) {
+ task = prop.wrap(task);
+ }
+ return workQueue.getDefaultQueue().submit(task);
+ }
+
+ private class Task implements Runnable {
+ private final Change change;
+
+ private Task(Change change) {
+ this.change = change;
+ }
+
+ @Override
+ public void run() {
+ ChangeData cd = new ChangeData(change);
+ try {
+ if (change.getStatus().isOpen()) {
+ closedIndex.delete(cd);
+ openIndex.replace(cd);
+ } else {
+ openIndex.delete(cd);
+ closedIndex.replace(cd);
+ }
+ } catch (IOException e) {
+ log.error("Error indexing change", e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "index-change-" + change.getId().get();
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java
new file mode 100644
index 0000000..7c0fd81
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java
@@ -0,0 +1,112 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.package com.google.gerrit.server.git;
+
+package com.google.gerrit.server.index;
+
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+/**
+ * Definition of a field stored in the secondary index.
+ *
+ * @param I input type from which documents are created and search results are
+ * returned.
+ * @param T type that should be extracted from the input object when converting
+ * to an index document.
+ */
+public abstract class FieldDef<I, T> {
+ /** Definition of a single (non-repeatable) field. */
+ public static abstract class Single<I, T> extends FieldDef<I, T> {
+ Single(String name, FieldType<T> type, boolean stored) {
+ super(name, type, stored);
+ }
+
+ @Override
+ public final boolean isRepeatable() {
+ return false;
+ }
+ }
+
+ /** Definition of a repeatable field. */
+ public static abstract class Repeatable<I, T>
+ extends FieldDef<I, Iterable<T>> {
+ Repeatable(String name, FieldType<T> type, boolean stored) {
+ super(name, type, stored);
+ }
+
+ @Override
+ public final boolean isRepeatable() {
+ return true;
+ }
+ }
+
+ /** Arguments needed to fill in missing data in the input object. */
+ public static class FillArgs {
+ final Provider<ReviewDb> db;
+ final PatchListCache patchListCache;
+
+ @Inject
+ FillArgs(Provider<ReviewDb> db,
+ PatchListCache patchListCache) {
+ this.db = db;
+ this.patchListCache = patchListCache;
+ }
+ }
+
+ private final String name;
+ private final FieldType<?> type;
+ private final boolean stored;
+
+ private FieldDef(String name, FieldType<?> type, boolean stored) {
+ this.name = name;
+ this.type = type;
+ this.stored = stored;
+ }
+
+ /** @return name of the field. */
+ public final String getName() {
+ return name;
+ }
+
+ /**
+ * @return type of the field; for repeatable fields, the inner type, not the
+ * iterable type.
+ */
+ public final FieldType<?> getType() {
+ return type;
+ }
+
+ /** @return whether the field should be stored in the index. */
+ public final boolean isStored() {
+ return stored;
+ }
+
+ /**
+ * Get the field contents from the input object.
+ *
+ * @param input input object.
+ * @param args arbitrary arguments needed to fill in indexable fields of the
+ * input object.
+ * @return the field value(s) to index.
+ *
+ * @throws OrmException
+ */
+ public abstract T get(I input, FillArgs args) throws OrmException;
+
+ /** @return whether the field is repeatable. */
+ public abstract boolean isRepeatable();
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java
new file mode 100644
index 0000000..ec4808b
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java
@@ -0,0 +1,42 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.package com.google.gerrit.server.git;
+
+package com.google.gerrit.server.index;
+
+
+/** Document field types supported by the secondary index system. */
+public class FieldType<T> {
+ /** A single integer-valued field. */
+ public static final FieldType<Integer> INTEGER =
+ new FieldType<Integer>("INTEGER");
+
+ /** A string field searched using exact-match semantics. */
+ public static final FieldType<String> EXACT =
+ new FieldType<String>("EXACT");
+
+ private final String name;
+
+ private FieldType(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexPredicate.java
new file mode 100644
index 0000000..605582a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexPredicate.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.package com.google.gerrit.server.git;
+
+package com.google.gerrit.server.index;
+
+import com.google.gerrit.server.query.OperatorPredicate;
+
+/** Index-aware predicate that includes a field type annotation. */
+public abstract class IndexPredicate<I> extends OperatorPredicate<I> {
+ private final FieldDef<I, ?> def;
+
+ public IndexPredicate(FieldDef<I, ?> def, String value) {
+ super(def.getName(), value);
+ this.def = def;
+ }
+
+ public FieldType<?> getType() {
+ return def.getType();
+ }
+
+ /**
+ * @return whether this predicate can only be satisfied by looking at the
+ * secondary index, i.e. it cannot be expressed as a query over the DB.
+ */
+ public boolean isIndexOnly() {
+ return false;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/NoIndexModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/NoIndexModule.java
new file mode 100644
index 0000000..fb7fbe6
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/NoIndexModule.java
@@ -0,0 +1,31 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.package com.google.gerrit.server.git;
+
+package com.google.gerrit.server.index;
+
+import com.google.gerrit.server.query.change.IndexRewrite;
+import com.google.inject.AbstractModule;
+
+public class NoIndexModule extends AbstractModule {
+ // TODO(dborowitz): This module should go away when the index becomes
+ // obligatory, as should the interfaces that exist only to support the
+ // non-index case.
+
+ @Override
+ protected void configure() {
+ bind(ChangeIndex.Manager.class).toInstance(ChangeIndex.Manager.DISABLED);
+ bind(ChangeIndexer.class).toInstance(ChangeIndexer.DISABLED);
+ bind(IndexRewrite.class).toInstance(IndexRewrite.DISABLED);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/PredicateWrapper.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/PredicateWrapper.java
new file mode 100644
index 0000000..f635da6
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/PredicateWrapper.java
@@ -0,0 +1,139 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.package com.google.gerrit.server.git;
+
+package com.google.gerrit.server.index;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangeDataSource;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Wrapper combining an {@link IndexPredicate} together with a
+ * {@link ChangeDataSource} that returns matching results from the index.
+ * <p>
+ * Appropriate to return as the rootmost predicate that can be processed using
+ * the secondary index; such predicates must also implement
+ * {@link ChangeDataSource} to be chosen by the query processor.
+ */
+public class PredicateWrapper extends Predicate<ChangeData> implements
+ ChangeDataSource {
+ private final Predicate<ChangeData> pred;
+ private final List<ChangeDataSource> sources;
+
+ public PredicateWrapper(Predicate<ChangeData> pred, ChangeIndex index)
+ throws QueryParseException {
+ this(pred, ImmutableList.of(index));
+ }
+
+ public PredicateWrapper(Predicate<ChangeData> pred,
+ Collection<ChangeIndex> indexes) throws QueryParseException {
+ this.pred = pred;
+ sources = Lists.newArrayListWithCapacity(indexes.size());
+ for (ChangeIndex index : indexes) {
+ sources.add(index.getSource(pred));
+ }
+ }
+
+ @Override
+ public int getCardinality() {
+ int n = 0;
+ for (ChangeDataSource source : sources) {
+ n += source.getCardinality();
+ }
+ return n;
+ }
+
+ @Override
+ public boolean hasChange() {
+ for (ChangeDataSource source : sources) {
+ if (!source.hasChange()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public ResultSet<ChangeData> read() throws OrmException {
+ final List<ResultSet<ChangeData>> results =
+ Lists.newArrayListWithCapacity(sources.size());
+ for (ChangeDataSource source : sources) {
+ results.add(source.read());
+ }
+ return new ResultSet<ChangeData>() {
+ @Override
+ public Iterator<ChangeData> iterator() {
+ // TODO(dborowitz): May return duplicates since moving a document
+ // between indexes is not atomic.
+ return Iterables.concat(results).iterator();
+ }
+
+ @Override
+ public List<ChangeData> toList() {
+ return Collections.unmodifiableList(Lists.newArrayList(iterator()));
+ }
+
+ @Override
+ public void close() {
+ for (ResultSet<ChangeData> rs : results) {
+ rs.close();
+ }
+ }
+ };
+ }
+
+ @Override
+ public Predicate<ChangeData> copy(
+ Collection<? extends Predicate<ChangeData>> children) {
+ return this;
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ return pred.match(cd);
+ }
+
+ @Override
+ public int getCost() {
+ return pred.getCost();
+ }
+
+ @Override
+ public int hashCode() {
+ return pred.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other != null
+ && getClass() == other.getClass()
+ && pred.equals(((PredicateWrapper) other).pred);
+ }
+
+ @Override
+ public String toString() {
+ return "index(" + pred + ")";
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
index 84304b8..69cc947 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
@@ -24,8 +24,8 @@
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.gerrit.reviewdb.client.AccountProjectWatch;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
@@ -202,7 +202,7 @@
}
if (filter != null) {
- qb.setAllowFile(true);
+ qb.setAllowFileRegex(true);
Predicate<ChangeData> filterPredicate = qb.parse(filter);
if (p == null) {
p = filterPredicate;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index e74172e..08dc5a0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -30,6 +30,7 @@
import com.google.gerrit.server.account.GroupBackends;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.index.ChangeIndex;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectCache;
@@ -113,6 +114,7 @@
final PatchListCache patchListCache;
final GitRepositoryManager repoManager;
final ProjectCache projectCache;
+ final ChangeIndex.Manager indexManager;
@Inject
Arguments(Provider<ReviewDb> dbProvider,
@@ -125,7 +127,8 @@
AllProjectsName allProjectsName,
PatchListCache patchListCache,
GitRepositoryManager repoManager,
- ProjectCache projectCache) {
+ ProjectCache projectCache,
+ ChangeIndex.Manager indexManager) {
this.dbProvider = dbProvider;
this.rewriter = rewriter;
this.userFactory = userFactory;
@@ -137,6 +140,7 @@
this.patchListCache = patchListCache;
this.repoManager = repoManager;
this.projectCache = projectCache;
+ this.indexManager = indexManager;
}
}
@@ -146,7 +150,7 @@
private final Arguments args;
private final CurrentUser currentUser;
- private boolean allowsFile;
+ private boolean allowFileRegex;
@Inject
ChangeQueryBuilder(Arguments args, @Assisted CurrentUser currentUser) {
@@ -155,8 +159,8 @@
this.currentUser = currentUser;
}
- public void setAllowFile(boolean on) {
- allowsFile = on;
+ public void setAllowFileRegex(boolean on) {
+ allowFileRegex = on;
}
@Operator
@@ -284,15 +288,20 @@
@Operator
public Predicate<ChangeData> file(String file) throws QueryParseException {
- if (!allowsFile) {
- throw error("operator not permitted here: file:" + file);
+ if (allowFileRegex) {
+ if (file.startsWith("^")) {
+ return new RegexFilePredicate(args.dbProvider, args.patchListCache, file);
+ } else {
+ throw new IllegalArgumentException();
+ }
+ } else {
+ if (!file.startsWith("^")
+ && args.indexManager != ChangeIndex.Manager.DISABLED) {
+ return new EqualsFilePredicate(args.dbProvider, args.patchListCache, file);
+ } else {
+ throw error("regular expression not permitted here: file:" + file);
+ }
}
-
- if (file.startsWith("^")) {
- return new RegexFilePredicate(args.dbProvider, args.patchListCache, file);
- }
-
- throw new IllegalArgumentException();
}
@Operator
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
index e6251bc..6760b04 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
@@ -40,14 +40,17 @@
new InvalidProvider<ReviewDb>(), //
new InvalidProvider<ChangeQueryRewriter>(), //
null, null, null, null, null, //
- null, null, null, null), null));
+ null, null, null, null, null), null));
private final Provider<ReviewDb> dbProvider;
+ private final IndexRewrite indexRewrite;
@Inject
- ChangeQueryRewriter(Provider<ReviewDb> dbProvider) {
+ ChangeQueryRewriter(Provider<ReviewDb> dbProvider,
+ IndexRewrite indexRewrite) {
super(mydef);
this.dbProvider = dbProvider;
+ this.indexRewrite = indexRewrite;
}
@Override
@@ -60,6 +63,11 @@
return hasSource(l) ? new OrSource(l) : super.or(l);
}
+ @Override
+ public Predicate<ChangeData> rewrite(Predicate<ChangeData> in) {
+ return super.rewrite(indexRewrite.rewrite(in));
+ }
+
@Rewrite("-status:open")
@NoCostComputation
public Predicate<ChangeData> r00_notOpen() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
index cb64801..6d08db0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
@@ -19,7 +19,8 @@
import com.google.common.collect.ImmutableBiMap;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IndexPredicate;
import com.google.gerrit.server.query.Predicate;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Provider;
@@ -34,8 +35,8 @@
* status:} but may also be {@code is:} to help do-what-i-meanery for end-users
* searching for changes. Either operator name has the same meaning.
*/
-final class ChangeStatusPredicate extends OperatorPredicate<ChangeData> {
- private static final ImmutableBiMap<Change.Status, String> VALUES;
+public final class ChangeStatusPredicate extends IndexPredicate<ChangeData> {
+ public static final ImmutableBiMap<Change.Status, String> VALUES;
static {
ImmutableBiMap.Builder<Change.Status, String> values =
@@ -70,14 +71,14 @@
private final Change.Status status;
ChangeStatusPredicate(Provider<ReviewDb> dbProvider, String value) {
- super(ChangeQueryBuilder.FIELD_STATUS, value);
+ super(ChangeField.STATUS, value);
this.dbProvider = dbProvider;
status = VALUES.inverse().get(value);
checkArgument(status != null, "invalid change status: %s", value);
}
ChangeStatusPredicate(Provider<ReviewDb> dbProvider, Change.Status status) {
- super(ChangeQueryBuilder.FIELD_STATUS, VALUES.get(status));
+ super(ChangeField.STATUS, VALUES.get(status));
this.dbProvider = dbProvider;
this.status = status;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsFilePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsFilePredicate.java
new file mode 100644
index 0000000..dd8c970
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsFilePredicate.java
@@ -0,0 +1,62 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.package com.google.gerrit.server.git;
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Provider;
+
+import java.util.Collections;
+import java.util.List;
+
+class EqualsFilePredicate extends IndexPredicate<ChangeData> {
+ private final Provider<ReviewDb> db;
+ private final PatchListCache cache;
+ private final String value;
+
+ EqualsFilePredicate(Provider<ReviewDb> db, PatchListCache plc, String value) {
+ super(ChangeField.FILE, value);
+ this.db = db;
+ this.cache = plc;
+ this.value = value;
+ }
+
+ @Override
+ public boolean match(ChangeData object) throws OrmException {
+ List<String> files = object.currentFilePaths(db, cache);
+ if (files != null) {
+ return Collections.binarySearch(files, value) >= 0;
+ } else {
+ // The ChangeData can't do expensive lookups right now. Bypass
+ // them and include the result anyway. We might be able to do
+ // a narrow later on to a smaller set.
+ //
+ return true;
+ }
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+
+ @Override
+ public boolean isIndexOnly() {
+ return true;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IndexRewrite.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IndexRewrite.java
new file mode 100644
index 0000000..f3bca64
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IndexRewrite.java
@@ -0,0 +1,37 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.package com.google.gerrit.server.git;
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.server.query.Predicate;
+
+public interface IndexRewrite {
+ /** Instance indicating secondary index is disabled. */
+ public static final IndexRewrite DISABLED = new IndexRewrite() {
+ @Override
+ public Predicate<ChangeData> rewrite(Predicate<ChangeData> in) {
+ return in;
+ }
+ };
+
+ /**
+ * Rewrite a predicate to push as much boolean logic as possible into the
+ * secondary index query system.
+ *
+ * @param in predicate to rewrite.
+ * @return a predicate with some subtrees replaced with predicates that are
+ * also sources that query the index directly.
+ */
+ public Predicate<ChangeData> rewrite(Predicate<ChangeData> in);
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IndexRewriteImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IndexRewriteImpl.java
new file mode 100644
index 0000000..48f2801
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IndexRewriteImpl.java
@@ -0,0 +1,235 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.package com.google.gerrit.server.git;
+
+package com.google.gerrit.server.query.change;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.index.ChangeIndex;
+import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gerrit.server.index.PredicateWrapper;
+import com.google.gerrit.server.query.AndPredicate;
+import com.google.gerrit.server.query.NotPredicate;
+import com.google.gerrit.server.query.OrPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.inject.Inject;
+
+import java.io.IOException;
+import java.util.BitSet;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+
+/** Rewriter that pushes boolean logic into the secondary index. */
+public class IndexRewriteImpl implements IndexRewrite {
+ private static final Set<Change.Status> OPEN_STATUSES;
+ private static final Set<Change.Status> CLOSED_STATUSES;
+
+ static {
+ EnumSet<Change.Status> open = EnumSet.noneOf(Change.Status.class);
+ EnumSet<Change.Status> closed = EnumSet.noneOf(Change.Status.class);
+ for (Change.Status s : Change.Status.values()) {
+ if (s.isOpen()) {
+ open.add(s);
+ } else {
+ closed.add(s);
+ }
+ }
+ OPEN_STATUSES = Sets.immutableEnumSet(open);
+ CLOSED_STATUSES = Sets.immutableEnumSet(closed);
+ }
+
+ private final ChangeIndex openIndex;
+ private final ChangeIndex closedIndex;
+
+ @Inject
+ IndexRewriteImpl(ChangeIndex.Manager indexManager) throws IOException {
+ this(indexManager.get("changes_open"), indexManager.get("changes_closed"));
+ }
+
+ @VisibleForTesting
+ IndexRewriteImpl(ChangeIndex openIndex, ChangeIndex closedIndex) {
+ this.openIndex = openIndex;
+ this.closedIndex = closedIndex;
+ }
+
+ @Override
+ public Predicate<ChangeData> rewrite(Predicate<ChangeData> in) {
+ Predicate<ChangeData> out = rewriteImpl(in);
+ if (out == null) {
+ return in;
+ } else if (out == in) {
+ return wrap(out);
+ } else {
+ return out;
+ }
+ }
+
+ /**
+ * Rewrite a single predicate subtree.
+ *
+ * @param in predicate to rewrite.
+ * @return {@code null} if no part of this subtree can be queried in the
+ * index directly. {@code in} if this subtree and all its children can be
+ * queried directly in the index. Otherwise, a predicate that is
+ * semantically equivalent, with some of its subtrees wrapped to query the
+ * index directly.
+ */
+ private Predicate<ChangeData> rewriteImpl(Predicate<ChangeData> in) {
+ if (in instanceof IndexPredicate) {
+ return in;
+ }
+ if (!isRewritePossible(in)) {
+ return null;
+ }
+ int n = in.getChildCount();
+ BitSet toKeep = new BitSet(n);
+ BitSet toWrap = new BitSet(n);
+ BitSet rewritten = new BitSet(n);
+ List<Predicate<ChangeData>> newChildren = Lists.newArrayListWithCapacity(n);
+ for (int i = 0; i < n; i++) {
+ Predicate<ChangeData> c = in.getChild(i);
+ Predicate<ChangeData> nc = rewriteImpl(c);
+ if (nc == null) {
+ toKeep.set(i);
+ newChildren.add(c);
+ } else if (nc == c) {
+ toWrap.set(i);
+ newChildren.add(nc);
+ } else {
+ rewritten.set(i);
+ newChildren.add(nc);
+ }
+ }
+ if (toKeep.cardinality() == n) {
+ return null; // Can't rewrite any children.
+ }
+ if (rewritten.cardinality() == n) {
+ // All children were partially, but not fully, rewritten.
+ return in.copy(newChildren);
+ }
+ if (toWrap.cardinality() == n) {
+ // All children can be fully rewritten, push work to parent.
+ return in;
+ }
+ return partitionChildren(in, newChildren, toWrap);
+ }
+
+
+ private Predicate<ChangeData> partitionChildren(Predicate<ChangeData> in,
+ List<Predicate<ChangeData>> newChildren, BitSet toWrap) {
+ if (toWrap.cardinality() == 1) {
+ int i = toWrap.nextSetBit(0);
+ newChildren.set(i, wrap(newChildren.get(i)));
+ return in.copy(newChildren);
+ }
+
+ // Group all toWrap predicates into a wrapped subtree and place it as a
+ // sibling of the non-/partially-wrapped predicates. Assumes partitioning
+ // the children into arbitrary subtrees of the same type is logically
+ // equivalent to having them as siblings.
+ List<Predicate<ChangeData>> wrapped = Lists.newArrayListWithCapacity(
+ toWrap.cardinality());
+ List<Predicate<ChangeData>> all = Lists.newArrayListWithCapacity(
+ newChildren.size() - toWrap.cardinality() + 1);
+ for (int i = 0; i < newChildren.size(); i++) {
+ Predicate<ChangeData> child = newChildren.get(i);
+ if (toWrap.get(i)) {
+ wrapped.add(child);
+ if (allNonIndexOnly(child)) {
+ // Duplicate non-index-only predicate subtrees alongside the wrapped
+ // subtrees so they can provide index hints to the DB-based rewriter.
+ all.add(child);
+ }
+ } else {
+ all.add(child);
+ }
+ }
+ all.add(wrap(in.copy(wrapped)));
+ return in.copy(all);
+ }
+
+ private static boolean allNonIndexOnly(Predicate<ChangeData> p) {
+ if (p instanceof IndexPredicate) {
+ return !((IndexPredicate<ChangeData>) p).isIndexOnly();
+ }
+ if (p instanceof AndPredicate
+ || p instanceof OrPredicate
+ || p instanceof NotPredicate) {
+ for (int i = 0; i < p.getChildCount(); i++) {
+ if (!allNonIndexOnly(p.getChild(i))) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ return true;
+ }
+ }
+
+ @VisibleForTesting
+ static EnumSet<Change.Status> getPossibleStatus(Predicate<ChangeData> in) {
+ if (in instanceof ChangeStatusPredicate) {
+ return EnumSet.of(((ChangeStatusPredicate) in).getStatus());
+ } else if (in.getClass() == NotPredicate.class) {
+ return EnumSet.complementOf(getPossibleStatus(in.getChild(0)));
+ } else if (in.getClass() == OrPredicate.class) {
+ EnumSet<Change.Status> s = EnumSet.noneOf(Change.Status.class);
+ for (int i = 0; i < in.getChildCount(); i++) {
+ s.addAll(getPossibleStatus(in.getChild(i)));
+ }
+ return s;
+ } else if (in.getClass() == AndPredicate.class) {
+ EnumSet<Change.Status> s = EnumSet.allOf(Change.Status.class);
+ for (int i = 0; i < in.getChildCount(); i++) {
+ s.retainAll(getPossibleStatus(in.getChild(i)));
+ }
+ return s;
+ } else if (in.getChildCount() == 0) {
+ return EnumSet.allOf(Change.Status.class);
+ } else {
+ throw new IllegalStateException(
+ "Invalid predicate type in change index query: " + in.getClass());
+ }
+ }
+
+ private PredicateWrapper wrap(Predicate<ChangeData> p) {
+ try {
+ Set<Change.Status> possibleStatus = getPossibleStatus(p);
+ List<ChangeIndex> indexes = Lists.newArrayListWithCapacity(2);
+ if (!Sets.intersection(possibleStatus, OPEN_STATUSES).isEmpty()) {
+ indexes.add(openIndex);
+ }
+ if (!Sets.intersection(possibleStatus, CLOSED_STATUSES).isEmpty()) {
+ indexes.add(closedIndex);
+ }
+ return new PredicateWrapper(p, indexes);
+ } catch (QueryParseException e) {
+ throw new IllegalStateException(
+ "Failed to convert " + p + " to index predicate", e);
+ }
+ }
+
+ private static boolean isRewritePossible(Predicate<ChangeData> p) {
+ if (p.getClass() != AndPredicate.class
+ && p.getClass() != OrPredicate.class
+ && p.getClass() != NotPredicate.class) {
+ return false;
+ }
+ return p.getChildCount() > 0;
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/IndexRewriteTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/IndexRewriteTest.java
new file mode 100644
index 0000000..877d3d6
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/IndexRewriteTest.java
@@ -0,0 +1,217 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.package com.google.gerrit.server.git;
+
+package com.google.gerrit.server.query.change;
+
+import static com.google.gerrit.reviewdb.client.Change.Status.ABANDONED;
+import static com.google.gerrit.reviewdb.client.Change.Status.DRAFT;
+import static com.google.gerrit.reviewdb.client.Change.Status.MERGED;
+import static com.google.gerrit.reviewdb.client.Change.Status.NEW;
+import static com.google.gerrit.reviewdb.client.Change.Status.SUBMITTED;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.index.ChangeIndex;
+import com.google.gerrit.server.index.PredicateWrapper;
+import com.google.gerrit.server.query.AndPredicate;
+import com.google.gerrit.server.query.OrPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.Set;
+
+@SuppressWarnings("unchecked")
+public class IndexRewriteTest extends TestCase {
+ private static class DummyIndex implements ChangeIndex {
+ @Override
+ public void insert(ChangeData cd) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void replace(ChangeData cd) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void delete(ChangeData cd) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ChangeDataSource getSource(Predicate<ChangeData> p)
+ throws QueryParseException {
+ return new Source();
+ }
+ }
+
+ private static class Source implements ChangeDataSource {
+ @Override
+ public int getCardinality() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean hasChange() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ResultSet<ChangeData> read() throws OrmException {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private DummyIndex openIndex;
+ private DummyIndex closedIndex;
+ private ChangeQueryBuilder queryBuilder;
+ private IndexRewrite rewrite;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ openIndex = new DummyIndex();
+ closedIndex = new DummyIndex();
+ queryBuilder = new ChangeQueryBuilder(
+ new ChangeQueryBuilder.Arguments(null, null, null, null, null, null,
+ null, null, null, null, null, null),
+ null);
+
+ rewrite = new IndexRewriteImpl(openIndex, closedIndex);
+ }
+
+ public void testIndexPredicate() throws Exception {
+ Predicate<ChangeData> in = parse("file:a");
+ assertEquals(wrap(in), rewrite(in));
+ }
+
+ public void testNonIndexPredicate() throws Exception {
+ Predicate<ChangeData> in = parse("branch:a");
+ assertSame(in, rewrite(in));
+ }
+
+ public void testIndexPredicates() throws Exception {
+ Predicate<ChangeData> in = parse("file:a file:b");
+ assertEquals(wrap(in), rewrite(in));
+ }
+
+ public void testNonIndexPredicates() throws Exception {
+ Predicate<ChangeData> in = parse("branch:a OR branch:b");
+ assertSame(in, rewrite(in));
+ }
+
+ public void testOneIndexPredicate() throws Exception {
+ Predicate<ChangeData> in = parse("branch:a file:b");
+ Predicate<ChangeData> out = rewrite(in);
+ assertSame(AndPredicate.class, out.getClass());
+ assertEquals(ImmutableList.of(in.getChild(0), wrap(in.getChild(1))),
+ out.getChildren());
+ }
+
+ public void testThreeLevelTreeWithAllIndexPredicates() throws Exception {
+ Predicate<ChangeData> in =
+ parse("-status:abandoned (status:open OR status:merged)");
+ assertEquals(wrap(in), rewrite.rewrite(in));
+ }
+
+ public void testThreeLevelTreeWithSomeIndexPredicates() throws Exception {
+ Predicate<ChangeData> in = parse("-branch:a (file:b OR file:c)");
+ Predicate<ChangeData> out = rewrite(in);
+ assertEquals(AndPredicate.class, out.getClass());
+ assertEquals(ImmutableList.of(in.getChild(0), wrap(in.getChild(1))),
+ out.getChildren());
+ }
+
+ public void testMultipleIndexPredicates() throws Exception {
+ Predicate<ChangeData> in =
+ parse("file:a OR branch:b OR file:c OR branch:d");
+ Predicate<ChangeData> out = rewrite(in);
+ assertSame(OrPredicate.class, out.getClass());
+ assertEquals(ImmutableList.of(
+ in.getChild(1), in.getChild(3),
+ wrap(Predicate.or(in.getChild(0), in.getChild(2)))),
+ out.getChildren());
+ }
+
+ public void testDuplicateSimpleNonIndexOnlyPredicates() throws Exception {
+ Predicate<ChangeData> in = parse("status:new project:p file:a");
+ Predicate<ChangeData> out = rewrite(in);
+ assertSame(AndPredicate.class, out.getClass());
+ assertEquals(ImmutableList.of(
+ in.getChild(0), in.getChild(1),
+ wrap(Predicate.and(in.getChild(0), in.getChild(2)))),
+ out.getChildren());
+ }
+
+ public void testDuplicateCompoundNonIndexOnlyPredicates() throws Exception {
+ Predicate<ChangeData> in =
+ parse("(status:new OR status:draft) project:p file:a");
+ Predicate<ChangeData> out = rewrite(in);
+ assertSame(AndPredicate.class, out.getClass());
+ assertEquals(ImmutableList.of(
+ in.getChild(0), in.getChild(1),
+ wrap(Predicate.and(in.getChild(0), in.getChild(2)))),
+ out.getChildren());
+ }
+
+ public void testDuplicateCompoundIndexOnlyPredicates() throws Exception {
+ Predicate<ChangeData> in =
+ parse("(status:new OR file:a) project:p file:b");
+ Predicate<ChangeData> out = rewrite(in);
+ assertSame(AndPredicate.class, out.getClass());
+ assertEquals(ImmutableList.of(
+ in.getChild(1),
+ wrap(Predicate.and(in.getChild(0), in.getChild(2)))),
+ out.getChildren());
+ }
+
+ public void testGetPossibleStatus() throws Exception {
+ assertEquals(EnumSet.allOf(Change.Status.class), status("file:a"));
+ assertEquals(EnumSet.of(NEW), status("is:new"));
+ assertEquals(EnumSet.of(SUBMITTED, DRAFT, MERGED, ABANDONED),
+ status("-is:new"));
+ assertEquals(EnumSet.of(NEW, MERGED), status("is:new OR is:merged"));
+
+ EnumSet<Change.Status> none = EnumSet.noneOf(Change.Status.class);
+ assertEquals(none, status("is:new is:merged"));
+ assertEquals(none, status("(is:new is:draft) (is:merged is:submitted)"));
+ assertEquals(none, status("(is:new is:draft) (is:merged is:submitted)"));
+
+ assertEquals(EnumSet.of(MERGED, SUBMITTED),
+ status("(is:new is:draft) OR (is:merged OR is:submitted)"));
+ }
+
+ private Predicate<ChangeData> parse(String query) throws QueryParseException {
+ return queryBuilder.parse(query);
+ }
+
+ private Predicate<ChangeData> rewrite(Predicate<ChangeData> in) {
+ return rewrite.rewrite(in);
+ }
+
+ private PredicateWrapper wrap(Predicate<ChangeData> p)
+ throws QueryParseException {
+ return new PredicateWrapper(p, openIndex);
+ }
+
+ private Set<Change.Status> status(String query) throws QueryParseException {
+ return IndexRewriteImpl.getPossibleStatus(parse(query));
+ }
+}
diff --git a/gerrit-sshd/BUCK b/gerrit-sshd/BUCK
index 769be58..93a3ef7 100644
--- a/gerrit-sshd/BUCK
+++ b/gerrit-sshd/BUCK
@@ -1,6 +1,8 @@
+SRCS = glob(['src/main/java/**/*.java'])
+
java_library2(
name = 'sshd',
- srcs = glob(['src/main/java/**/*.java']),
+ srcs = SRCS,
deps = [
'//gerrit-extension-api:api',
'//gerrit-cache-h2:cache-h2',
@@ -29,3 +31,9 @@
],
visibility = ['PUBLIC'],
)
+
+java_sources(
+ name = 'sshd-src',
+ srcs = SRCS,
+ visibility = ['PUBLIC'],
+)
diff --git a/gerrit-war/BUCK b/gerrit-war/BUCK
index 8cf24ad..1fef7f8 100644
--- a/gerrit-war/BUCK
+++ b/gerrit-war/BUCK
@@ -5,6 +5,7 @@
'//gerrit-cache-h2:cache-h2',
'//gerrit-extension-api:api',
'//gerrit-httpd:httpd',
+ '//gerrit-lucene:lucene',
'//gerrit-openid:openid',
'//gerrit-reviewdb:server',
'//gerrit-server:common_rules',
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index d0b8c38..0745984 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -18,11 +18,11 @@
import static com.google.inject.Stage.PRODUCTION;
import com.google.gerrit.common.ChangeHookRunner;
-import com.google.gerrit.httpd.GerritUiOptions;
import com.google.gerrit.httpd.auth.openid.OpenIdModule;
import com.google.gerrit.httpd.plugins.HttpPluginModule;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.lucene.LuceneIndexModule;
import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
import com.google.gerrit.server.config.AuthConfig;
@@ -37,6 +37,7 @@
import com.google.gerrit.server.git.LocalDiskRepositoryManager;
import com.google.gerrit.server.git.ReceiveCommitsExecutorModule;
import com.google.gerrit.server.git.WorkQueue;
+import com.google.gerrit.server.index.NoIndexModule;
import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
import com.google.gerrit.server.mail.SmtpEmailSender;
import com.google.gerrit.server.patch.IntraLineWorkerPool;
@@ -236,6 +237,11 @@
modules.add(new SmtpEmailSender.Module());
modules.add(new SignedTokenEmailTokenVerifier.Module());
modules.add(new PluginModule());
+ if (LuceneIndexModule.isEnabled(cfgInjector)) {
+ modules.add(new LuceneIndexModule());
+ } else {
+ modules.add(new NoIndexModule());
+ }
modules.add(new CanonicalWebUrlModule() {
@Override
protected Class<? extends Provider<String>> provider() {
diff --git a/lib/BUCK b/lib/BUCK
index a9d0bf6..0ce791f 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -246,3 +246,19 @@
visibility = ['//lib:easymock'],
attach_source = False,
)
+
+maven_jar(
+ name = 'lucene-core',
+ id = 'org.apache.lucene:lucene-core:4.3.0',
+ bin_sha1 = 'd4e40fe5661b8de5d8c66db3d63a47b6b3ecf7f3',
+ src_sha1 = '86c29288b1930e33ba7ffea1b866af9a52d3d24a',
+ license = 'Apache2.0',
+)
+
+maven_jar(
+ name = 'lucene-analyzers-common',
+ id = 'org.apache.lucene:lucene-analyzers-common:4.3.0',
+ bin_sha1 = 'e7c3976156d292f696016e138b67ab5e6bfc1a56',
+ src_sha1 = '3606622b3c1f09b4b7cf34070cbf60d414af9b6b',
+ license = 'Apache2.0',
+)
diff --git a/lib/codemirror/BUCK b/lib/codemirror/BUCK
index cd2804b..b9f5711 100644
--- a/lib/codemirror/BUCK
+++ b/lib/codemirror/BUCK
@@ -5,14 +5,17 @@
prebuilt_jar(
name = 'codemirror',
binary_jar = genfile('codemirror.jar'),
- deps = [':codemirror__jar'],
+ deps = [
+ ':jar',
+ '//lib:LICENSE-codemirror',
+ ],
visibility = ['PUBLIC'],
)
# TODO(sop) Repackage by license boundaries.
# TODO(sop) Minify with Closure JS compiler.
genrule(
- name = 'codemirror__jar',
+ name = 'jar',
cmd = ';'.join([
'cd $TMP',
'mkdir net META-INF',
@@ -22,15 +25,12 @@
'zip -r $OUT *'
]),
srcs = [genfile('codemirror-' + VERSION + '.zip')],
- deps = [
- ':codemirror__download_bin',
- '//lib:LICENSE-codemirror',
- ],
+ deps = [':download'],
out = 'codemirror.jar',
)
genrule(
- name = 'codemirror__download_bin',
+ name = 'download',
cmd = '${//tools:download_file}' +
' -o $OUT' +
' -u ' + URL +
diff --git a/tools/BUCK b/tools/BUCK
index b7b9d82b..5f371f1 100644
--- a/tools/BUCK
+++ b/tools/BUCK
@@ -13,9 +13,15 @@
python_binary(
name = 'pack_war',
main = 'pack_war.py',
+ deps = [':util'],
visibility = ['PUBLIC'],
)
+python_library(
+ name = 'util',
+ srcs = ['util.py'],
+ visibility = ['PUBLIC'],
+)
def shquote(s):
return s.replace("'", "'\\''")
diff --git a/tools/DEFS b/tools/DEFS
index af90a8a..50d325c 100644
--- a/tools/DEFS
+++ b/tools/DEFS
@@ -16,13 +16,25 @@
name,
srcs,
outs):
+ tmp = name + '.srcjar'
genrule(
name = name,
srcs = srcs,
- cmd = '${//lib/antlr:antlr-tool} -o $(dirname $OUT) $SRCS',
+ cmd = '${//lib/antlr:antlr-tool} -o $TMP $SRCS;' +
+ 'cd $TMP;' +
+ 'zip -qr $OUT .',
deps = ['//lib/antlr:antlr-tool'],
- out = outs[0],
+ out = tmp,
)
+ for o in outs:
+ genrule(
+ name = o,
+ cmd = 'unzip -qp $SRCS %s >$OUT' % o,
+ srcs = [genfile(tmp)],
+ deps = [':' + name],
+ out = o,
+ )
+
def gwt_module(
name,
@@ -156,3 +168,14 @@
],
visibility = visibility,
)
+
+def java_sources(
+ name,
+ srcs,
+ visibility = []
+ ):
+ java_library(
+ name = name,
+ resources = srcs,
+ visibility = visibility,
+ )
diff --git a/tools/download_all.py b/tools/download_all.py
index 7be8e12..241d20b 100755
--- a/tools/download_all.py
+++ b/tools/download_all.py
@@ -18,7 +18,7 @@
from subprocess import check_call, CalledProcessError, Popen, PIPE
MAIN = ['//tools/eclipse:classpath']
-PAT = re.compile(r'"(//.*?__download_[^"]*)" -> "//tools:download_file"')
+PAT = re.compile(r'"(//.*?)" -> "//tools:download_file"')
opts = OptionParser()
opts.add_option('--src', action='store_true')
diff --git a/tools/pack_war.py b/tools/pack_war.py
index 1c14bc8..0ba802b 100755
--- a/tools/pack_war.py
+++ b/tools/pack_war.py
@@ -16,12 +16,7 @@
from optparse import OptionParser
from os import environ, makedirs, path, symlink
from subprocess import check_call
-try:
- from subprocess import check_output
-except ImportError:
- from subprocess import Popen, PIPE
- def check_output(*cmd):
- return Popen(*cmd, stdout=PIPE).communicate()[0]
+from util import check_output
opts = OptionParser()
opts.add_option('-o', help='path to write WAR to')
diff --git a/tools/util.py b/tools/util.py
new file mode 100644
index 0000000..0c121e1
--- /dev/null
+++ b/tools/util.py
@@ -0,0 +1,20 @@
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+try:
+ from subprocess import check_output
+except ImportError:
+ from subprocess import Popen, PIPE
+ def check_output(*cmd):
+ return Popen(*cmd, stdout=PIPE).communicate()[0]