Merge "Provide easy way to access plugin configuration"
diff --git a/.buckconfig b/.buckconfig
index e60c7cd5..171acb5 100644
--- a/.buckconfig
+++ b/.buckconfig
@@ -15,3 +15,6 @@
[java]
src_roots = java, resources
+
+[project]
+ ignore = .git
diff --git a/Documentation/cmd-create-project.txt b/Documentation/cmd-create-project.txt
index 666fe91..e3ad834 100644
--- a/Documentation/cmd-create-project.txt
+++ b/Documentation/cmd-create-project.txt
@@ -109,6 +109,7 @@
+
* FAST_FORWARD_ONLY: produces a strictly linear history.
* MERGE_IF_NECESSARY: create a merge commit when required.
+* REBASE_IF_NECESSARY: rebase the commit when required.
* MERGE_ALWAYS: always create a merge commit.
* CHERRY_PICK: always cherry-pick the commit.
diff --git a/Documentation/cmd-set-project.txt b/Documentation/cmd-set-project.txt
index 7ff534b..af20006 100644
--- a/Documentation/cmd-set-project.txt
+++ b/Documentation/cmd-set-project.txt
@@ -57,6 +57,7 @@
+
* FAST_FORWARD_ONLY: produces a strictly linear history.
* MERGE_IF_NECESSARY: create a merge commit when required.
+* REBASE_IF_NECESSARY: rebase the commit when required.
* MERGE_ALWAYS: always create a merge commit.
* CHERRY_PICK: always cherry-pick the commit.
diff --git a/Documentation/config-auto-site-initialization.txt b/Documentation/config-auto-site-initialization.txt
new file mode 100644
index 0000000..4c204fd
--- /dev/null
+++ b/Documentation/config-auto-site-initialization.txt
@@ -0,0 +1,82 @@
+Gerrit Code Review - Automatic Site Initialization on Startup
+=============================================================
+
+Description
+-----------
+
+Gerrit supports automatic site initialization on server startup
+when Gerrit runs in a servlet container. Both creation of a new site
+and upgrade of an existing site are supported. Installation of
+plugins during the site creation/initialization is not yet supported.
+
+This feature may be useful for such setups where Gerrit administrators
+don't have direct access to the database and the file system of the
+server where Gerrit should be deployed and, therefore, cannot perform
+the init from their local machine prior to deploying Gerrit on such a
+server. It may also make deployment and testing in a local servlet
+container faster to setup as the init step could be skipped.
+
+Gerrit Configuration
+--------------------
+
+The site initialization will be performed only if the `gerrit.init`
+system property exists (the value of the property is not used, only the
+existence of the property matters).
+
+If the `gerrit.site_path` system property is defined then the init is
+run for that site. The database connectivity, in that case, is defined
+in the `etc/gerrit.config`.
+
+If `gerrit.site_path` is not defined then Gerrit will try to find an
+existing site by looking into the `system_config` table in the database
+defined via the `jdbc/ReviewDb` JNDI property. If the `system_config`
+table exists then the `site_path` from that table is used for the
+initialization. The database connectivity is defined by the
+`jdbc/ReviewDb` JNDI property.
+
+Finally, if neither the `gerrit.site_path` property nor the
+`system_config` table exists, the `gerrit.init_path` system property,
+if defined, will be used to determine the site path. The database
+connectivity, also for this case, is defined by the `jdbc/ReviewDb`
+JNDI property.
+
+Example 1
+~~~~~~~~~
+
+Prepare Tomcat so that a site is initialized at a given path using
+the H2 database (if the site doesn't exist yet) or using whatever
+database is defined in `etc/gerrit.config` of that site:
+
+----
+ $ export CATALINA_OPTS='-Dgerrit.init -Dgerrit.site_path=/path/to/site'
+ $ catalina.sh start
+----
+
+Example 2
+~~~~~~~~~
+
+Prepare Tomcat so that an existing site with the path defined in the
+`system_config` table is initialized (upgraded) on Gerrit startup. The
+assumption is that the `jdbc/ReviewDb` JNDI property is defined in
+Tomcat:
+
+----
+ $ export CATALINA_OPTS='-Dgerrit.init'
+ $ catalina.sh start
+----
+
+Example 3
+~~~~~~~~~
+
+Assuming the database schema doesn't exist in the database defined
+via the `jdbc/ReviewDb` JNDI property, initialize a new site using that
+database and a given path:
+
+----
+ $ export CATALINA_OPTS='-Dgerrit.init -Dgerrit.init_path=/path/to/site'
+ $ catalina.sh start
+----
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/config-labels.txt b/Documentation/config-labels.txt
index a1cf9ee..c3e09c4 100644
--- a/Documentation/config-labels.txt
+++ b/Documentation/config-labels.txt
@@ -244,6 +244,30 @@
configuration for this label in child projects will be ignored. Defaults
to true.
+[[label_branch]]
+`label.Label-Name.branch`
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default a given project's label applicable scope is all changes
+on all branches of this project and its child projects.
+
+Label's applicable scope can be branch specific via configuration.
+E.g. create a label `Video-Qualify` on parent project and configure
+the `branch` as:
+
+====
+ [label "Video-Qualify"]
+ branch = refs/heads/video-1.0/*
+ branch = refs/heads/video-1.1/Kino
+====
+
+Then *only* changes in above branch scope of parent project and child
+projects will be affected by `Video-Qualify`.
+
+NOTE: The `branch` is independent from the branch scope defined in `access`
+parts in `project.config` file. That means from the UI a user can always
+assign permissions for that label on a branch, but this permission is then
+ignored if the label doesn't apply for that branch.
[[label_example]]
Example
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt
index 7671767..74f56b3 100644
--- a/Documentation/dev-buck.txt
+++ b/Documentation/dev-buck.txt
@@ -37,6 +37,13 @@
which buck
----
+If you plan to use the link:#buck-daemon[Buck daemon] add a symbolic
+link in `~/bin` to the buckd executable:
+
+----
+ ln -s `pwd`/bin/buckd ~/bin/
+----
+
[[eclipse]]
Eclipse Integration
@@ -289,7 +296,6 @@
)
----
-
Caching Build Results
~~~~~~~~~~~~~~~~~~~~~
@@ -306,6 +312,29 @@
EOF
----
+[[buck-daemon]]
+Using Buck daemon
+~~~~~~~~~~~~~~~~~
+
+Buck ships with daemon command `buckd`, which uses
+link:https://github.com/martylamb/nailgun[Nailgun] protocol for running
+Java programs from the command line without incurring the JVM startup
+overhead. Using a Buck daemon can save significant amounts of time as it
+avoids the overhead of starting a Java virtual machine, loading the buck class
+files and parsing the build files for each command. It is safe to run several
+buck daemons started from different project directories and they will not
+interfere with each other. Buck's documentation covers daemon in
+http://facebook.github.io/buck/command/buckd.html[buckd]. The trivial case is
+to run `buckd` from the project's root directory and use `buck` as usually:
+
+----
+$>buckd
+$>buck build gerrit
+Using buckd.
+[-] PARSING BUILD FILES...FINISHED 0.6s
+[-] BUILDING...FINISHED 0.2s
+----
+
GERRIT
------
Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index c109294..87ecc16 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -255,6 +255,26 @@
to package additional dependencies. Relocating (or renaming) classes
should not be necessary due to the ClassLoader isolation.
+[[events]]
+Listening to Events
+-------------------
+
+Certain operations in Gerrit trigger events. Plugins may receive
+notifications of these events by implementing the corresponding
+listeners.
+
+* `com.google.gerrit.extensions.events.LifecycleListener`:
++
+Gerrit server startup and shutdown
+
+* `com.google.gerrit.extensions.events.NewProjectCreatedListener`:
++
+Project creation
+
+* `com.google.gerrit.extensions.events.ProjectDeletedListener`:
++
+Project deletion
+
[[ssh]]
SSH Commands
------------
diff --git a/Documentation/index.txt b/Documentation/index.txt
index 28e0184..ee76855 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -62,6 +62,7 @@
.. link:dev-design.html[System Design]
.. link:config-contact.html[User Contact Information]
.. link:config-reverseproxy.html[Reverse Proxy]
+.. link:config-auto-site-initialization.html[Automatic Site Initialization on Startup]
.. link:pgm-index.html[Server Side Administrative Tools]
. Developer
.. link:dev-readme.html[Developer Setup]
diff --git a/Documentation/install-j2ee.txt b/Documentation/install-j2ee.txt
index f6853c8..5ba8cb1 100644
--- a/Documentation/install-j2ee.txt
+++ b/Documentation/install-j2ee.txt
@@ -46,6 +46,9 @@
from `'$site_path'/lib` into your servlet container's extensions
directory so it's available to Gerrit Code Review.
+* ('Optional') link:config-auto-site-initialization.html[
+Configure Automatic Site Initialization on Startup]
+
Jetty 7.x
---------
diff --git a/Documentation/install.txt b/Documentation/install.txt
index 8137f53..d80b197 100644
--- a/Documentation/install.txt
+++ b/Documentation/install.txt
@@ -146,6 +146,7 @@
* link:config-themes.html[Themes]
* link:config-gitweb.html[Gitweb Integration]
* link:config-gerrit.html[Other System Settings]
+* link:config-auto-site-initialization.html[Automatic Site Initialization on Startup]
[[anonymous_access]]
diff --git a/Documentation/prolog-cookbook.txt b/Documentation/prolog-cookbook.txt
index 80f92a3..b1710a2 100644
--- a/Documentation/prolog-cookbook.txt
+++ b/Documentation/prolog-cookbook.txt
@@ -303,11 +303,11 @@
The prolog environment running the `submit_rule` is loaded with state describing the
change that is being evaluated. The easiest way to load this state is to test your
`submit_rule` against a real change on a running gerrit instance. The command
-link:cmd-test-submit-rule.html[test-submit-rule] loads a specific change and executes
+link:cmd-test-submit-rule.html[test-submit rule] loads a specific change and executes
the `submit_rule`. It optionally reads the rule from from `stdin` to facilitate easy testing.
====
- cat rules.pl | ssh gerrit_srv gerrit test-submit-rule I45e080b105a50a625cc8e1fb5b357c0bfabe6d68 -s
+ cat rules.pl | ssh gerrit_srv gerrit test-submit rule I45e080b105a50a625cc8e1fb5b357c0bfabe6d68 -s
====
Prolog vs Gerrit plugin for project specific submit rules
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 97ce2a1..65dd3a4 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -932,6 +932,44 @@
blocked by Verified
----
+[[publish-draft-change]]
+Publish Draft Change
+~~~~~~~~~~~~~~~~~~~~
+[verse]
+'POST /changes/link:#change-id[\{change-id\}]/publish'
+
+Publishes a draft change.
+
+.Request
+----
+ POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/publish HTTP/1.0
+ Content-Type: application/json;charset=UTF-8
+----
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
+
+[[delete-draft-change]]
+Delete Draft Change
+~~~~~~~~~~~~~~~~~~~
+[verse]
+'DELETE /changes/link:#change-id[\{change-id\}]'
+
+Deletes a draft change.
+
+.Request
+----
+ DELETE /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940 HTTP/1.0
+ Content-Type: application/json;charset=UTF-8
+----
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
+
[[reviewer-endpoints]]
Reviewer Endpoints
------------------
@@ -1504,6 +1542,44 @@
"revision 674ac754f91e64a0efb8087e59a176484bd534d1 is not current revision"
----
+[[publish-draft-revision]]
+Publish Draft Revision
+~~~~~~~~~~~~~~~~~~~~~~
+[verse]
+'POST /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/publish'
+
+Publishes a draft revision.
+
+.Request
+----
+ POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/current/publish HTTP/1.0
+ Content-Type: application/json;charset=UTF-8
+----
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
+
+[[delete-draft-revision]]
+Delete Draft Revision
+~~~~~~~~~~~~~~~~~~~~~
+[verse]
+'DELETE /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]'
+
+Deletes a draft revision.
+
+.Request
+----
+ DELETE /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1 HTTP/1.0
+ Content-Type: application/json;charset=UTF-8
+----
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
+
[[get-patch]]
Get Patch
~~~~~~~~~
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index d7e9fd7..f1c13f8 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -168,6 +168,11 @@
+
Changes that match 'MESSAGE' arbitrary string in the commit message body.
+[[comment]]
+comment:'TEXT'::
++
+Changes that match 'TEXT' string in any comment left by a reviewer.
+
[[file]]
file:^'REGEX'::
+
@@ -187,12 +192,6 @@
ones using a bracket expression). For example, to match all XML
files named like 'name1.xml', 'name2.xml', and 'name3.xml' use
`file:"^name[1-3].xml"`.
-+
-Currently this operator is only available on a watched project
-and may not be used in the search bar. The same holds true for web UI
-"My > Watched Changes", i. e. file:regex is used over the is:watched
-expression. It never produces any results, because the error message:
-"operator not permitted here: file:regex" is suppressed.
[[has]]
has:draft::
diff --git a/ReleaseNotes/ReleaseNotes-2.6.2.txt b/ReleaseNotes/ReleaseNotes-2.6.2.txt
index 2a7a7b3..af00a71 100644
--- a/ReleaseNotes/ReleaseNotes-2.6.2.txt
+++ b/ReleaseNotes/ReleaseNotes-2.6.2.txt
@@ -21,10 +21,6 @@
If the title is not specified, the path of the dashboard config file
is used as title.
-* link:https://code.google.com/p/gerrit/issues/detail?id=2010[Issue 2010]:
-Fix null-pointer exception when searching for changes with the query
-`owner:self`.
-
* Properly handle double-click on external group in GroupTable.
+
Double-clicking on an external group opens the group's URL (if it
@@ -41,11 +37,15 @@
* link:https://code.google.com/p/gerrit/issues/detail?id=1966[Issue 1966]:
Fix Gerrit plugins under Tomcat by avoiding Guice static filter.
-* link:https://code.google.com/p/gerrit/issues/detail?id=2054[Issue 2054]:
-Expand capabilities of `ldap.groupMemberPattern`.
+* link:https://code.google.com/p/gerrit/issues/detail?id=2010[Issue 2010]:
+Fix null-pointer exception when searching for changes with the query
+`owner:self`.
* link:https://code.google.com/p/gerrit/issues/detail?id=2039[Issue 2039]:
-Fix browser NPE when ChangeCache is incomplete.
+Fix browser null-pointer exception when ChangeCache is incomplete.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=2054[Issue 2054]:
+Expand capabilities of `ldap.groupMemberPattern`.
* link:https://code.google.com/p/gerrit/issues/detail?id=2056[Issue 2056]:
Display custom NoOp label score for open changes.
@@ -56,5 +56,12 @@
* link:https://code.google.com/p/gerrit/issues/detail?id=2098[Issue 2098]:
Fix re-enabling of disabled plugins.
+* link:https://code.google.com/p/gerrit/issues/detail?id=2127[Issue 2127]:
+Remove hard-coded documentation links from the admin page.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=2128[Issue 2128]:
+Fix null-pointer exception when deleting draft patch set when previous
+draft was already deleted.
+
No other changes since 2.6.1.
diff --git a/ReleaseNotes/ReleaseNotes-2.7.txt b/ReleaseNotes/ReleaseNotes-2.7.txt
index eb95146..348b812 100644
--- a/ReleaseNotes/ReleaseNotes-2.7.txt
+++ b/ReleaseNotes/ReleaseNotes-2.7.txt
@@ -100,7 +100,7 @@
+
Teams that want to use Gerrit's submit strategies to handle contention on busy
branches can use `%submit` to create a change and have it
-link:link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.7/user-upload.html#auto_merge[
+link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.7/user-upload.html#auto_merge[
immediately submitted], if the caller has Submit permission on `refs/for/<ref>`.
* Allow administrators to see all groups.
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 3a95fc3..adb346f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -14,20 +14,51 @@
package com.google.gerrit.acceptance;
-import org.junit.After;
-import org.junit.Before;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Rule;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
public abstract class AbstractDaemonTest {
protected GerritServer server;
- @Before
- public final void beforeTest() throws Exception {
- server = GerritServer.start();
+ @Rule
+ public TestRule testRunner = new TestRule() {
+ @Override
+ public Statement apply(final Statement base, final Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ beforeTest(config(description));
+ base.evaluate();
+ afterTest();
+ }
+ };
+ }
+ };
+
+ private static Config config(Description description) {
+ GerritConfigs cfgs = description.getAnnotation(GerritConfigs.class);
+ GerritConfig cfg = description.getAnnotation(GerritConfig.class);
+ if (cfgs != null && cfg != null) {
+ throw new IllegalStateException("Use either @GerritConfigs or @GerritConfig not both");
+ }
+ if (cfgs != null) {
+ return ConfigAnnotationParser.parse(cfgs);
+ } else if (cfg != null) {
+ return ConfigAnnotationParser.parse(cfg);
+ } else {
+ return null;
+ }
+ }
+
+ private void beforeTest(Config cfg) throws Exception {
+ server = GerritServer.start(cfg);
server.getTestInjector().injectMembers(this);
}
- @After
- public final void afterTest() throws Exception {
+ private void afterTest() throws Exception {
server.stop();
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
index f56ef07..cb785b9 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
@@ -114,6 +114,12 @@
return create(username, null, username, (String[]) null);
}
+ public TestAccount admin()
+ throws UnsupportedEncodingException, OrmException, JSchException {
+ return create("admin", "admin@example.com", "Administrator",
+ "Administrators");
+ }
+
private AccountExternalId.Key getEmailKey(String email) {
return new AccountExternalId.Key(AccountExternalId.SCHEME_MAILTO, email);
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ConfigAnnotationParser.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ConfigAnnotationParser.java
new file mode 100644
index 0000000..cf60fb4
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ConfigAnnotationParser.java
@@ -0,0 +1,58 @@
+// 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.acceptance;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+
+import org.eclipse.jgit.lib.Config;
+
+import java.util.ArrayList;
+
+class ConfigAnnotationParser {
+
+ private static Splitter splitter = Splitter.on(".").trimResults();
+
+ static Config parse(GerritConfigs annotation) {
+ if (annotation == null) {
+ return null;
+ }
+
+ Config cfg = new Config();
+ for (GerritConfig c : annotation.value()) {
+ parse(cfg, c);
+ }
+ return cfg;
+ }
+
+ static Config parse(GerritConfig annotation) {
+ Config cfg = new Config();
+ parse(cfg, annotation);
+ return cfg;
+ }
+
+ static private void parse(Config cfg, GerritConfig c) {
+ ArrayList<String> l = Lists.newArrayList(splitter.split(c.name()));
+ if (l.size() == 2) {
+ cfg.setString(l.get(0), null, l.get(1), c.value());
+ } else if (l.size() == 3) {
+ cfg.setString(l.get(0), l.get(1), l.get(2), c.value());
+ } else {
+ throw new IllegalArgumentException(
+ "GerritConfig.name must be of the format"
+ + " section.subsection.name or section.name");
+ }
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritConfig.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritConfig.java
new file mode 100644
index 0000000..5cb1229
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritConfig.java
@@ -0,0 +1,28 @@
+// 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.acceptance;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Target({METHOD})
+@Retention(RUNTIME)
+public @interface GerritConfig {
+ String name();
+ String value();
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritConfigs.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritConfigs.java
new file mode 100644
index 0000000..58bb9f2
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritConfigs.java
@@ -0,0 +1,27 @@
+// 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.acceptance;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Target({METHOD})
+@Retention(RUNTIME)
+public @interface GerritConfigs {
+ public GerritConfig[] value();
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
index a9ce827..f97d2b9 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
@@ -23,6 +23,7 @@
import com.google.inject.Module;
import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.RepositoryCache;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.util.FS;
@@ -42,11 +43,11 @@
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
-class GerritServer {
+public class GerritServer {
/** Returns fully started Gerrit server */
- static GerritServer start() throws Exception {
- final File site = initSite();
+ static GerritServer start(Config base) throws Exception {
+ final File site = initSite(base);
final CyclicBarrier serverStarted = new CyclicBarrier(2);
final Daemon daemon = new Daemon(new Runnable() {
public void run() {
@@ -80,11 +81,12 @@
return new GerritServer(site, i, daemon, daemonService);
}
- private static File initSite() throws Exception {
+ private static File initSite(Config base) throws Exception {
File tmp = TempFileUtil.createTempDirectory();
Init init = new Init();
int rc = init.main(new String[] {
- "-d", tmp.getPath(), "--batch", "--no-auto-start"});
+ "-d", tmp.getPath(), "--batch", "--no-auto-start",
+ "--skip-plugins"});
if (rc != 0) {
throw new RuntimeException("Couldn't initialize site");
}
@@ -92,10 +94,11 @@
InetSocketAddress http = newPort();
InetSocketAddress sshd = newPort();
String url = "http://" + format(http) + "/";
- FileBasedConfig cfg = new FileBasedConfig(
+ MergeableFileBasedConfig cfg = new MergeableFileBasedConfig(
new File(new File(tmp, "etc"), "gerrit.config"),
FS.DETECTED);
cfg.load();
+ cfg.merge(base);
cfg.setString("gerrit", null, "canonicalWebUrl", url);
cfg.setString("httpd", null, "listenUrl", url);
cfg.setString("sshd", null, "listenAddress", format(sshd));
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/MergeableFileBasedConfig.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/MergeableFileBasedConfig.java
new file mode 100644
index 0000000..f1baa9d
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/MergeableFileBasedConfig.java
@@ -0,0 +1,60 @@
+// 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.acceptance;
+
+import com.google.common.collect.Lists;
+
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+
+import java.io.File;
+
+/**
+ * A file based Config that can merge another Config instance.
+ */
+public class MergeableFileBasedConfig extends FileBasedConfig {
+ public MergeableFileBasedConfig(File cfgLocation, FS fs) {
+ super(cfgLocation, fs);
+ }
+
+ /**
+ * Merge another Config into this Config.
+ *
+ * In case a configuration parameter exists both in this instance and in the
+ * merged instance then the value in this instance will simply replaced by
+ * the value from the merged instance.
+ *
+ * @param s Config to merge into this instance
+ */
+ public void merge(Config s) {
+ if (s == null) {
+ return;
+ }
+ for (String section : s.getSections()) {
+ for (String subsection : s.getSubsections(section)) {
+ for (String name : s.getNames(section, subsection)) {
+ setStringList(section, subsection, name, Lists.newArrayList(s
+ .getStringList(section, subsection, name)));
+ }
+ }
+
+ for (String name : s.getNames(section)) {
+ setStringList(section, null, name,
+ Lists.newArrayList(s.getStringList(section, null, name)));
+ }
+ }
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/UseGerritConfigAnnotationTest.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/UseGerritConfigAnnotationTest.java
new file mode 100644
index 0000000..0931e12
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/UseGerritConfigAnnotationTest.java
@@ -0,0 +1,46 @@
+// 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.acceptance;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.Config;
+import org.junit.Test;
+
+public class UseGerritConfigAnnotationTest extends AbstractDaemonTest {
+
+ @Inject
+ @GerritServerConfig
+ Config serverConfig;
+
+ @Test
+ @GerritConfig(name="x.y", value="z")
+ public void testOne() {
+ assertEquals("z", serverConfig.getString("x", null, "y"));
+ }
+
+ @Test
+ @GerritConfigs({
+ @GerritConfig(name="x.y", value="z"),
+ @GerritConfig(name="a.b", value="c"),
+ })
+ public void testMultiple() {
+ assertEquals("z", serverConfig.getString("x", null, "y"));
+ assertEquals("c", serverConfig.getString("a", null, "b"));
+ }
+}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java
index db8bde9..caf914d 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java
@@ -103,6 +103,7 @@
protected short maxPositive;
private transient boolean canOverride;
+ private transient List<String> refPatterns;
private transient List<Integer> intList;
private transient Map<Short, LabelValue> byValue;
@@ -157,10 +158,18 @@
return canOverride;
}
+ public List<String> getRefPatterns() {
+ return refPatterns;
+ }
+
public void setCanOverride(boolean canOverride) {
this.canOverride = canOverride;
}
+ public void setRefPatterns(List<String> refPatterns) {
+ this.refPatterns = refPatterns;
+ }
+
public List<LabelValue> getValues() {
return values;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index c767f84..4459bac 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -22,8 +22,8 @@
projectRepoBrowser = Repository Browser
useContentMerge = Automatically resolve conflicts
useContributorAgreements = Require a valid contributor agreement to upload
-useSignedOffBy = Require <a href="http://gerrit-documentation.googlecode.com/svn/Documentation/2.5/user-signedoffby.html#Signed-off-by" target="_blank"><code>Signed-off-by</code></a> in commit message
-requireChangeID = Require <a href="http://gerrit-documentation.googlecode.com/svn/Documentation/2.5/user-changeid.html" target="_blank"><code>Change-Id</code></a> in commit message
+useSignedOffBy = Require <code>Signed-off-by</code> in commit message
+requireChangeID = Require <code>Change-Id</code> in commit message
headingMaxObjectSizeLimit = Maximum Git object size limit
headingGroupOptions = Group Options
isVisibleToAll = Make group visible to all registered users.
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index d8a2648..6e8c6f6 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -788,8 +788,12 @@
List<String> p = splitProjection(projection);
if (p.size() == 2) {
+ String viewname = p.get(1);
+ if (Strings.isNullOrEmpty(viewname)) {
+ viewname = "/";
+ }
RestView<RestResource> view =
- views.get(p.get(0), method + "." + p.get(1));
+ views.get(p.get(0), method + "." + viewname);
if (view != null) {
return new ViewData(p.get(0), view);
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/IncludedInDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/IncludedInDetailFactory.java
index c07ee51..0c7df3e 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/IncludedInDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/IncludedInDetailFactory.java
@@ -20,6 +20,7 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.change.IncludedInResolver;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
@@ -29,23 +30,15 @@
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
/** Creates a {@link IncludedInDetail} of a {@link Change}. */
class IncludedInDetailFactory extends Handler<IncludedInDetail> {
- private static final Logger log =
- LoggerFactory.getLogger(IncludedInDetailFactory.class);
interface Factory {
IncludedInDetailFactory create(Change.Id id);
@@ -56,7 +49,6 @@
private final GitRepositoryManager repoManager;
private final Change.Id changeId;
- private IncludedInDetail detail;
private ChangeControl control;
@Inject
@@ -92,11 +84,7 @@
throw new InvalidRevisionException();
}
- detail = new IncludedInDetail();
- detail.setBranches(includedIn(repo, rw, rev, Constants.R_HEADS));
- detail.setTags(includedIn(repo, rw, rev, Constants.R_TAGS));
-
- return detail;
+ return IncludedInResolver.resolve(repo, rw, rev);
} finally {
rw.release();
}
@@ -104,32 +92,4 @@
repo.close();
}
}
-
- private List<String> includedIn(final Repository repo, final RevWalk rw,
- final RevCommit rev, final String namespace) throws IOException,
- MissingObjectException, IncorrectObjectTypeException {
- final List<String> result = new ArrayList<String>();
- for (final Ref ref : repo.getRefDatabase().getRefs(namespace).values()) {
- final RevCommit tip;
- try {
- tip = rw.parseCommit(ref.getObjectId());
- } catch (IncorrectObjectTypeException notCommit) {
- // Its OK for a tag reference to point to a blob or a tree, this
- // is common in the Linux kernel or git.git repository.
- //
- continue;
- } catch (MissingObjectException notHere) {
- // Log the problem with this branch, but keep processing.
- //
- log.warn("Reference " + ref.getName() + " in " + repo.getDirectory()
- + " points to dangling object " + ref.getObjectId());
- continue;
- }
-
- if (rw.isMergedInto(rev, tip)) {
- result.add(ref.getName().substring(namespace.length()));
- }
- }
- return result;
- }
-}
+}
\ No newline at end of file
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
index 286565b..c98bd5d 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -48,6 +48,7 @@
import com.google.inject.assistedinject.AssistedInject;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.analysis.util.CharArraySet;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Store;
@@ -112,7 +113,7 @@
private static IndexWriterConfig getIndexWriterConfig(Config cfg, String name) {
IndexWriterConfig writerConfig = new IndexWriterConfig(LUCENE_VERSION,
- new StandardAnalyzer(LUCENE_VERSION));
+ new StandardAnalyzer(LUCENE_VERSION, CharArraySet.EMPTY_SET));
writerConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);
double m = 1 << 20;
writerConfig.setRAMBufferSizeMB(cfg.getLong("index", name, "ramBufferSize",
diff --git a/gerrit-pgm/BUCK b/gerrit-pgm/BUCK
index 2a88694..38480bc 100644
--- a/gerrit-pgm/BUCK
+++ b/gerrit-pgm/BUCK
@@ -1,4 +1,6 @@
-INIT_SRCS = ['src/main/java/com/google/gerrit/pgm/' + n for n in [
+SRCS = 'src/main/java/com/google/gerrit/pgm/'
+
+INIT_API_SRCS = [SRCS + n for n in [
'init/InitFlags.java',
'init/InitStep.java',
'init/InitStep.java',
@@ -10,7 +12,7 @@
java_library(
name = 'init-api',
- srcs = INIT_SRCS,
+ srcs = INIT_API_SRCS,
deps = [
'//gerrit-server:server',
'//lib:jsr305',
@@ -23,16 +25,65 @@
java_sources(
name = 'init-api-src',
- srcs = INIT_SRCS,
+ srcs = INIT_API_SRCS,
visibility = ['PUBLIC'],
)
+INIT_BASE_SRCS = [SRCS + 'BaseInit.java'] + glob(
+ [SRCS + n for n in [
+ 'init/**/*.java',
+ 'util/**/*.java',
+ ]],
+ excludes = INIT_API_SRCS +
+ [SRCS + n for n in [
+ 'init/Browser.java',
+ 'util/ErrorLogFile.java',
+ 'util/GarbageCollectionLogFile.java',
+ 'util/LogFileCompressor.java',
+ 'util/RuntimeShutdown.java',
+ ]]
+ )
+
+INIT_BASE_RSRCS = ['src/main/resources/com/google/gerrit/pgm/libraries.config']
+
+java_library2(
+ name = 'init-base',
+ srcs = INIT_BASE_SRCS,
+ resources = INIT_BASE_RSRCS,
+ deps = [
+ ':init-api',
+ '//gerrit-common:server',
+ '//gerrit-extension-api:api',
+ '//gerrit-reviewdb:server',
+ '//gerrit-server:server',
+ '//gerrit-util-cli:cli',
+ '//lib/commons:dbcp',
+ '//lib/guice:guice',
+ '//lib/guice:guice-assistedinject',
+ '//lib/jgit:jgit',
+ '//lib/mina:sshd',
+ '//lib:args4j',
+ '//lib:guava',
+ '//lib:gwtjsonrpc',
+ '//lib:gwtorm',
+ '//lib:jsr305',
+ ],
+ compile_deps = ['//gerrit-launcher:launcher'],
+ visibility = ['//gerrit-war:'],
+)
+
java_library2(
name = 'pgm',
- srcs = glob(['src/main/java/**/*.java'], excludes = INIT_SRCS),
- resources = glob(['src/main/resources/**/*']),
+ srcs = glob(
+ ['src/main/java/**/*.java'],
+ excludes = INIT_API_SRCS + INIT_BASE_SRCS
+ ),
+ resources = glob(
+ ['src/main/resources/**/*'],
+ excludes = INIT_BASE_RSRCS),
deps = [
':init-api',
+ ':init-base',
'//gerrit-cache-h2:cache-h2',
'//gerrit-common:server',
'//gerrit-extension-api:api',
@@ -49,13 +100,9 @@
'//lib:args4j',
'//lib:guava',
'//lib:gwtorm',
- '//lib:gwtjsonrpc',
'//lib:h2',
- '//lib:jsr305',
'//lib:servlet-api-3_0',
- '//lib/commons:dbcp',
'//lib/guice:guice',
- '//lib/guice:guice-assistedinject',
'//lib/guice:guice-servlet',
'//lib/jetty:server',
'//lib/jetty:servlet',
@@ -63,7 +110,6 @@
'//lib/log:api',
'//lib/log:log4j',
'//lib/lucene:core',
- '//lib/mina:sshd',
'//lib/prolog:prolog-cafe',
],
compile_deps = ['//gerrit-launcher:launcher'],
@@ -80,6 +126,7 @@
srcs = glob(['src/test/java/**/*.java']),
deps = [
':init-api',
+ ':init-base',
':pgm',
'//gerrit-server:server',
'//lib:junit',
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java
new file mode 100644
index 0000000..b830959
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java
@@ -0,0 +1,291 @@
+// 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.pgm;
+
+import static com.google.gerrit.server.schema.DataSourceProvider.Context.SINGLE_USER;
+import static com.google.inject.Stage.PRODUCTION;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.Lists;
+import com.google.gerrit.pgm.init.InitFlags;
+import com.google.gerrit.pgm.init.InitModule;
+import com.google.gerrit.pgm.init.InstallPlugins;
+import com.google.gerrit.pgm.init.SitePathInitializer;
+import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.util.Die;
+import com.google.gerrit.pgm.util.SiteProgram;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.config.SitePath;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.schema.SchemaUpdater;
+import com.google.gerrit.server.schema.UpdateUI;
+import com.google.gwtorm.jdbc.JdbcExecutor;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.gwtorm.server.StatementExecutor;
+import com.google.inject.AbstractModule;
+import com.google.inject.CreationException;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import com.google.inject.Provider;
+import com.google.inject.TypeLiteral;
+import com.google.inject.spi.Message;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+/** Initialize a new Gerrit installation. */
+public class BaseInit extends SiteProgram {
+
+ private final boolean standalone;
+
+ public BaseInit() {
+ this.standalone = true;
+ }
+
+ public BaseInit(File sitePath, boolean standalone) {
+ this(sitePath, null, standalone);
+ }
+
+ public BaseInit(File sitePath, final Provider<DataSource> dsProvider,
+ boolean standalone) {
+ super(sitePath, dsProvider);
+ this.standalone = standalone;
+ }
+
+ @Override
+ public int run() throws Exception {
+ final SiteInit init = createSiteInit();
+ beforeInit(init);
+
+ init.flags.autoStart = getAutoStart() && init.site.isNew;;
+
+ final SiteRun run;
+ try {
+ init.initializer.run();
+ init.flags.deleteOnFailure = false;
+
+ run = createSiteRun(init);
+ run.upgradeSchema();
+ } catch (Exception failure) {
+ if (init.flags.deleteOnFailure) {
+ recursiveDelete(getSitePath());
+ }
+ throw failure;
+ } catch (Error failure) {
+ if (init.flags.deleteOnFailure) {
+ recursiveDelete(getSitePath());
+ }
+ throw failure;
+ }
+
+ System.err.println("Initialized " + getSitePath().getCanonicalPath());
+ afterInit(run);
+ return 0;
+ }
+
+ protected void beforeInit(SiteInit init) throws Exception {
+ }
+
+ protected void afterInit(SiteRun run) throws Exception {
+ }
+
+ protected List<String> getInstallPlugins() {
+ return null;
+ }
+
+ protected boolean getAutoStart() {
+ return false;
+ }
+
+ static class SiteInit {
+ final SitePaths site;
+ final InitFlags flags;
+ final ConsoleUI ui;
+ final SitePathInitializer initializer;
+
+ @Inject
+ SiteInit(final SitePaths site, final InitFlags flags, final ConsoleUI ui,
+ final SitePathInitializer initializer) {
+ this.site = site;
+ this.flags = flags;
+ this.ui = ui;
+ this.initializer = initializer;
+ }
+ }
+
+ private SiteInit createSiteInit() {
+ final ConsoleUI ui = getConsoleUI();
+ final File sitePath = getSitePath();
+ final List<Module> m = new ArrayList<Module>();
+
+ m.add(new InitModule(standalone));
+ m.add(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(ConsoleUI.class).toInstance(ui);
+ bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath);
+ List<String> plugins =
+ Objects.firstNonNull(getInstallPlugins(), Lists.<String> newArrayList());
+ bind(new TypeLiteral<List<String>>() {}).annotatedWith(
+ InstallPlugins.class).toInstance(plugins);
+ }
+ });
+
+ try {
+ return Guice.createInjector(PRODUCTION, m).getInstance(SiteInit.class);
+ } catch (CreationException ce) {
+ final Message first = ce.getErrorMessages().iterator().next();
+ Throwable why = first.getCause();
+
+ if (why instanceof Die) {
+ throw (Die) why;
+ }
+
+ final StringBuilder buf = new StringBuilder(ce.getMessage());
+ while (why != null) {
+ buf.append("\n");
+ buf.append(why.getMessage());
+ why = why.getCause();
+ if (why != null) {
+ buf.append("\n caused by ");
+ }
+ }
+ throw die(buf.toString(), new RuntimeException("InitInjector failed", ce));
+ }
+ }
+
+ protected ConsoleUI getConsoleUI() {
+ return ConsoleUI.getInstance(false);
+ }
+
+ static class SiteRun {
+ final ConsoleUI ui;
+ final SitePaths site;
+ final InitFlags flags;
+ final SchemaUpdater schemaUpdater;
+ final SchemaFactory<ReviewDb> schema;
+ final GitRepositoryManager repositoryManager;
+
+ @Inject
+ SiteRun(final ConsoleUI ui, final SitePaths site, final InitFlags flags,
+ final SchemaUpdater schemaUpdater,
+ final SchemaFactory<ReviewDb> schema,
+ final GitRepositoryManager repositoryManager) {
+ this.ui = ui;
+ this.site = site;
+ this.flags = flags;
+ this.schemaUpdater = schemaUpdater;
+ this.schema = schema;
+ this.repositoryManager = repositoryManager;
+ }
+
+ void upgradeSchema() throws OrmException {
+ final List<String> pruneList = new ArrayList<String>();
+ schemaUpdater.update(new UpdateUI() {
+ @Override
+ public void message(String msg) {
+ System.err.println(msg);
+ System.err.flush();
+ }
+
+ @Override
+ public boolean yesno(boolean def, String msg) {
+ return ui.yesno(def, msg);
+ }
+
+ @Override
+ public boolean isBatch() {
+ return ui.isBatch();
+ }
+
+ @Override
+ public void pruneSchema(StatementExecutor e, List<String> prune) {
+ for (String p : prune) {
+ if (!pruneList.contains(p)) {
+ pruneList.add(p);
+ }
+ }
+ }
+ });
+
+ if (!pruneList.isEmpty()) {
+ StringBuilder msg = new StringBuilder();
+ msg.append("Execute the following SQL to drop unused objects:\n");
+ msg.append("\n");
+ for (String sql : pruneList) {
+ msg.append(" ");
+ msg.append(sql);
+ msg.append(";\n");
+ }
+
+ if (ui.isBatch()) {
+ System.err.print(msg);
+ System.err.flush();
+
+ } else if (ui.yesno(true, "%s\nExecute now", msg)) {
+ final JdbcSchema db = (JdbcSchema) schema.open();
+ try {
+ final JdbcExecutor e = new JdbcExecutor(db);
+ try {
+ for (String sql : pruneList) {
+ e.execute(sql);
+ }
+ } finally {
+ e.close();
+ }
+ } finally {
+ db.close();
+ }
+ }
+ }
+ }
+ }
+
+ private SiteRun createSiteRun(final SiteInit init) {
+ return createSysInjector(init).getInstance(SiteRun.class);
+ }
+
+ private Injector createSysInjector(final SiteInit init) {
+ final List<Module> modules = new ArrayList<Module>();
+ modules.add(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(ConsoleUI.class).toInstance(init.ui);
+ bind(InitFlags.class).toInstance(init.flags);
+ }
+ });
+ return createDbInjector(SINGLE_USER).createChildInjector(modules);
+ }
+
+ private static void recursiveDelete(File path) {
+ File[] entries = path.listFiles();
+ if (entries != null) {
+ for (File e : entries) {
+ recursiveDelete(e);
+ }
+ }
+ if (!path.delete() && path.exists()) {
+ System.err.println("warn: Cannot remove " + path);
+ }
+ }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
index 0f0b208..1cab515 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
@@ -14,338 +14,161 @@
package com.google.gerrit.pgm;
-import static com.google.gerrit.server.schema.DataSourceProvider.Context.SINGLE_USER;
-import static com.google.inject.Stage.PRODUCTION;
-
import com.google.common.base.Function;
import com.google.common.base.Joiner;
-import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.pgm.init.Browser;
-import com.google.gerrit.pgm.init.InitFlags;
-import com.google.gerrit.pgm.init.InitModule;
import com.google.gerrit.pgm.init.InitPlugins;
import com.google.gerrit.pgm.init.InitPlugins.PluginData;
-import com.google.gerrit.pgm.init.InstallPlugins;
-import com.google.gerrit.pgm.init.SitePathInitializer;
import com.google.gerrit.pgm.util.ConsoleUI;
-import com.google.gerrit.pgm.util.Die;
import com.google.gerrit.pgm.util.ErrorLogFile;
import com.google.gerrit.pgm.util.IoUtil;
-import com.google.gerrit.pgm.util.SiteProgram;
-import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.config.GerritServerConfigModule;
import com.google.gerrit.server.config.SitePath;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.schema.SchemaUpdater;
-import com.google.gerrit.server.schema.UpdateUI;
import com.google.gerrit.server.util.HostPlatform;
-import com.google.gwtorm.jdbc.JdbcExecutor;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.gwtorm.server.StatementExecutor;
import com.google.inject.AbstractModule;
-import com.google.inject.CreationException;
import com.google.inject.Guice;
import com.google.inject.Inject;
-import com.google.inject.Injector;
import com.google.inject.Module;
-import com.google.inject.TypeLiteral;
-import com.google.inject.spi.Message;
+import com.google.inject.Provider;
import org.kohsuke.args4j.Option;
import java.io.File;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.List;
+import java.util.ArrayList;
+
+import javax.sql.DataSource;
/** Initialize a new Gerrit installation. */
-public class Init extends SiteProgram {
+public class Init extends BaseInit {
@Option(name = "--batch", usage = "Batch mode; skip interactive prompting")
private boolean batchMode;
@Option(name = "--no-auto-start", usage = "Don't automatically start daemon after init")
private boolean noAutoStart;
+ @Option(name = "--skip-plugins", usage = "Don't install plugin")
+ private boolean skipPlugins = false;
+
@Option(name = "--list-plugins", usage = "List available plugins")
private boolean listPlugins;
@Option(name = "--install-plugin", usage = "Install given plugin without asking", multiValued = true)
private List<String> installPlugins;
+ @Inject
+ Browser browser;
+
public Init() {
}
public Init(File sitePath) {
- super(sitePath);
+ this(sitePath, null);
+ }
+
+ public Init(File sitePath, final Provider<DataSource> dsProvider) {
+ super(sitePath, dsProvider, true);
batchMode = true;
noAutoStart = true;
}
@Override
- public int run() throws Exception {
+ protected void beforeInit(SiteInit init) throws Exception {
ErrorLogFile.errorOnlyConsole();
- final SiteInit init = createSiteInit();
- final List<PluginData> plugins = InitPlugins.listPlugins(init.site);
- ConsoleUI ui = ConsoleUI.getInstance(false);
- verifyInstallPluginList(ui, plugins);
- if (listPlugins) {
- if (!plugins.isEmpty()) {
- ui.message("Available plugins:\n");
- for (PluginData plugin : plugins) {
- ui.message(" * %s version %s\n", plugin.name, plugin.version);
- }
- } else {
- ui.message("No plugins found.\n");
- }
- return 0;
- }
-
- init.flags.autoStart = !noAutoStart && init.site.isNew;
-
- final SiteRun run;
- try {
- init.initializer.run();
- init.flags.deleteOnFailure = false;
-
- run = createSiteRun(init);
- run.upgradeSchema();
- } catch (Exception failure) {
- if (init.flags.deleteOnFailure) {
- recursiveDelete(getSitePath());
- }
- throw failure;
- } catch (Error failure) {
- if (init.flags.deleteOnFailure) {
- recursiveDelete(getSitePath());
- }
- throw failure;
- }
-
- System.err.println("Initialized " + getSitePath().getCanonicalPath());
- run.start();
- return 0;
- }
-
- static class SiteInit {
- final SitePaths site;
- final InitFlags flags;
- final ConsoleUI ui;
- final SitePathInitializer initializer;
-
- @Inject
- SiteInit(final SitePaths site, final InitFlags flags, final ConsoleUI ui,
- final SitePathInitializer initializer) {
- this.site = site;
- this.flags = flags;
- this.ui = ui;
- this.initializer = initializer;
- }
- }
-
- private SiteInit createSiteInit() {
- final ConsoleUI ui = ConsoleUI.getInstance(batchMode);
- final File sitePath = getSitePath();
- final List<Module> m = new ArrayList<Module>();
-
- m.add(new InitModule());
- m.add(new AbstractModule() {
- @Override
- protected void configure() {
- bind(ConsoleUI.class).toInstance(ui);
- bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath);
- List<String> plugins =
- Objects.firstNonNull(installPlugins, Lists.<String> newArrayList());
- bind(new TypeLiteral<List<String>>() {}).annotatedWith(
- InstallPlugins.class).toInstance(plugins);
- }
- });
-
- try {
- return Guice.createInjector(PRODUCTION, m).getInstance(SiteInit.class);
- } catch (CreationException ce) {
- final Message first = ce.getErrorMessages().iterator().next();
- Throwable why = first.getCause();
-
- if (why instanceof Die) {
- throw (Die) why;
- }
-
- final StringBuilder buf = new StringBuilder(ce.getMessage());
- while (why != null) {
- buf.append("\n");
- buf.append(why.getMessage());
- why = why.getCause();
- if (why != null) {
- buf.append("\n caused by ");
- }
- }
- throw die(buf.toString(), new RuntimeException("InitInjector failed", ce));
- }
- }
-
- static class SiteRun {
- final ConsoleUI ui;
- final SitePaths site;
- final InitFlags flags;
- final SchemaUpdater schemaUpdater;
- final SchemaFactory<ReviewDb> schema;
- final GitRepositoryManager repositoryManager;
- final Browser browser;
-
- @Inject
- SiteRun(final ConsoleUI ui, final SitePaths site, final InitFlags flags,
- final SchemaUpdater schemaUpdater,
- final SchemaFactory<ReviewDb> schema,
- final GitRepositoryManager repositoryManager,
- final Browser browser) {
- this.ui = ui;
- this.site = site;
- this.flags = flags;
- this.schemaUpdater = schemaUpdater;
- this.schema = schema;
- this.repositoryManager = repositoryManager;
- this.browser = browser;
- }
-
- void upgradeSchema() throws OrmException {
- final List<String> pruneList = new ArrayList<String>();
- schemaUpdater.update(new UpdateUI() {
- @Override
- public void message(String msg) {
- System.err.println(msg);
- System.err.flush();
- }
-
- @Override
- public boolean yesno(boolean def, String msg) {
- return ui.yesno(def, msg);
- }
-
- @Override
- public boolean isBatch() {
- return ui.isBatch();
- }
-
- @Override
- public void pruneSchema(StatementExecutor e, List<String> prune) {
- for (String p : prune) {
- if (!pruneList.contains(p)) {
- pruneList.add(p);
- }
+ if (!skipPlugins) {
+ final List<PluginData> plugins = InitPlugins.listPlugins(init.site);
+ ConsoleUI ui = ConsoleUI.getInstance(false);
+ verifyInstallPluginList(ui, plugins);
+ if (listPlugins) {
+ if (!plugins.isEmpty()) {
+ ui.message("Available plugins:\n");
+ for (PluginData plugin : plugins) {
+ ui.message(" * %s version %s\n", plugin.name, plugin.version);
}
- }
- });
-
- if (!pruneList.isEmpty()) {
- StringBuilder msg = new StringBuilder();
- msg.append("Execute the following SQL to drop unused objects:\n");
- msg.append("\n");
- for (String sql : pruneList) {
- msg.append(" ");
- msg.append(sql);
- msg.append(";\n");
- }
-
- if (ui.isBatch()) {
- System.err.print(msg);
- System.err.flush();
-
- } else if (ui.yesno(true, "%s\nExecute now", msg)) {
- final JdbcSchema db = (JdbcSchema) schema.open();
- try {
- final JdbcExecutor e = new JdbcExecutor(db);
- try {
- for (String sql : pruneList) {
- e.execute(sql);
- }
- } finally {
- e.close();
- }
- } finally {
- db.close();
- }
- }
- }
- }
-
- void start() throws Exception {
- if (flags.autoStart) {
- if (HostPlatform.isWin32()) {
- System.err.println("Automatic startup not supported on Win32.");
-
} else {
- startDaemon();
- if (!ui.isBatch()) {
- browser.open(PageLinks.ADMIN_PROJECTS);
- }
+ ui.message("No plugins found.\n");
}
}
}
-
- void startDaemon() {
- final String[] argv = {site.gerrit_sh.getAbsolutePath(), "start"};
- final Process proc;
- try {
- System.err.println("Executing " + argv[0] + " " + argv[1]);
- proc = Runtime.getRuntime().exec(argv);
- } catch (IOException e) {
- System.err.println("error: cannot start Gerrit: " + e.getMessage());
- return;
- }
-
- try {
- proc.getOutputStream().close();
- } catch (IOException e) {
- }
-
- IoUtil.copyWithThread(proc.getInputStream(), System.err);
- IoUtil.copyWithThread(proc.getErrorStream(), System.err);
-
- for (;;) {
- try {
- final int rc = proc.waitFor();
- if (rc != 0) {
- System.err.println("error: cannot start Gerrit: exit status " + rc);
- }
- break;
- } catch (InterruptedException e) {
- // retry
- }
- }
- }
-
}
- private SiteRun createSiteRun(final SiteInit init) {
- return createSysInjector(init).getInstance(SiteRun.class);
- }
-
- private Injector createSysInjector(final SiteInit init) {
- final List<Module> modules = new ArrayList<Module>();
+ @Override
+ protected void afterInit(SiteRun run) throws Exception {
+ List<Module> modules = Lists.newArrayList();
modules.add(new AbstractModule() {
@Override
protected void configure() {
- bind(ConsoleUI.class).toInstance(init.ui);
- bind(InitFlags.class).toInstance(init.flags);
+ bind(File.class).annotatedWith(SitePath.class).toInstance(getSitePath());
+ bind(Browser.class);
}
});
- return createDbInjector(SINGLE_USER).createChildInjector(modules);
+ modules.add(new GerritServerConfigModule());
+ Guice.createInjector(modules).injectMembers(this);
+ start(run);
}
- private static void recursiveDelete(File path) {
- File[] entries = path.listFiles();
- if (entries != null) {
- for (File e : entries) {
- recursiveDelete(e);
+ @Override
+ protected List<String> getInstallPlugins() {
+ return installPlugins;
+ }
+
+ @Override
+ protected ConsoleUI getConsoleUI() {
+ return ConsoleUI.getInstance(batchMode);
+ }
+
+ @Override
+ protected boolean getAutoStart() {
+ return !noAutoStart;
+ }
+
+ void start(SiteRun run) throws Exception {
+ if (run.flags.autoStart) {
+ if (HostPlatform.isWin32()) {
+ System.err.println("Automatic startup not supported on Win32.");
+
+ } else {
+ startDaemon(run);
+ if (!run.ui.isBatch()) {
+ browser.open(PageLinks.ADMIN_PROJECTS);
+ }
}
}
- if (!path.delete() && path.exists()) {
- System.err.println("warn: Cannot remove " + path);
+ }
+
+ void startDaemon(SiteRun run) {
+ final String[] argv = {run.site.gerrit_sh.getAbsolutePath(), "start"};
+ final Process proc;
+ try {
+ System.err.println("Executing " + argv[0] + " " + argv[1]);
+ proc = Runtime.getRuntime().exec(argv);
+ } catch (IOException e) {
+ System.err.println("error: cannot start Gerrit: " + e.getMessage());
+ return;
+ }
+
+ try {
+ proc.getOutputStream().close();
+ } catch (IOException e) {
+ }
+
+ IoUtil.copyWithThread(proc.getInputStream(), System.err);
+ IoUtil.copyWithThread(proc.getErrorStream(), System.err);
+
+ for (;;) {
+ try {
+ final int rc = proc.waitFor();
+ if (rc != 0) {
+ System.err.println("error: cannot start Gerrit: exit status " + rc);
+ }
+ break;
+ } catch (InterruptedException e) {
+ // retry
+ }
}
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java
index adbb03a..267b41a 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java
@@ -34,10 +34,14 @@
/** Run the daemon (and open the web UI in a browser) after initialization. */
public boolean autoStart;
+ /** Skip plugins */
+ public boolean skipPlugins;
+
public final FileBasedConfig cfg;
public final FileBasedConfig sec;
public final List<String> installPlugins;
+
@Inject
InitFlags(final SitePaths site,
final @InstallPlugins List<String> installPlugins) throws IOException,
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java
index 8b3d87e..94185eb 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java
@@ -23,6 +23,13 @@
/** Injection configuration for the site initialization process. */
public class InitModule extends FactoryModule {
+
+ private final boolean standalone;
+
+ public InitModule(boolean standalone) {
+ this.standalone = standalone;
+ }
+
@Override
protected void configure() {
bind(SitePaths.class);
@@ -36,14 +43,20 @@
step().to(UpgradeFrom2_0_x.class);
step().to(InitGitManager.class);
- step().to(InitDatabase.class);
+ if (standalone) {
+ step().to(InitDatabase.class);
+ }
step().to(InitAuth.class);
step().to(InitSendEmail.class);
- step().to(InitContainer.class);
+ if (standalone) {
+ step().to(InitContainer.class);
+ }
step().to(InitSshd.class);
step().to(InitHttpd.class);
step().to(InitCache.class);
- step().to(InitPlugins.class);
+ if (standalone) {
+ step().to(InitPlugins.class);
+ }
}
protected LinkedBindingBuilder<InitStep> step() {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
index e71f3d1..71a4d86 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
@@ -22,7 +22,7 @@
import static com.google.gerrit.pgm.init.InitUtil.saveSecure;
import static com.google.gerrit.pgm.init.InitUtil.version;
-import com.google.gerrit.pgm.Init;
+import com.google.gerrit.pgm.BaseInit;
import com.google.gerrit.pgm.util.ConsoleUI;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.mail.OutgoingEmail;
@@ -75,13 +75,17 @@
mkdir(site.data_dir);
for (InitStep step : steps) {
+ if (step instanceof InitPlugins
+ && flags.skipPlugins) {
+ continue;
+ }
step.run();
}
savePublic(flags.cfg);
saveSecure(flags.sec);
- extract(site.gerrit_sh, Init.class, "gerrit.sh");
+ extract(site.gerrit_sh, BaseInit.class, "gerrit.sh");
chmod(0755, site.gerrit_sh);
chmod(0700, site.tmp_dir);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
index f4f0bd2..11968db 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
@@ -17,6 +17,8 @@
import static com.google.inject.Scopes.SINGLETON;
import static com.google.inject.Stage.PRODUCTION;
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.GerritServerConfigModule;
@@ -29,11 +31,15 @@
import com.google.gerrit.server.schema.SchemaModule;
import com.google.gwtorm.server.OrmException;
import com.google.inject.AbstractModule;
+import com.google.inject.Binding;
import com.google.inject.CreationException;
import com.google.inject.Guice;
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 com.google.inject.name.Named;
import com.google.inject.name.Names;
import com.google.inject.spi.Message;
@@ -41,6 +47,8 @@
import org.kohsuke.args4j.Option;
import java.io.File;
+import java.lang.annotation.Annotation;
+import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
@@ -51,11 +59,14 @@
@Option(name = "--site-path", aliases = {"-d"}, usage = "Local directory containing site data")
private File sitePath = new File(".");
+ protected Provider<DataSource> dsProvider;
+
protected SiteProgram() {
}
- protected SiteProgram(File sitePath) {
+ protected SiteProgram(File sitePath, final Provider<DataSource> dsProvider) {
this.sitePath = sitePath;
+ this.dsProvider = dsProvider;
}
/** @return the site path specified on the command line. */
@@ -92,17 +103,31 @@
@Override
protected void configure() {
bind(DataSourceProvider.Context.class).toInstance(context);
- bind(Key.get(DataSource.class, Names.named("ReviewDb")))
- .toProvider(SiteLibraryBasedDataSourceProvider.class)
- .in(SINGLETON);
- listener().to(SiteLibraryBasedDataSourceProvider.class);
+ if (dsProvider != null) {
+ bind(Key.get(DataSource.class, Names.named("ReviewDb")))
+ .toProvider(dsProvider)
+ .in(SINGLETON);
+ if (LifecycleListener.class.isAssignableFrom(dsProvider.getClass())) {
+ listener().toInstance((LifecycleListener) dsProvider);
+ }
+ } else {
+ bind(Key.get(DataSource.class, Names.named("ReviewDb")))
+ .toProvider(SiteLibraryBasedDataSourceProvider.class)
+ .in(SINGLETON);
+ listener().to(SiteLibraryBasedDataSourceProvider.class);
+ }
}
});
Module configModule = new GerritServerConfigModule();
modules.add(configModule);
Injector cfgInjector = Guice.createInjector(sitePathModule, configModule);
Config cfg = cfgInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
- String dbType = cfg.getString("database", null, "type");
+ String dbType;
+ if (dsProvider != null) {
+ dbType = getDbType(dsProvider);
+ } else {
+ dbType = cfg.getString("database", null, "type");
+ }
final DataSourceType dst = Guice.createInjector(new DataSourceModule(), configModule,
sitePathModule).getInstance(
@@ -151,6 +176,44 @@
}
}
+ private String getDbType(Provider<DataSource> dsProvider) {
+ String dbProductName;
+ try {
+ Connection conn = dsProvider.get().getConnection();
+ try {
+ dbProductName = conn.getMetaData().getDatabaseProductName().toLowerCase();
+ } finally {
+ conn.close();
+ }
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+
+ List<Module> modules = Lists.newArrayList();
+ modules.add(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath);
+ }
+ });
+ modules.add(new GerritServerConfigModule());
+ modules.add(new DataSourceModule());
+ Injector i = Guice.createInjector(modules);
+ List<Binding<DataSourceType>> dsTypeBindings =
+ i.findBindingsByType(new TypeLiteral<DataSourceType>() {});
+ for (Binding<DataSourceType> binding : dsTypeBindings) {
+ Annotation annotation = binding.getKey().getAnnotation();
+ if (annotation instanceof Named) {
+ if (((Named) annotation).value().toLowerCase().contains(dbProductName)) {
+ return ((Named) annotation).value();
+ }
+ }
+ }
+ throw new IllegalStateException(String.format(
+ "Cannot guess database type from the database product name '%s'",
+ dbProductName));
+ }
+
@SuppressWarnings("deprecation")
private static boolean isCannotCreatePoolException(Throwable why) {
return why instanceof org.apache.commons.dbcp.SQLNestedException
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
index bdba433..f79e05f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
@@ -397,6 +397,13 @@
final GitReferenceUpdated gitRefUpdated, final ReviewDb db)
throws NoSuchChangeException, OrmException, IOException {
final Change.Id changeId = patchSetId.getParentKey();
+ deleteDraftChange(changeId, gitManager, gitRefUpdated, db);
+ }
+
+ public static void deleteDraftChange(final Change.Id changeId,
+ GitRepositoryManager gitManager,
+ final GitReferenceUpdated gitRefUpdated, final ReviewDb db)
+ throws NoSuchChangeException, OrmException, IOException {
final Change change = db.changes().get(changeId);
if (change == null || change.getStatus() != Change.Status.DRAFT) {
throw new NoSuchChangeException(changeId);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java
new file mode 100644
index 0000000..7d3a012
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java
@@ -0,0 +1,90 @@
+// 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.change;
+
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.UiAction;
+import com.google.gerrit.reviewdb.client.Change.Status;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.change.DeleteDraftChange.Input;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.io.IOException;
+
+public class DeleteDraftChange implements
+ RestModifyView<ChangeResource, Input>, UiAction<ChangeResource> {
+ public static class Input {
+ }
+
+ protected final Provider<ReviewDb> dbProvider;
+ private final GitRepositoryManager gitManager;
+ private final GitReferenceUpdated gitRefUpdated;
+
+ @Inject
+ public DeleteDraftChange(Provider<ReviewDb> dbProvider,
+ GitRepositoryManager gitManager,
+ GitReferenceUpdated gitRefUpdated,
+ PatchSetInfoFactory patchSetInfoFactory) {
+ this.dbProvider = dbProvider;
+ this.gitManager = gitManager;
+ this.gitRefUpdated = gitRefUpdated;
+ }
+
+ @Override
+ public Object apply(ChangeResource rsrc, Input input)
+ throws ResourceConflictException, AuthException,
+ ResourceNotFoundException, OrmException, IOException {
+ if (rsrc.getChange().getStatus() != Status.DRAFT) {
+ throw new ResourceConflictException("Change is not a draft");
+ }
+
+ if (!rsrc.getControl().canDeleteDraft(dbProvider.get())) {
+ throw new AuthException("Not permitted to delete this draft change");
+ }
+
+ try {
+ ChangeUtil.deleteDraftChange(rsrc.getChange().getId(),
+ gitManager, gitRefUpdated, dbProvider.get());
+ } catch (NoSuchChangeException e) {
+ throw new ResourceNotFoundException(e.getMessage());
+ }
+
+ return Response.none();
+ }
+
+ @Override
+ public UiAction.Description getDescription(ChangeResource rsrc) {
+ try {
+ return new UiAction.Description()
+ .setTitle(String.format("Delete Draft Change %d",
+ rsrc.getChange().getChangeId()))
+ .setVisible(rsrc.getChange().getStatus() == Status.DRAFT
+ && rsrc.getControl().canDeleteDraft(dbProvider.get()));
+ } catch (OrmException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java
new file mode 100644
index 0000000..d4e17d6
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java
@@ -0,0 +1,161 @@
+// 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.change;
+
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.UiAction;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetInfo;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.change.DeleteDraftPatchSet.Input;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gwtorm.server.AtomicUpdate;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.io.IOException;
+
+public class DeleteDraftPatchSet implements RestModifyView<RevisionResource, Input>,
+ UiAction<RevisionResource> {
+ public static class Input {
+ }
+
+ protected final Provider<ReviewDb> dbProvider;
+ private final GitRepositoryManager gitManager;
+ private final GitReferenceUpdated gitRefUpdated;
+ private final PatchSetInfoFactory patchSetInfoFactory;
+
+ @Inject
+ public DeleteDraftPatchSet(Provider<ReviewDb> dbProvider,
+ GitRepositoryManager gitManager,
+ GitReferenceUpdated gitRefUpdated,
+ PatchSetInfoFactory patchSetInfoFactory) {
+ this.dbProvider = dbProvider;
+ this.gitManager = gitManager;
+ this.gitRefUpdated = gitRefUpdated;
+ this.patchSetInfoFactory = patchSetInfoFactory;
+ }
+
+ @Override
+ public Object apply(RevisionResource rsrc, Input input)
+ throws ResourceNotFoundException, AuthException, OrmException,
+ IOException, ResourceConflictException {
+ PatchSet patchSet = rsrc.getPatchSet();
+ PatchSet.Id patchSetId = patchSet.getId();
+ Change change = rsrc.getChange();
+
+ if (!patchSet.isDraft()) {
+ throw new ResourceConflictException("Patch set is not a draft.");
+ }
+
+ if (!rsrc.getControl().canDeleteDraft(dbProvider.get())) {
+ throw new AuthException("Not permitted to delete this draft patch set");
+ }
+
+ deleteDraftPatchSet(patchSet, change);
+ deleteOrUpdateDraftChange(patchSetId, change);
+
+ return Response.none();
+ }
+
+ @Override
+ public UiAction.Description getDescription(RevisionResource rsrc) {
+ PatchSet.Id current = rsrc.getChange().currentPatchSetId();
+ try {
+ int psCount = dbProvider.get().patchSets()
+ .byChange(rsrc.getChange().getId()).toList().size();
+ return new UiAction.Description()
+ .setTitle(String.format("Delete Draft Revision %d",
+ rsrc.getPatchSet().getPatchSetId()))
+ .setVisible(rsrc.getPatchSet().isDraft()
+ && rsrc.getPatchSet().getId().equals(current)
+ && rsrc.getControl().canDeleteDraft(dbProvider.get())
+ && psCount > 1);
+ } catch (OrmException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private void deleteDraftPatchSet(PatchSet patchSet, Change change)
+ throws ResourceNotFoundException, OrmException, IOException {
+ try {
+ ChangeUtil.deleteOnlyDraftPatchSet(patchSet,
+ change, gitManager, gitRefUpdated, dbProvider.get());
+ } catch (NoSuchChangeException e) {
+ throw new ResourceNotFoundException(e.getMessage());
+ }
+ }
+
+ private void deleteOrUpdateDraftChange(PatchSet.Id patchSetId,
+ Change change) throws OrmException, ResourceNotFoundException,
+ IOException {
+ if (dbProvider.get()
+ .patchSets()
+ .byChange(change.getId())
+ .toList().size() == 0) {
+ deleteDraftChange(patchSetId);
+ } else {
+ if (change.currentPatchSetId().equals(patchSetId)) {
+ updateCurrentPatchSet(dbProvider.get(), change,
+ previousPatchSetInfo(patchSetId));
+ }
+ }
+ }
+
+ private void deleteDraftChange(PatchSet.Id patchSetId)
+ throws OrmException, IOException, ResourceNotFoundException {
+ try {
+ ChangeUtil.deleteDraftChange(patchSetId,
+ gitManager, gitRefUpdated, dbProvider.get());
+ } catch (NoSuchChangeException e) {
+ throw new ResourceNotFoundException(e.getMessage());
+ }
+ }
+
+ private PatchSetInfo previousPatchSetInfo(PatchSet.Id patchSetId)
+ throws ResourceNotFoundException {
+ try {
+ return patchSetInfoFactory.get(dbProvider.get(),
+ new PatchSet.Id(patchSetId.getParentKey(),
+ patchSetId.get() - 1));
+ } catch (PatchSetInfoNotAvailableException e) {
+ throw new ResourceNotFoundException(e.getMessage());
+ }
+ }
+
+ private static void updateCurrentPatchSet(final ReviewDb db,
+ final Change change, final PatchSetInfo psInfo)
+ throws OrmException {
+ db.changes().atomicUpdate(change.getId(), new AtomicUpdate<Change>() {
+ @Override
+ public Change update(Change c) {
+ c.setCurrentPatchSet(psInfo);
+ ChangeUtil.updated(c);
+ return c;
+ }
+ });
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
index ac09374..3925f15 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
@@ -27,6 +27,7 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.change.DeleteReviewer.Input;
+import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -39,10 +40,12 @@
}
private final Provider<ReviewDb> dbProvider;
+ private final ChangeIndexer indexer;
@Inject
- DeleteReviewer(Provider<ReviewDb> dbProvider) {
+ DeleteReviewer(Provider<ReviewDb> dbProvider, ChangeIndexer indexer) {
this.dbProvider = dbProvider;
+ this.indexer = indexer;
}
@Override
@@ -70,6 +73,7 @@
} finally {
db.rollback();
}
+ indexer.index(rsrc.getChange());
return Response.none();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java
new file mode 100644
index 0000000..b02f6f0
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java
@@ -0,0 +1,159 @@
+// 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.change;
+
+import com.google.gerrit.common.data.IncludedInDetail;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Resolve in which tags and branches a commit is included.
+ */
+public class IncludedInResolver {
+
+ private static final Logger log = LoggerFactory
+ .getLogger(IncludedInResolver.class);
+
+ public static IncludedInDetail resolve(final Repository repo,
+ final RevWalk rw, final RevCommit commit) throws IOException {
+
+ Set<Ref> tags =
+ new HashSet<Ref>(repo.getRefDatabase().getRefs(Constants.R_TAGS)
+ .values());
+ Set<Ref> branches =
+ new HashSet<Ref>(repo.getRefDatabase().getRefs(Constants.R_HEADS)
+ .values());
+ Set<Ref> allTagsAndBranches = new HashSet<Ref>();
+ allTagsAndBranches.addAll(tags);
+ allTagsAndBranches.addAll(branches);
+ Set<Ref> allMatchingTagsAndBranches =
+ includedIn(repo, rw, commit, allTagsAndBranches);
+
+ IncludedInDetail detail = new IncludedInDetail();
+ detail
+ .setBranches(getMatchingRefNames(allMatchingTagsAndBranches, branches));
+ detail.setTags(getMatchingRefNames(allMatchingTagsAndBranches, tags));
+
+ return detail;
+ }
+
+ /**
+ * Resolves which tip refs include the target commit.
+ */
+ private static Set<Ref> includedIn(final Repository repo, final RevWalk rw,
+ final RevCommit target, final Set<Ref> tipRefs) throws IOException,
+ MissingObjectException, IncorrectObjectTypeException {
+
+ Set<Ref> result = new HashSet<Ref>();
+
+ Map<RevCommit, Set<Ref>> tipsAndCommits = parseCommits(repo, rw, tipRefs);
+
+ List<RevCommit> tips = new ArrayList<RevCommit>(tipsAndCommits.keySet());
+ Collections.sort(tips, new Comparator<RevCommit>() {
+ @Override
+ public int compare(RevCommit c1, RevCommit c2) {
+ return c1.getCommitTime() - c2.getCommitTime();
+ }
+ });
+
+ Set<RevCommit> targetReachableFrom = new HashSet<RevCommit>();
+ targetReachableFrom.add(target);
+
+ for (RevCommit tip : tips) {
+ boolean commitFound = false;
+ rw.resetRetain(RevFlag.UNINTERESTING);
+ rw.markStart(tip);
+ for (RevCommit commit : rw) {
+ if (targetReachableFrom.contains(commit)) {
+ commitFound = true;
+ targetReachableFrom.add(tip);
+ result.addAll(tipsAndCommits.get(tip));
+ break;
+ }
+ }
+ if (!commitFound) {
+ rw.markUninteresting(tip);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns the short names of refs which are as well in the matchingRefs list
+ * as well as in the allRef list.
+ */
+ private static List<String> getMatchingRefNames(Set<Ref> matchingRefs,
+ Set<Ref> allRefs) {
+ List<String> refNames = new ArrayList<String>();
+ for (Ref matchingRef : matchingRefs) {
+ if (allRefs.contains(matchingRef)) {
+ refNames.add(Repository.shortenRefName(matchingRef.getName()));
+ }
+ }
+ return refNames;
+ }
+
+ /**
+ * Parse commit of ref and store the relation between ref and commit.
+ */
+ private static Map<RevCommit, Set<Ref>> parseCommits(final Repository repo,
+ final RevWalk rw, final Set<Ref> refs) throws IOException {
+ Map<RevCommit, Set<Ref>> result = new HashMap<RevCommit, Set<Ref>>();
+ for (Ref ref : refs) {
+ final RevCommit commit;
+ try {
+ commit = rw.parseCommit(ref.getObjectId());
+ } catch (IncorrectObjectTypeException notCommit) {
+ // Its OK for a tag reference to point to a blob or a tree, this
+ // is common in the Linux kernel or git.git repository.
+ //
+ continue;
+ } catch (MissingObjectException notHere) {
+ // Log the problem with this branch, but keep processing.
+ //
+ log.warn("Reference " + ref.getName() + " in " + repo.getDirectory()
+ + " points to dangling object " + ref.getObjectId());
+ continue;
+ }
+ Set<Ref> relatedRefs = result.get(commit);
+ if (relatedRefs == null) {
+ relatedRefs = new HashSet<Ref>();
+ result.put(commit, relatedRefs);
+ }
+ relatedRefs.add(ref);
+ }
+ return result;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
index b96e296..25bd80f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
@@ -49,7 +49,9 @@
get(CHANGE_KIND, "topic").to(GetTopic.class);
put(CHANGE_KIND, "topic").to(PutTopic.class);
delete(CHANGE_KIND, "topic").to(PutTopic.class);
+ delete(CHANGE_KIND).to(DeleteDraftChange.class);
post(CHANGE_KIND, "abandon").to(Abandon.class);
+ post(CHANGE_KIND, "publish").to(Publish.CurrentRevision.class);
post(CHANGE_KIND, "restore").to(Restore.class);
post(CHANGE_KIND, "revert").to(Revert.class);
post(CHANGE_KIND, "submit").to(Submit.CurrentRevision.class);
@@ -64,7 +66,9 @@
child(CHANGE_KIND, "revisions").to(Revisions.class);
post(REVISION_KIND, "cherrypick").to(CherryPick.class);
get(REVISION_KIND, "commit").to(GetCommit.class);
+ delete(REVISION_KIND).to(DeleteDraftPatchSet.class);
get(REVISION_KIND, "mergeable").to(Mergeable.class);
+ post(REVISION_KIND, "publish").to(Publish.class);
get(REVISION_KIND, "related").to(GetRelated.class);
get(REVISION_KIND, "review").to(GetReview.class);
post(REVISION_KIND, "review").to(PostReview.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
index 5a6d441..f41d97d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
@@ -48,6 +48,7 @@
import com.google.gerrit.server.change.ReviewerJson.ReviewerInfo;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.group.GroupsCollection;
+import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.mail.AddReviewerSender;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchProjectException;
@@ -94,6 +95,7 @@
private final ChangeHooks hooks;
private final AccountCache accountCache;
private final ReviewerJson json;
+ private final ChangeIndexer indexer;
@Inject
PostReviewers(AccountsCollection accounts,
@@ -108,7 +110,8 @@
@GerritServerConfig Config cfg,
ChangeHooks hooks,
AccountCache accountCache,
- ReviewerJson json) {
+ ReviewerJson json,
+ ChangeIndexer indexer) {
this.accounts = accounts;
this.reviewerFactory = reviewerFactory;
this.addReviewerSenderFactory = addReviewerSenderFactory;
@@ -122,6 +125,7 @@
this.hooks = hooks;
this.accountCache = accountCache;
this.json = json;
+ this.indexer = indexer;
}
@Override
@@ -255,6 +259,7 @@
db.rollback();
}
+ indexer.index(rsrc.getChange());
accountLoaderFactory.create(true).fill(result.reviewers);
postAdd(rsrc.getChange(), result);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Publish.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Publish.java
new file mode 100644
index 0000000..7355ef3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Publish.java
@@ -0,0 +1,161 @@
+// 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.change;
+
+import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.UiAction;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.mail.PatchSetNotificationSender;
+import com.google.gerrit.server.change.Publish.Input;
+import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
+import com.google.gwtorm.server.AtomicUpdate;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.io.IOException;
+
+public class Publish implements RestModifyView<RevisionResource, Input>,
+ UiAction<RevisionResource> {
+ public static class Input {
+ }
+
+ private final Provider<ReviewDb> dbProvider;
+ private final PatchSetNotificationSender sender;
+ private final ChangeHooks hooks;
+ private final ChangeIndexer indexer;
+
+ @Inject
+ public Publish(Provider<ReviewDb> dbProvider,
+ PatchSetNotificationSender sender,
+ ChangeHooks hooks,
+ ChangeIndexer indexer) {
+ this.dbProvider = dbProvider;
+ this.sender = sender;
+ this.hooks = hooks;
+ this.indexer = indexer;
+ }
+
+ @Override
+ public Object apply(RevisionResource rsrc, Input input) throws IOException,
+ ResourceNotFoundException, ResourceConflictException,
+ OrmException, AuthException {
+ if (!rsrc.getPatchSet().isDraft()) {
+ throw new ResourceConflictException("Patch set is not a draft");
+ }
+
+ if (!rsrc.getControl().canPublish(dbProvider.get())) {
+ throw new AuthException("Cannot publish this draft patch set");
+ }
+
+ PatchSet updatedPatchSet = updateDraftPatchSet(rsrc);
+ Change updatedChange = updateDraftChange(rsrc);
+
+ try {
+ if (!updatedPatchSet.isDraft()
+ || updatedChange.getStatus() == Change.Status.NEW) {
+ indexer.index(updatedChange);
+ hooks.doDraftPublishedHook(updatedChange, updatedPatchSet, dbProvider.get());
+ sender.send(rsrc.getChange().getStatus() == Change.Status.DRAFT,
+ rsrc.getUser(), updatedChange, updatedPatchSet,
+ rsrc.getControl().getLabelTypes());
+ }
+ } catch (PatchSetInfoNotAvailableException e) {
+ throw new ResourceNotFoundException(e.getMessage());
+ }
+
+ return Response.none();
+ }
+
+ private Change updateDraftChange(RevisionResource rsrc) throws OrmException {
+ Change updatedChange = dbProvider.get().changes()
+ .atomicUpdate(rsrc.getChange().getId(),
+ new AtomicUpdate<Change>() {
+ @Override
+ public Change update(Change change) {
+ if (change.getStatus() == Change.Status.DRAFT) {
+ change.setStatus(Change.Status.NEW);
+ ChangeUtil.updated(change);
+ }
+ return change;
+ }
+ });
+ return updatedChange;
+ }
+
+ private PatchSet updateDraftPatchSet(RevisionResource rsrc) throws OrmException {
+ final PatchSet updatedPatchSet = dbProvider.get().patchSets()
+ .atomicUpdate(rsrc.getPatchSet().getId(),
+ new AtomicUpdate<PatchSet>() {
+ @Override
+ public PatchSet update(PatchSet patchset) {
+ patchset.setDraft(false);
+ return patchset;
+ }
+ });
+ return updatedPatchSet;
+ }
+
+ @Override
+ public UiAction.Description getDescription(RevisionResource rsrc) {
+ PatchSet.Id current = rsrc.getChange().currentPatchSetId();
+ try {
+ return new UiAction.Description()
+ .setTitle(String.format("Publish Revision %d",
+ rsrc.getPatchSet().getPatchSetId()))
+ .setVisible(rsrc.getPatchSet().isDraft()
+ && rsrc.getPatchSet().getId().equals(current)
+ && rsrc.getControl().canPublish(dbProvider.get()));
+ } catch (OrmException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ public static class CurrentRevision implements
+ RestModifyView<ChangeResource, Input> {
+ private final Provider<ReviewDb> dbProvider;
+ private final Publish publish;
+
+ @Inject
+ CurrentRevision(Provider<ReviewDb> dbProvider,
+ Publish publish) {
+ this.dbProvider = dbProvider;
+ this.publish = publish;
+ }
+
+ @Override
+ public Object apply(ChangeResource rsrc, Input input) throws AuthException,
+ ResourceConflictException, ResourceConflictException, IOException,
+ OrmException, ResourceNotFoundException, AuthException {
+ PatchSet ps = dbProvider.get().patchSets()
+ .get(rsrc.getChange().currentPatchSetId());
+ if (ps == null) {
+ throw new ResourceConflictException("current revision is missing");
+ } else if (!rsrc.getControl().isPatchVisible(ps, dbProvider.get())) {
+ throw new AuthException("current revision not accessible");
+ }
+ return publish.apply(new RevisionResource(rsrc, ps), input);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/DeleteDraftPatchSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/DeleteDraftPatchSet.java
index adec292..d20b0f6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/DeleteDraftPatchSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/DeleteDraftPatchSet.java
@@ -112,9 +112,7 @@
}
if (change.currentPatchSetId().equals(patchSetId)) {
try {
- PatchSet.Id id =
- new PatchSet.Id(patchSetId.getParentKey(), patchSetId.get() - 1);
- change.setCurrentPatchSet(patchSetInfoFactory.get(db, id));
+ change.setCurrentPatchSet(patchSetInfoFactory.get(db, highestId));
} catch (PatchSetInfoNotAvailableException e) {
throw new NoSuchChangeException(changeId);
}
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 2fbc79f..fd0596e 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
@@ -15,18 +15,11 @@
package com.google.gerrit.server.changedetail;
-import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromApprovals;
-import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromFooters;
-
import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.ReviewResult;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeUtil;
@@ -35,7 +28,7 @@
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.PatchSetNotificationSender;
import com.google.gerrit.server.mail.ReplacePatchSetSender;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
@@ -46,23 +39,10 @@
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.FooterLine;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
import java.util.concurrent.Callable;
public class PublishDraft implements Callable<ReviewResult> {
- private static final Logger log =
- LoggerFactory.getLogger(PublishDraft.class);
-
public interface Factory {
PublishDraft create(PatchSet.Id patchSetId);
}
@@ -70,13 +50,8 @@
private final ChangeControl.Factory changeControlFactory;
private final ReviewDb db;
private final ChangeHooks hooks;
- private final GitRepositoryManager repoManager;
- private final PatchSetInfoFactory patchSetInfoFactory;
- private final ApprovalsUtil approvalsUtil;
- private final AccountResolver accountResolver;
- private final CreateChangeSender.Factory createChangeSenderFactory;
- private final ReplacePatchSetSender.Factory replacePatchSetFactory;
private final ChangeIndexer indexer;
+ private final PatchSetNotificationSender sender;
private final PatchSet.Id patchSetId;
@@ -90,17 +65,13 @@
final CreateChangeSender.Factory createChangeSenderFactory,
final ReplacePatchSetSender.Factory replacePatchSetFactory,
final ChangeIndexer indexer,
+ final PatchSetNotificationSender sender,
@Assisted final PatchSet.Id patchSetId) {
this.changeControlFactory = changeControlFactory;
this.db = db;
this.hooks = hooks;
- this.repoManager = repoManager;
- this.patchSetInfoFactory = patchSetInfoFactory;
- this.approvalsUtil = approvalsUtil;
- this.accountResolver = accountResolver;
- this.createChangeSenderFactory = createChangeSenderFactory;
- this.replacePatchSetFactory = replacePatchSetFactory;
this.indexer = indexer;
+ this.sender = sender;
this.patchSetId = patchSetId;
}
@@ -153,7 +124,7 @@
indexer.index(updatedChange);
hooks.doDraftPublishedHook(updatedChange, updatedPatchSet, db);
- sendNotifications(control.getChange().getStatus() == Change.Status.DRAFT,
+ sender.send(control.getChange().getStatus() == Change.Status.DRAFT,
(IdentifiedUser) control.getCurrentUser(), updatedChange, updatedPatchSet,
labelTypes);
}
@@ -161,66 +132,4 @@
return result;
}
-
- private void sendNotifications(final boolean newChange,
- final IdentifiedUser currentUser, final Change updatedChange,
- final PatchSet updatedPatchSet, final LabelTypes labelTypes)
- throws OrmException, IOException, PatchSetInfoNotAvailableException {
- final Repository git = repoManager.openRepository(updatedChange.getProject());
- try {
- final RevWalk revWalk = new RevWalk(git);
- final RevCommit commit;
- try {
- commit = revWalk.parseCommit(ObjectId.fromString(updatedPatchSet.getRevision().get()));
- } finally {
- revWalk.release();
- }
- final PatchSetInfo info = patchSetInfoFactory.get(commit, updatedPatchSet.getId());
- final List<FooterLine> footerLines = commit.getFooterLines();
- final Account.Id me = currentUser.getAccountId();
- final MailRecipients recipients =
- getRecipientsFromFooters(accountResolver, updatedPatchSet, footerLines);
- recipients.remove(me);
-
- if (newChange) {
- approvalsUtil.addReviewers(db, labelTypes, updatedChange, updatedPatchSet, info,
- recipients.getReviewers(), Collections.<Account.Id> emptySet());
- try {
- CreateChangeSender cm = createChangeSenderFactory.create(updatedChange);
- cm.setFrom(me);
- cm.setPatchSet(updatedPatchSet, info);
- cm.addReviewers(recipients.getReviewers());
- cm.addExtraCC(recipients.getCcOnly());
- cm.send();
- } catch (Exception e) {
- log.error("Cannot send email for new change " + updatedChange.getId(), e);
- }
- } else {
- final List<PatchSetApproval> patchSetApprovals =
- db.patchSetApprovals().byChange(updatedChange.getId()).toList();
- final MailRecipients oldRecipients =
- getRecipientsFromApprovals(patchSetApprovals);
- approvalsUtil.addReviewers(db, labelTypes, updatedChange, updatedPatchSet, info,
- recipients.getReviewers(), oldRecipients.getAll());
- final ChangeMessage msg =
- new ChangeMessage(new ChangeMessage.Key(updatedChange.getId(),
- ChangeUtil.messageUUID(db)), me,
- updatedPatchSet.getCreatedOn(), updatedPatchSet.getId());
- msg.setMessage("Uploaded patch set " + updatedPatchSet.getPatchSetId() + ".");
- try {
- ReplacePatchSetSender cm = replacePatchSetFactory.create(updatedChange);
- cm.setFrom(me);
- cm.setPatchSet(updatedPatchSet, info);
- cm.setChangeMessage(msg);
- cm.addReviewers(recipients.getReviewers());
- cm.addExtraCC(recipients.getCcOnly());
- cm.send();
- } catch (Exception e) {
- log.error("Cannot send email for new patch set " + updatedPatchSet.getId(), e);
- }
- }
- } finally {
- git.close();
- }
- }
}
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 f09015b..c4d06d2 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
@@ -509,7 +509,7 @@
if (rw.isMergedInto(commit, branchTip)) {
commit.statusCode = CommitMergeStatus.ALREADY_MERGED;
try {
- setMergedPatchSet(chg.getId(), ps.getId());
+ setMerged(chg, null);
} catch (OrmException e) {
log.error("Cannot mark change " + chg.getId() + " merged", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
index a803ddd..3df559d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -59,6 +59,7 @@
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
@@ -127,6 +128,7 @@
private static final String KEY_COPY_MAX_SCORE = "copyMaxScore";
private static final String KEY_VALUE = "value";
private static final String KEY_CAN_OVERRIDE = "canOverride";
+ private static final String KEY_Branch = "branch";
private static final Set<String> LABEL_FUNCTIONS = ImmutableSet.of(
"MaxWithBlock", "AnyWithBlock", "MaxNoBlock", "NoBlock", "NoOp");
@@ -651,10 +653,17 @@
rc.getBoolean(LABEL, name, KEY_COPY_MAX_SCORE, false));
label.setCanOverride(
rc.getBoolean(LABEL, name, KEY_CAN_OVERRIDE, true));
+ label.setRefPatterns(getStringListOrNull(rc, LABEL, name, KEY_Branch));
labelSections.put(name, label);
}
}
+ private List<String> getStringListOrNull(Config rc, String section,
+ String subSection, String name) {
+ String[] ac = rc.getStringList(section, subSection, name);
+ return ac.length == 0 ? null : Arrays.asList(ac);
+ }
+
private void loadCommentLinkSections(Config rc) {
Set<String> subsections = rc.getSubsections(COMMENTLINK);
commentLinkSections = Lists.newArrayListWithCapacity(subsections.size());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java
index dd94577..ccd6c89 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java
@@ -126,7 +126,10 @@
@Override
public int getCost() {
- return pred.getCost();
+ // Index queries are assumed to be cheaper than any other type of query, so
+ // so try to make sure they get picked. Note that pred's cost may be higher
+ // because it doesn't know whether it's being used in an index query or not.
+ return 0;
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
index 5b3f59a..cd6409e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
@@ -170,7 +170,6 @@
CommentRange range = comment.getRange();
if (range != null) {
String prefix = String.format("Line %d: ", range.getStartLine());
- out.append(prefix);
for (int n = range.getStartLine(); n <= range.getEndLine(); n++) {
out.append(n == range.getStartLine()
? prefix
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java
new file mode 100644
index 0000000..3484dd8
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java
@@ -0,0 +1,149 @@
+// 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.mail;
+
+import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromApprovals;
+import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromFooters;
+
+import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.common.data.LabelTypes;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.ChangeMessage;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.client.PatchSetInfo;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
+import com.google.gerrit.server.ChangeUtil;
+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.MailUtil.MailRecipients;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.FooterLine;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+public class PatchSetNotificationSender {
+ private static final Logger log =
+ LoggerFactory.getLogger(PatchSetNotificationSender.class);
+
+ private final ReviewDb db;
+ private final GitRepositoryManager repoManager;
+ private final PatchSetInfoFactory patchSetInfoFactory;
+ private final ApprovalsUtil approvalsUtil;
+ private final AccountResolver accountResolver;
+ private final CreateChangeSender.Factory createChangeSenderFactory;
+ private final ReplacePatchSetSender.Factory replacePatchSetFactory;
+
+ @Inject
+ public PatchSetNotificationSender(ReviewDb db,
+ ChangeHooks hooks,
+ GitRepositoryManager repoManager,
+ PatchSetInfoFactory patchSetInfoFactory,
+ ApprovalsUtil approvalsUtil,
+ AccountResolver accountResolver,
+ CreateChangeSender.Factory createChangeSenderFactory,
+ ReplacePatchSetSender.Factory replacePatchSetFactory,
+ ChangeIndexer indexer) {
+ this.db = db;
+ this.repoManager = repoManager;
+ this.patchSetInfoFactory = patchSetInfoFactory;
+ this.approvalsUtil = approvalsUtil;
+ this.accountResolver = accountResolver;
+ this.createChangeSenderFactory = createChangeSenderFactory;
+ this.replacePatchSetFactory = replacePatchSetFactory;
+ }
+
+ public void send(final boolean newChange,
+ final IdentifiedUser currentUser, final Change updatedChange,
+ final PatchSet updatedPatchSet, final LabelTypes labelTypes)
+ throws OrmException, IOException, PatchSetInfoNotAvailableException {
+ final Repository git = repoManager.openRepository(updatedChange.getProject());
+ try {
+ final RevWalk revWalk = new RevWalk(git);
+ final RevCommit commit;
+ try {
+ commit = revWalk.parseCommit(ObjectId.fromString(
+ updatedPatchSet.getRevision().get()));
+ } finally {
+ revWalk.release();
+ }
+ final PatchSetInfo info = patchSetInfoFactory.get(commit, updatedPatchSet.getId());
+ final List<FooterLine> footerLines = commit.getFooterLines();
+ final Account.Id me = currentUser.getAccountId();
+ final MailRecipients recipients =
+ getRecipientsFromFooters(accountResolver, updatedPatchSet, footerLines);
+ recipients.remove(me);
+
+ if (newChange) {
+ approvalsUtil.addReviewers(db, labelTypes,
+ updatedChange, updatedPatchSet, info,
+ recipients.getReviewers(), Collections.<Account.Id> emptySet());
+ try {
+ CreateChangeSender cm = createChangeSenderFactory.create(updatedChange);
+ cm.setFrom(me);
+ cm.setPatchSet(updatedPatchSet, info);
+ cm.addReviewers(recipients.getReviewers());
+ cm.addExtraCC(recipients.getCcOnly());
+ cm.send();
+ } catch (Exception e) {
+ log.error("Cannot send email for new change " + updatedChange.getId(), e);
+ }
+ } else {
+ final List<PatchSetApproval> patchSetApprovals =
+ db.patchSetApprovals().byChange(
+ updatedChange.getId()).toList();
+ final MailRecipients oldRecipients =
+ getRecipientsFromApprovals(patchSetApprovals);
+ approvalsUtil.addReviewers(db, labelTypes, updatedChange,
+ updatedPatchSet, info, recipients.getReviewers(),
+ oldRecipients.getAll());
+ final ChangeMessage msg =
+ new ChangeMessage(new ChangeMessage.Key(updatedChange.getId(),
+ ChangeUtil.messageUUID(db)), me,
+ updatedPatchSet.getCreatedOn(), updatedPatchSet.getId());
+ msg.setMessage("Uploaded patch set " + updatedPatchSet.getPatchSetId() + ".");
+ try {
+ ReplacePatchSetSender cm = replacePatchSetFactory.create(updatedChange);
+ cm.setFrom(me);
+ cm.setPatchSet(updatedPatchSet, info);
+ cm.setChangeMessage(msg);
+ cm.addReviewers(recipients.getReviewers());
+ cm.addExtraCC(recipients.getCcOnly());
+ cm.send();
+ } catch (Exception e) {
+ log.error("Cannot send email for new patch set " + updatedPatchSet.getId(), e);
+ }
+ }
+ } finally {
+ git.close();
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
index 667a510..decf90a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
@@ -14,8 +14,11 @@
package com.google.gerrit.server.project;
+import com.google.common.collect.Lists;
+import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.PermissionRange;
+import com.google.gerrit.common.data.RefConfigSection;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.data.SubmitTypeRecord;
import com.google.gerrit.reviewdb.client.Account;
@@ -242,9 +245,28 @@
&& getRefControl().canUpload(); // as long as you can upload too
}
- /** All available label types for this project. */
+ /** All available label types for this change. */
public LabelTypes getLabelTypes() {
- return getProjectControl().getLabelTypes();
+ String destBranch = getChange().getDest().get();
+ List<LabelType> all = getProjectControl().getLabelTypes().getLabelTypes();
+
+ List<LabelType> r = Lists.newArrayListWithCapacity(all.size());
+ for (LabelType l : all) {
+ List<String> refs = l.getRefPatterns();
+ if (refs == null) {
+ r.add(l);
+ } else {
+ for (String refPattern : refs) {
+ if (RefConfigSection.isValid(refPattern)
+ && match(destBranch, refPattern)) {
+ r.add(l);
+ break;
+ }
+ }
+ }
+ }
+
+ return new LabelTypes(r);
}
/** All value ranges of any allowed label permission. */
@@ -403,6 +425,11 @@
return resultsToSubmitRecord(evaluator.getSubmitRule(), results);
}
+ private boolean match(String destBranch, String refPattern) {
+ return RefPatternMatcher.getMatcher(refPattern).match(destBranch,
+ this.getRefControl().getCurrentUser().getUserName());
+ }
+
private List<SubmitRecord> cannotSubmitDraft(ReviewDb db, PatchSet patchSet,
ChangeData cd) {
try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
index 2978c89..a41c197 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
@@ -36,7 +36,7 @@
import java.io.IOException;
public class DeleteBranch implements RestModifyView<BranchResource, Input>{
- private static final Logger log = LoggerFactory.getLogger(CreateBranch.class);
+ private static final Logger log = LoggerFactory.getLogger(DeleteBranch.class);
static class Input {
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
index 0555fc7..5282b49 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
@@ -162,12 +162,15 @@
}
private ChangeDataSource source() {
+ int minCost = Integer.MAX_VALUE;
+ Predicate<ChangeData> s = null;
for (Predicate<ChangeData> p : getChildren()) {
- if (p instanceof ChangeDataSource) {
- return (ChangeDataSource) p;
+ if (p instanceof ChangeDataSource && p.getCost() < minCost) {
+ s = p;
+ minCost = p.getCost();
}
}
- return null;
+ return (ChangeDataSource) s;
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
index 76d873a..24186b8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
@@ -160,6 +160,7 @@
} else {
changes = changes.subList(0, imp.getLimit());
}
+ data.set(n, changes);
more.set(n, true);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
index 72c7138..bdce7f4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
@@ -33,6 +33,7 @@
import com.google.gerrit.server.query.QueryParseException;
import com.google.gson.Gson;
import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -229,7 +230,7 @@
final Predicate<ChangeData> visibleToMe = queryBuilder.is_visible();
int cnt = queries.size();
- // Begin all queries, possibly asynchronously.
+ // Parse and rewrite all queries.
List<Integer> limits = Lists.newArrayListWithCapacity(cnt);
List<ChangeDataSource> sources = Lists.newArrayListWithCapacity(cnt);
for (int i = 0; i < cnt; i++) {
@@ -251,9 +252,16 @@
sources.add(a);
}
+ // Run each query asynchronously, if supported.
+ List<ResultSet<ChangeData>> matches = Lists.newArrayListWithCapacity(cnt);
+ for (ChangeDataSource s : sources) {
+ matches.add(s.read());
+ }
+ sources = null;
+
List<List<ChangeData>> out = Lists.newArrayListWithCapacity(cnt);
for (int i = 0; i < cnt; i++) {
- List<ChangeData> results = Lists.newArrayList(sources.get(i).read());
+ List<ChangeData> results = matches.get(i).toList();
Collections.sort(results, sortkeyAfter != null ? cmpAfter : cmpBefore);
if (results.size() > maxLimit) {
moreResults = true;
diff --git a/gerrit-server/src/main/java/gerrit/PRED_get_legacy_label_types_1.java b/gerrit-server/src/main/java/gerrit/PRED_get_legacy_label_types_1.java
index 4b89544..698c11c 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_get_legacy_label_types_1.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_get_legacy_label_types_1.java
@@ -16,9 +16,7 @@
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelValue;
-import com.google.gerrit.rules.PrologEnvironment;
import com.google.gerrit.rules.StoredValues;
-import com.google.gerrit.server.project.ProjectState;
import com.googlecode.prolog_cafe.lang.IntegerTerm;
import com.googlecode.prolog_cafe.lang.ListTerm;
@@ -55,14 +53,8 @@
public Operation exec(Prolog engine) throws PrologException {
engine.setB0();
Term a1 = arg1.dereference();
-
- PrologEnvironment env = (PrologEnvironment) engine.control;
- ProjectState state = env.getArgs().getProjectCache()
- .get(StoredValues.CHANGE.get(engine).getDest().getParentKey());
- if (state == null) {
- return engine.fail();
- }
- List<LabelType> list = state.getLabelTypes().getLabelTypes();
+ List<LabelType> list =
+ StoredValues.CHANGE_CONTROL.get(engine).getLabelTypes().getLabelTypes();
Term head = Prolog.Nil;
for (int idx = list.size() - 1; 0 <= idx; idx--) {
head = new ListTerm(export(list.get(idx)), head);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/change/IncludedInResolverTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/change/IncludedInResolverTest.java
new file mode 100644
index 0000000..ae58819
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/change/IncludedInResolverTest.java
@@ -0,0 +1,205 @@
+// 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.change;
+
+import com.google.gerrit.common.data.IncludedInDetail;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class IncludedInResolverTest extends RepositoryTestCase {
+
+ // Branch names
+ private static final String BRANCH_MASTER = "master";
+ private static final String BRANCH_1_0 = "rel-1.0";
+ private static final String BRANCH_1_3 = "rel-1.3";
+ private static final String BRANCH_2_0 = "rel-2.0";
+ private static final String BRANCH_2_5 = "rel-2.5";
+
+ // Tag names
+ private static final String TAG_1_0 = "1.0";
+ private static final String TAG_1_0_1 = "1.0.1";
+ private static final String TAG_1_3 = "1.3";
+ private static final String TAG_2_0_1 = "2.0.1";
+ private static final String TAG_2_0 = "2.0";
+ private static final String TAG_2_5 = "2.5";
+ private static final String TAG_2_5_ANNOTATED = "2.5-annotated";
+ private static final String TAG_2_5_ANNOTATED_TWICE = "2.5-annotated_twice";
+
+ // Commits
+ private RevCommit commit_initial;
+ private RevCommit commit_v1_3;
+ private RevCommit commit_v2_5;
+
+ private List<String> expTags = new ArrayList<String>();
+ private List<String> expBranches = new ArrayList<String>();
+
+ private RevWalk revWalk;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ /*- The following graph will be created.
+
+ o tag 2.5, 2.5_annotated, 2.5_annotated_twice
+ |\
+ | o tag 2.0.1
+ | o tag 2.0
+ o | tag 1.3
+ |/
+ o c3
+
+ | o tag 1.0.1
+ |/
+ o tag 1.0
+ o c2
+ o c1
+
+ */
+
+ Git git = new Git(db);
+ revWalk = new RevWalk(db);
+ // Version 1.0
+ commit_initial = git.commit().setMessage("c1").call();
+ git.commit().setMessage("c2").call();
+ RevCommit commit_v1_0 = git.commit().setMessage("version 1.0").call();
+ git.tag().setName(TAG_1_0).setObjectId(commit_v1_0).call();
+ RevCommit c3 = git.commit().setMessage("c3").call();
+ // Version 1.01
+ createAndCheckoutBranch(commit_v1_0, BRANCH_1_0);
+ RevCommit commit_v1_0_1 =
+ git.commit().setMessage("verREFS_HEADS_RELsion 1.0.1").call();
+ git.tag().setName(TAG_1_0_1).setObjectId(commit_v1_0_1).call();
+ // Version 1.3
+ createAndCheckoutBranch(c3, BRANCH_1_3);
+ commit_v1_3 = git.commit().setMessage("version 1.3").call();
+ git.tag().setName(TAG_1_3).setObjectId(commit_v1_3).call();
+ // Version 2.0
+ createAndCheckoutBranch(c3, BRANCH_2_0);
+ RevCommit commit_v2_0 = git.commit().setMessage("version 2.0").call();
+ git.tag().setName(TAG_2_0).setObjectId(commit_v2_0).call();
+ RevCommit commit_v2_0_1 = git.commit().setMessage("version 2.0.1").call();
+ git.tag().setName(TAG_2_0_1).setObjectId(commit_v2_0_1).call();
+
+ // Version 2.5
+ createAndCheckoutBranch(commit_v1_3, BRANCH_2_5);
+ git.merge().include(commit_v2_0_1).setCommit(false)
+ .setFastForward(FastForwardMode.NO_FF).call();
+ commit_v2_5 = git.commit().setMessage("version 2.5").call();
+ git.tag().setName(TAG_2_5).setObjectId(commit_v2_5).setAnnotated(false)
+ .call();
+ Ref ref_tag_2_5_annotated =
+ git.tag().setName(TAG_2_5_ANNOTATED).setObjectId(commit_v2_5)
+ .setAnnotated(true).call();
+ RevTag tag_2_5_annotated =
+ revWalk.parseTag(ref_tag_2_5_annotated.getObjectId());
+ git.tag().setName(TAG_2_5_ANNOTATED_TWICE).setObjectId(tag_2_5_annotated)
+ .setAnnotated(true).call();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ revWalk.release();
+ super.tearDown();
+ }
+
+ @Test
+ public void resolveLatestCommit() throws Exception {
+ // Check tip commit
+ IncludedInDetail detail = resolve(commit_v2_5);
+
+ // Check that only tags and branches which refer the tip are returned
+ expTags.add(TAG_2_5);
+ expTags.add(TAG_2_5_ANNOTATED);
+ expTags.add(TAG_2_5_ANNOTATED_TWICE);
+ assertEquals(expTags, detail.getTags());
+ expBranches.add(BRANCH_2_5);
+ assertEquals(expBranches, detail.getBranches());
+ }
+
+ @Test
+ public void resolveFirstCommit() throws Exception {
+ // Check first commit
+ IncludedInDetail detail = resolve(commit_initial);
+
+ // Check whether all tags and branches are returned
+ expTags.add(TAG_1_0);
+ expTags.add(TAG_1_0_1);
+ expTags.add(TAG_1_3);
+ expTags.add(TAG_2_0);
+ expTags.add(TAG_2_0_1);
+ expTags.add(TAG_2_5);
+ expTags.add(TAG_2_5_ANNOTATED);
+ expTags.add(TAG_2_5_ANNOTATED_TWICE);
+ assertEquals(expTags, detail.getTags());
+
+ expBranches.add(BRANCH_MASTER);
+ expBranches.add(BRANCH_1_0);
+ expBranches.add(BRANCH_1_3);
+ expBranches.add(BRANCH_2_0);
+ expBranches.add(BRANCH_2_5);
+ assertEquals(expBranches, detail.getBranches());
+ }
+
+ @Test
+ public void resolveBetwixtCommit() throws Exception {
+ // Check a commit somewhere in the middle
+ IncludedInDetail detail = resolve(commit_v1_3);
+
+ // Check whether all succeeding tags and branches are returned
+ expTags.add(TAG_1_3);
+ expTags.add(TAG_2_5);
+ expTags.add(TAG_2_5_ANNOTATED);
+ expTags.add(TAG_2_5_ANNOTATED_TWICE);
+ assertEquals(expTags, detail.getTags());
+
+ expBranches.add(BRANCH_1_3);
+ expBranches.add(BRANCH_2_5);
+ assertEquals(expBranches, detail.getBranches());
+ }
+
+ private IncludedInDetail resolve(RevCommit commit) throws Exception {
+ return IncludedInResolver.resolve(db, revWalk, commit);
+ }
+
+ private void assertEquals(List<String> list1, List<String> list2) {
+ Collections.sort(list1);
+ Collections.sort(list2);
+ Assert.assertEquals(list1, list2);
+ }
+
+ private void createAndCheckoutBranch(ObjectId objectId, String branchName)
+ throws IOException {
+ String fullBranchName = "refs/heads/" + branchName;
+ super.createBranch(objectId, fullBranchName);
+ super.checkoutBranch(fullBranchName);
+ }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommandProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommandProvider.java
index c7594bc..ce1da95 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommandProvider.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommandProvider.java
@@ -60,10 +60,7 @@
return new RegistrationHandle() {
@Override
public void remove() {
- if (!m.remove(name.value(), commandProvider)) {
- throw new IllegalStateException(String.format(
- "can not unregister command: %s", name.value()));
- }
+ m.remove(name.value(), commandProvider);
}
};
}
@@ -71,11 +68,12 @@
public RegistrationHandle replace(final CommandName name,
final Provider<Command> cmd) {
final ConcurrentMap<String, CommandProvider> m = getMap();
- m.put(name.value(), new CommandProvider(cmd, null));
+ final CommandProvider commandProvider = new CommandProvider(cmd, null);
+ m.put(name.value(), commandProvider);
return new RegistrationHandle() {
@Override
public void remove() {
- m.remove(name.value(), cmd);
+ m.remove(name.value(), commandProvider);
}
};
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index 714a6ad..7af882e 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -31,11 +31,11 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.change.Abandon;
import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.change.DeleteDraftPatchSet;
import com.google.gerrit.server.change.PostReview;
import com.google.gerrit.server.change.Restore;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.change.Submit;
-import com.google.gerrit.server.changedetail.DeleteDraftPatchSet;
import com.google.gerrit.server.changedetail.PublishDraft;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.project.ChangeControl;
@@ -131,7 +131,7 @@
private ReviewDb db;
@Inject
- private DeleteDraftPatchSet.Factory deleteDraftPatchSetFactory;
+ private DeleteDraftPatchSet deleteDraftPatchSetImpl;
@Inject
private ProjectControl.Factory projectControlFactory;
@@ -285,6 +285,16 @@
new ChangeResource(ctl), patchSet),
input);
}
+
+ if (publishPatchSet) {
+ final ReviewResult result =
+ publishDraftFactory.create(patchSet.getId()).call();
+ handleReviewResultErrors(result);
+ } else if (deleteDraftPatchSet) {
+ deleteDraftPatchSetImpl.apply(new RevisionResource(
+ new ChangeResource(ctl), patchSet),
+ new DeleteDraftPatchSet.Input());
+ }
} catch (InvalidChangeOperationException e) {
throw error(e.getMessage());
} catch (IllegalStateException e) {
@@ -296,16 +306,6 @@
} catch (ResourceConflictException e) {
throw error(e.getMessage());
}
-
- if (publishPatchSet) {
- final ReviewResult result =
- publishDraftFactory.create(patchSet.getId()).call();
- handleReviewResultErrors(result);
- } else if (deleteDraftPatchSet) {
- final ReviewResult result =
- deleteDraftPatchSetFactory.create(patchSet.getId()).call();
- handleReviewResultErrors(result);
- }
}
private void handleReviewResultErrors(final ReviewResult result) {
diff --git a/gerrit-war/BUCK b/gerrit-war/BUCK
index 03461b8..fa9a88f 100644
--- a/gerrit-war/BUCK
+++ b/gerrit-war/BUCK
@@ -9,11 +9,13 @@
'//gerrit-httpd:httpd',
'//gerrit-lucene:lucene',
'//gerrit-openid:openid',
+ '//gerrit-pgm:init-base',
'//gerrit-reviewdb:server',
'//gerrit-server:server',
'//gerrit-server/src/main/prolog:common',
'//gerrit-solr:solr',
'//gerrit-sshd:sshd',
+ '//lib:guava',
'//lib:gwtorm',
'//lib/guice:guice',
'//lib/guice:guice-servlet',
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java
new file mode 100644
index 0000000..0948c46
--- /dev/null
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java
@@ -0,0 +1,87 @@
+// 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.httpd;
+
+import com.google.gerrit.pgm.BaseInit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public final class SiteInitializer {
+ private static final Logger log = LoggerFactory
+ .getLogger(SiteInitializer.class);
+
+ final String sitePath;
+ final String initPath;
+
+ SiteInitializer(String sitePath, String initPath) {
+ this.sitePath = sitePath;
+ this.initPath = initPath;
+ }
+
+ public void init() {
+ try {
+
+ if (sitePath != null) {
+ File site = new File(sitePath);
+ log.info(String.format("Initializing site at %s",
+ site.getAbsolutePath()));
+ new BaseInit(site, false).run();
+ return;
+ }
+
+ Connection conn = connectToDb();
+ try {
+ File site = getSiteFromReviewDb(conn);
+ if (site == null && initPath != null) {
+ site = new File(initPath);
+ }
+
+ if (site != null) {
+ log.info(String.format("Initializing site at %s",
+ site.getAbsolutePath()));
+ new BaseInit(site, new ReviewDbDataSourceProvider(), false).run();
+ }
+ } finally {
+ conn.close();
+ }
+ } catch (Exception e) {
+ log.error("Site init failed", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ private Connection connectToDb() throws SQLException {
+ return new ReviewDbDataSourceProvider().get().getConnection();
+ }
+
+ private File getSiteFromReviewDb(Connection conn) {
+ try {
+ ResultSet rs = conn.createStatement().executeQuery(
+ "select site_path from system_config");
+ if (rs.next()) {
+ return new File(rs.getString(1));
+ }
+ return null;
+ } catch (SQLException e) {
+ return null;
+ }
+ }
+}
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 3c70cf6..bccd0de 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
@@ -115,6 +115,10 @@
sitePath = new File(path);
}
+ if (System.getProperty("gerrit.init") != null) {
+ new SiteInitializer(path, System.getProperty("gerrit.init_path")).init();
+ }
+
try {
dbInjector = createDbInjector();
} catch (CreationException ce) {
diff --git a/plugins/commit-message-length-validator b/plugins/commit-message-length-validator
index 9f988c6..45f347d 160000
--- a/plugins/commit-message-length-validator
+++ b/plugins/commit-message-length-validator
@@ -1 +1 @@
-Subproject commit 9f988c6e5b1e07f66479f0d4d08537281fa4694f
+Subproject commit 45f347d0e258ef7b871b046bfa96b9f902063b10
diff --git a/plugins/replication b/plugins/replication
index 3fffd16..0c5fef2 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 3fffd16403c1d3a4d6f0b13e9f33f4f78df1f6b4
+Subproject commit 0c5fef2a8eda0e76677e5487d2fb8391e1cb126e
diff --git a/plugins/reviewnotes b/plugins/reviewnotes
index 1fac69b..d470050 160000
--- a/plugins/reviewnotes
+++ b/plugins/reviewnotes
@@ -1 +1 @@
-Subproject commit 1fac69b085cf67706a3a124df366d55e1976cd21
+Subproject commit d47005095fcf3bc918a1dda62d21988cc6e470e6
diff --git a/tools/util/query_tester.sh b/tools/util/query_tester.sh
new file mode 100755
index 0000000..25646c2
--- /dev/null
+++ b/tools/util/query_tester.sh
@@ -0,0 +1,81 @@
+#!/bin/bash
+
+usage() {
+ cat <<EOF
+ query_suite run -h <host> -i <suite_dir> -o <output_dir> [-s <suite>]
+
+ Run query test suites
+
+ A query test suite must be a subdirectory of suite_dir and contain
+ either files with queries in them, or if the suite name looks like
+ "values_operator", the files must contain values for the operator
+ instead of full queries.
+
+ The query outputs for each suite will be stored in files named
+ after their suites and input file names under the output_dir.
+
+EOF
+ exit
+}
+
+e() { [ -n "$VERBOSE" ] && echo "$@" >&2 ; "$@" ; }
+
+query() { # host query
+ local host=$1 query="$2"
+ e ssh -p 29418 "$host" gerrit query --format json "$query"
+}
+
+run_suite() { # host idir odir suite operator
+ local host=$1 idir=$2 odir=$3 suite=$4 operator=$5 qfile files
+
+ mkdir -p "$odir/$suite"
+
+ echo "$suite" | grep -q "^values_" && operator=$(echo "$suite" | sed '-es/^values_//')
+
+ if [ -z "$suite" ] ; then
+ files=($(cd "$idir" ; echo *))
+ else
+ files=($(cd "$idir" ; echo $suite/*))
+ fi
+
+ for qfile in "${files[@]}" ; do
+ if [ -d "$idir/$qfile" ] ; then
+ run_suite "$host" "$idir" "$odir" "$qfile" "$operator"
+ else
+ if [ -n "$operator" ] ; then
+ query "$host" "$operator:{$(< "$idir/$qfile")}" > "$odir/$qfile"
+ else
+ query "$host" "$(< "$idir/$qfile")" > "$odir/$qfile"
+ fi
+ fi
+ done
+}
+
+cmd=''
+while [ $# -gt 0 ] ; do
+ case "$1" in
+ -v) VERBOSE=$1 ;;
+
+ -h) shift ; HOST=$1 ;;
+ -s) shift ; SUITE=$1 ;;
+ -i) shift ; IDIR=$1 ;;
+ -o) shift ; ODIR=$1 ;;
+
+ run) cmd=$1 ;;
+
+ --exec) shift ; "$@" ; exit ;;
+
+ *) usage ;;
+ esac
+ shift
+done
+
+case "$cmd" in
+ run)
+ [ -z "$HOST" ] && usage
+ [ -z "$IDIR" ] && usage
+ [ -z "$ODIR" ] && usage
+ [ -z "$SUITE" ] && SUITE=""
+ run_suite "$HOST" "$IDIR" "$ODIR" "$SUITE" ; exit
+ ;;
+esac