Merge "Add SSH to configure logging level at runtime"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index ed44db8..7ebbbbe 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1750,6 +1750,24 @@
 This property is honored only if the password does not
 appear in the http.proxy property above.
 
+[[http.addUserAsRequestAttribute]]http.addUserAsRequestAttribute::
++
+If true, 'User' attribute will be added to the request attributes so it
+can be accessed outside the request scope (will be set to username or id
+if username not configured).
++
+This attribute can be used by the servlet container to log user in the
+http access log.
++
+When running the embedded servlet container, this attribute is used to
+print user in the httpd_log.
++
+* `%{User}r`
++
+Pattern to print user in Tomcat AccessLog.
+
++
+Default value is true.
 
 [[httpd]]
 === Section httpd
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt
index 8121a63..0bf44d0 100644
--- a/Documentation/dev-buck.txt
+++ b/Documentation/dev-buck.txt
@@ -467,6 +467,15 @@
 To use `buckd` the additional
 link:https://facebook.github.io/watchman[watchman] program must be installed.
 
+To disable `buckd`, the environment variable `NO_BUCKD` must be set. It's not
+recommended to put it in the shell config, as it can be forgotten about it and
+then assumed Buck was working as it should when it should be using buckd.
+Prepend the variable to Buck invocation instead:
+
+----
+  $ NO_BUCKD=1 buck build gerrit
+----
+
 [[watchman]]
 === Installing watchman
 
diff --git a/Documentation/dev-eclipse.txt b/Documentation/dev-eclipse.txt
index 3063882..adc62b2 100644
--- a/Documentation/dev-eclipse.txt
+++ b/Documentation/dev-eclipse.txt
@@ -49,7 +49,7 @@
 
 === Running GWT Debug Mode
 
-The gerrit_gwt_debug launch configuration uses GWT's 
+The gerrit_gwt_debug launch configuration uses GWT's
 link:http://www.gwtproject.org/articles/superdevmode.html[Super Dev Mode].
 
 Due to a problem where the codeserver does not correctly identify the connected
@@ -58,7 +58,7 @@
 
 [source,xml]
 ----
-  <set-property name="user.agent" value="geko1_8" />
+  <set-property name="user.agent" value="gecko1_8" />
 ----
 
 or
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 71fc175..997f481 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -295,9 +295,9 @@
   authenticated and has commented on the current revision.
 --
 
-[[patch-set-links]]
+[[web-links]]
 --
-* `PATCHSET_LINKS`: include the `web_links` field.
+* `WEB_LINKS`: include the `web_links` field.
 --
 
 .Request
diff --git a/bucklets/gerrit_plugin.bucklet b/bucklets/gerrit_plugin.bucklet
index eb10456..f3e9830 100644
--- a/bucklets/gerrit_plugin.bucklet
+++ b/bucklets/gerrit_plugin.bucklet
@@ -13,3 +13,6 @@
 #
 # When compiling from standalone cookbook-plugin, bucklets directory points
 # to cloned bucklets library that includes real gerrit_plugin.bucklet code.
+
+GERRIT_PLUGIN_API = ['//gerrit-plugin-api:lib']
+GERRIT_GWT_API = ['//gerrit-plugin-gwtui/gerrit:gwtui-api']
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index 5b3795e..2134fda 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -24,17 +24,25 @@
 import com.google.gerrit.extensions.common.LabelInfo;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gerrit.testutil.ConfigSuite;
 import com.google.gwtorm.server.OrmException;
 
 import com.jcraft.jsch.JSchException;
 
 import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.lib.Config;
 import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
 
 public abstract class AbstractPushForReview extends AbstractDaemonTest {
+  @ConfigSuite.Config
+  public static Config noteDbEnabled() {
+    return NotesMigration.allEnabledConfig();
+  }
+
   protected enum Protocol {
     SSH, HTTP
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/actions/ActionButton.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/actions/ActionButton.java
index 6aaac8c..2e2d314 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/actions/ActionButton.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/actions/ActionButton.java
@@ -68,6 +68,7 @@
       .openDiv()
       .append(action.label())
       .closeDiv());
+    setStyleName("");
     setTitle(action.title());
     setEnabled(action.enabled());
     addClickHandler(this);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
index f3e99b5..a0b1fc73 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
@@ -584,6 +584,7 @@
 
   private Button createChangeAction() {
     final Button createChange = new Button(Util.C.buttonCreateChange());
+    createChange.setStyleName("");
     createChange.setTitle(Util.C.buttonCreateChangeDescription());
     createChange.addClickHandler(new ClickHandler() {
       @Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Hashtags.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Hashtags.java
index 0cc3ced..bc84984 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Hashtags.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Hashtags.java
@@ -162,9 +162,10 @@
           .setAttribute(DATA_ID, hashtagName)
           .setStyleName(style.hashtagName())
           .openAnchor()
-          .setAttribute("href", "#" + PageLinks.toChangeQuery("hashtag:" + hashtagName))
+          .setAttribute("href",
+              "#" + PageLinks.toChangeQuery("hashtag:\"" + hashtagName + "\""))
           .setAttribute("role", "listitem")
-          .append(hashtagName)
+          .append("#").append(hashtagName)
           .closeAnchor()
           .openElement("button")
           .setAttribute("title", "Remove hashtag")
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/GetUserFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GetUserFilter.java
similarity index 72%
rename from gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/GetUserFilter.java
rename to gerrit-httpd/src/main/java/com/google/gerrit/httpd/GetUserFilter.java
index 4f35f1c..94b8f29 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/GetUserFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GetUserFilter.java
@@ -12,9 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.pgm.http.jetty;
+package com.google.gerrit.httpd;
 
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -24,7 +25,6 @@
 import org.eclipse.jgit.lib.Config;
 
 import java.io.IOException;
-import java.net.URI;
 
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
@@ -34,28 +34,26 @@
 import javax.servlet.ServletResponse;
 
 /**
- * Stores as a request attribute, so the {@link HttpLog} can include the the
- * user for the request outside of the request scope.
+ * Stores user as a request attribute, so servlets can access it outside of the
+ * request scope.
  */
 @Singleton
 public class GetUserFilter implements Filter {
 
-  static final String REQ_ATTR_KEY = CurrentUser.class.toString();
+  public static final String REQ_ATTR_KEY = "User";
 
   public static class Module extends ServletModule {
 
-    private boolean loggingEnabled;
+    private final boolean enabled;
 
     @Inject
     Module(@GerritServerConfig final Config cfg) {
-      URI[] urls = JettyServer.listenURLs(cfg);
-      boolean reverseProxy = JettyServer.isReverseProxied(urls);
-      this.loggingEnabled = cfg.getBoolean("httpd", "requestLog", !reverseProxy);
+      enabled = cfg.getBoolean("http", "addUserAsRequestAttribute", true);
     }
 
     @Override
     protected void configureServlets() {
-      if (loggingEnabled) {
+      if (enabled) {
         filter("/*").through(GetUserFilter.class);
       }
     }
@@ -72,7 +70,15 @@
   public void doFilter(
       ServletRequest req, ServletResponse resp, FilterChain chain)
       throws IOException, ServletException {
-    req.setAttribute(REQ_ATTR_KEY, userProvider.get());
+    CurrentUser user = userProvider.get();
+    if (user != null && user.isIdentifiedUser()) {
+      IdentifiedUser who = (IdentifiedUser) user;
+      if (who.getUserName() != null && !who.getUserName().isEmpty()) {
+        req.setAttribute(REQ_ATTR_KEY, who.getUserName());
+      } else {
+        req.setAttribute(REQ_ATTR_KEY, "a/" + who.getAccountId());
+      }
+    }
     chain.doFilter(req, resp);
   }
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index 75f2e96..cc00294 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.common.ChangeHookRunner;
 import com.google.gerrit.httpd.AllRequestFilter;
 import com.google.gerrit.httpd.GerritOptions;
+import com.google.gerrit.httpd.GetUserFilter;
 import com.google.gerrit.httpd.GitOverHttpModule;
 import com.google.gerrit.httpd.H2CacheBasedWebSession;
 import com.google.gerrit.httpd.HttpCanonicalWebUrlProvider;
@@ -31,7 +32,6 @@
 import com.google.gerrit.httpd.plugins.HttpPluginModule;
 import com.google.gerrit.lifecycle.LifecycleManager;
 import com.google.gerrit.lucene.LuceneIndexModule;
-import com.google.gerrit.pgm.http.jetty.GetUserFilter;
 import com.google.gerrit.pgm.http.jetty.JettyEnv;
 import com.google.gerrit.pgm.http.jetty.JettyModule;
 import com.google.gerrit.pgm.http.jetty.ProjectQoSFilter;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java
index ba97d4a..f84abce 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java
@@ -14,8 +14,7 @@
 
 package com.google.gerrit.pgm.http.jetty;
 
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.httpd.GetUserFilter;
 import com.google.gerrit.server.util.SystemLog;
 import com.google.gerrit.server.util.TimeUtil;
 import com.google.inject.Inject;
@@ -66,11 +65,6 @@
 
   @Override
   public void log(final Request req, final Response rsp) {
-    CurrentUser user = (CurrentUser) req.getAttribute(GetUserFilter.REQ_ATTR_KEY);
-    doLog(req, rsp, user);
-  }
-
-  private void doLog(Request req, Response rsp, CurrentUser user) {
     final LoggingEvent event = new LoggingEvent( //
         Logger.class.getName(), // fqnOfCategoryClass
         log, // logger
@@ -90,13 +84,9 @@
       uri = uri + "?" + qs;
     }
 
-    if (user != null && user.isIdentifiedUser()) {
-      IdentifiedUser who = (IdentifiedUser) user;
-      if (who.getUserName() != null && !who.getUserName().isEmpty()) {
-        event.setProperty(P_USER, who.getUserName());
-      } else {
-        event.setProperty(P_USER, "a/" + who.getAccountId());
-      }
+    String user = (String) req.getAttribute(GetUserFilter.REQ_ATTR_KEY);
+    if (user != null) {
+      event.setProperty(P_USER, user);
     }
 
     set(event, P_HOST, req.getRemoteAddr());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/WebLinks.java b/gerrit-server/src/main/java/com/google/gerrit/server/WebLinks.java
index 629e75b..ae61fcf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/WebLinks.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/WebLinks.java
@@ -52,7 +52,7 @@
     return links;
   }
 
-  public Iterable<WebLinkInfo> getPatchLinks(String project, String revision,
+  public Iterable<WebLinkInfo> getFileLinks(String project, String revision,
       String file) {
     List<WebLinkInfo> links = Lists.newArrayList();
     for (FileWebLink webLink : fileLinks) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index c20cc71..0c72d35 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -59,7 +59,7 @@
   private final Restore restore;
   private final GetTopic getTopic;
   private final PutTopic putTopic;
-  private final Provider<PostReviewers> postReviewers;
+  private final PostReviewers postReviewers;
   private final Provider<ChangeJson> changeJson;
   private final PostHashtags postHashtags;
 
@@ -72,7 +72,7 @@
       Restore restore,
       GetTopic getTopic,
       PutTopic putTopic,
-      Provider<PostReviewers> postReviewers,
+      PostReviewers postReviewers,
       Provider<ChangeJson> changeJson,
       PostHashtags postHashtags,
       @Assisted ChangeResource change) {
@@ -183,7 +183,7 @@
   @Override
   public void addReviewer(AddReviewerInput in) throws RestApiException {
     try {
-      postReviewers.get().apply(change, in);
+      postReviewers.apply(change, in);
     } catch (OrmException | EmailException | IOException e) {
       throw new RestApiException("Cannot add change reviewer", e);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java
index bc38039..6330e34 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java
@@ -32,6 +32,7 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
+import com.google.inject.OutOfScopeException;
 import com.google.inject.Provider;
 import com.google.inject.ProvisionException;
 import com.google.inject.assistedinject.Assisted;
@@ -152,7 +153,7 @@
 
   @Override
   public CurrentUser getCurrentUser() {
-    return null;
+    throw new OutOfScopeException("No user on email thread");
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java
index f18481e..d3232cb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java
@@ -206,7 +206,7 @@
   private List<WebLinkInfo> getFileWebLinks(Project project, String rev,
       String file) {
     List<WebLinkInfo> fileWebLinks = new ArrayList<>();
-    for (WebLinkInfo link : webLinks.get().getPatchLinks(project.getName(),
+    for (WebLinkInfo link : webLinks.get().getFileLinks(project.getName(),
         rev, file)) {
       if (!Strings.isNullOrEmpty(link.name) && !Strings.isNullOrEmpty(link.url)) {
         fileWebLinks.add(link);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
index d74a314f..3767b32 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
@@ -27,6 +27,7 @@
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.change.PatchSetInserter;
 import com.google.gerrit.server.git.GitRepositoryManager;
@@ -63,14 +64,14 @@
   private final PatchSetInserter.Factory patchSetInserterFactory;
   private final ChangeControl.GenericFactory changeControlFactory;
   private final Provider<ReviewDb> db;
-  private final Provider<IdentifiedUser> user;
+  private final Provider<CurrentUser> user;
 
   @Inject
   ChangeEditUtil(GitRepositoryManager gitManager,
       PatchSetInserter.Factory patchSetInserterFactory,
       ChangeControl.GenericFactory changeControlFactory,
       Provider<ReviewDb> db,
-      Provider<IdentifiedUser> user) {
+      Provider<CurrentUser> user) {
     this.gitManager = gitManager;
     this.patchSetInserterFactory = patchSetInserterFactory;
     this.changeControlFactory = changeControlFactory;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index 2bfdaa7..9013ad9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -2045,6 +2045,7 @@
       recipients.remove(me);
 
       ChangeUpdate update = updateFactory.create(changeCtl, newPatchSet.getCreatedOn());
+      update.setPatchSetId(newPatchSet.getId());
       db.changes().beginTransaction(change.getId());
       try {
         change = db.changes().get(change.getId());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/MostSpecificComparator.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/MostSpecificComparator.java
index 804a7ec..1cb180b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/MostSpecificComparator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/MostSpecificComparator.java
@@ -28,7 +28,7 @@
  * name and the regex string shortest example. A shorter distance is a more
  * specific match.
  * <li>2 - Finites first, infinities after.
- * <li>3 - Number of transitions.
+ * <li>3 - Number of transitions.  More transitions is more specific.
  * <li>4 - Length of the expression text.
  * </ul>
  *
@@ -72,7 +72,7 @@
       }
     }
     if (cmp == 0) {
-      cmp = transitions(pattern1) - transitions(pattern2);
+      cmp = transitions(pattern2) - transitions(pattern1);
     }
     if (cmp == 0) {
       cmp = pattern2.length() - pattern1.length();
@@ -86,7 +86,7 @@
       example = RefControl.shortestExample(pattern);
 
     } else if (pattern.endsWith("/*")) {
-      example = pattern.substring(0, pattern.length() - 1) + '1';
+      example = pattern;
 
     } else if (pattern.equals(refName)) {
       return 0;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/util/MostSpecificComparatorTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/util/MostSpecificComparatorTest.java
new file mode 100644
index 0000000..e974f1f
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/util/MostSpecificComparatorTest.java
@@ -0,0 +1,80 @@
+// Copyright (C) 2014 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.util;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class MostSpecificComparatorTest {
+
+  private MostSpecificComparator cmp;
+
+  @Test
+  public void shorterDistanceWins() {
+    cmp = new MostSpecificComparator("refs/heads/master");
+    moreSpecificFirst("refs/heads/master", "refs/heads/master2");
+    moreSpecificFirst("refs/heads/master", "refs/heads/maste");
+    moreSpecificFirst("refs/heads/master", "refs/heads/*");
+    moreSpecificFirst("refs/heads/master", "^refs/heads/.*");
+    moreSpecificFirst("refs/heads/master", "^refs/heads/master.*");
+  }
+
+  /**
+   * Assuming two patterns have the same Levenshtein distance,
+   * the pattern which represents a finite language wins over a pattern
+   * which represents an infinite language.
+   */
+  @Test
+  public void finiteWinsOverInfinite() {
+    cmp = new MostSpecificComparator("refs/heads/master");
+    moreSpecificFirst("^refs/heads/......", "refs/heads/*");
+    moreSpecificFirst("^refs/heads/maste.", "^refs/heads/maste.*");
+  }
+
+  /**
+   * Assuming two patterns have the same Levenshtein distance
+   * and are both either finite or infinite the one with the higher
+   * number of state transitions (in an equivalent automaton) wins
+   */
+  @Test
+  public void higherNumberOfTransitionsWins() {
+    cmp = new MostSpecificComparator("refs/heads/x");
+    moreSpecificFirst("^refs/heads/[a-z].*", "refs/heads/*");
+    // Previously there was a bug where having a '1' in a refname would cause a
+    // glob pattern's Levenshtein distance to decrease by 1.  These two
+    // patterns should be a Levenshtein distance of 12 from the both of the
+    // refnames, where previously the 'branch1' refname would be a distance of
+    // 11 from 'refs/heads/abc/*'
+    cmp = new MostSpecificComparator("refs/heads/abc/spam/branch2");
+    moreSpecificFirst("^refs/heads/.*spam.*", "refs/heads/abc/*");
+    cmp = new MostSpecificComparator("refs/heads/abc/spam/branch1");
+    moreSpecificFirst("^refs/heads/.*spam.*", "refs/heads/abc/*");
+  }
+
+  /**
+   * Assuming the same Levenshtein distance, (in)finity and the number
+   * of transitions, the longer pattern wins
+   */
+  @Test
+  public void longerPatternWins() {
+    cmp = new MostSpecificComparator("refs/heads/x");
+    moreSpecificFirst("^refs/heads/[a-z].*", "^refs/heads/..*");
+  }
+
+  private void moreSpecificFirst(String first, String second) {
+    assertTrue(cmp.compare(first, second) < 0);
+  }
+}
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 99db2db..fbe2743 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
@@ -344,6 +344,7 @@
     if (authConfig.getAuthType() == AuthType.OPENID) {
       modules.add(new OpenIdModule());
     }
+    modules.add(sysInjector.getInstance(GetUserFilter.Module.class));
 
     return sysInjector.createChildInjector(modules);
   }
diff --git a/tools/eclipse/project.py b/tools/eclipse/project.py
index 133e8b4..294aad2 100755
--- a/tools/eclipse/project.py
+++ b/tools/eclipse/project.py
@@ -37,15 +37,17 @@
 
 opts = OptionParser()
 opts.add_option('--src', action='store_true')
+opts.add_option('--plugins', help='create eclipse projects for plugins',
+                action='store_true')
 args, _ = opts.parse_args()
 
-def gen_project():
-  p = path.join(ROOT, '.project')
+def gen_project(name='gerrit', dir=ROOT):
+  p = path.join(dir, '.project')
   with open(p, 'w') as fd:
     print("""\
 <?xml version="1.0" encoding="UTF-8"?>
 <projectDescription>
-  <name>gerrit</name>
+  <name>""" + name + """</name>
   <buildSpec>
     <buildCommand>
       <name>org.eclipse.jdt.core.javabuilder</name>
@@ -57,6 +59,18 @@
 </projectDescription>\
 """, file=fd)
 
+def gen_plugin_classpath(dir):
+  p = path.join(dir, '.classpath')
+  with open(p, 'w') as fd:
+    print("""\
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+    <classpathentry kind="src" path="src/main/java"/>
+    <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+    <classpathentry combineaccessrules="false" kind="src" path="/gerrit"/>
+    <classpathentry kind="output" path="buck-out/eclipse/classes"/>
+</classpath>""", file=fd)
+
 def gen_classpath():
   def query_classpath(targets):
     deps = []
@@ -72,7 +86,7 @@
     impl = minidom.getDOMImplementation()
     return impl.createDocument(None, 'classpath', None)
 
-  def classpathentry(kind, path, src=None, out=None):
+  def classpathentry(kind, path, src=None, out=None, exported=None):
     e = doc.createElement('classpathentry')
     e.setAttribute('kind', kind)
     e.setAttribute('path', path)
@@ -80,6 +94,8 @@
       e.setAttribute('sourcepath', src)
     if out:
       e.setAttribute('output', out)
+    if exported:
+      e.setAttribute('exported', 'true')
     doc.documentElement.appendChild(e)
 
   doc = make_classpath()
@@ -87,6 +103,7 @@
   lib = set()
   gwt_src = set()
   gwt_lib = set()
+  plugins = set()
 
   java_library = re.compile(r'[^/]+/gen/(.*)/lib__[^/]+__output/[^/]+[.]jar$')
   for p in query_classpath(MAIN):
@@ -119,6 +136,9 @@
     if s.startswith('lib/'):
       out = 'buck-out/eclipse/lib'
     elif s.startswith('plugins/'):
+      if args.plugins:
+        plugins.add(s)
+        continue
       out = 'buck-out/eclipse/' + s
 
     p = path.join(s, 'java')
@@ -145,8 +165,10 @@
         s = j[:-4] + '-src.jar'
         if not path.exists(s):
           s = None
-      classpathentry('lib', j, s)
-
+      if args.plugins:
+        classpathentry('lib', j, s, exported=True)
+      else:
+        classpathentry('lib', j, s)
   for s in sorted(gwt_src):
     p = path.join(ROOT, s, 'src', 'main', 'java')
     classpathentry('lib', p, out='buck-out/eclipse/gwtsrc')
@@ -158,6 +180,15 @@
   with open(p, 'w') as fd:
     doc.writexml(fd, addindent='\t', newl='\n', encoding='UTF-8')
 
+  if args.plugins:
+    for plugin in plugins:
+      plugindir = path.join(ROOT, plugin)
+      try:
+        gen_project(plugin.replace('plugins/', ""), plugindir)
+        gen_plugin_classpath(plugindir)
+      except (IOError, OSError) as err:
+        print('error generating project for %s: %s' % (plugin, err), file=sys.stderr)
+
 try:
   if args.src:
     try: