Merge "Run most acceptance tests in-memory"
diff --git a/.buckversion b/.buckversion
index 7876c60..62fc368 100644
--- a/.buckversion
+++ b/.buckversion
@@ -1 +1 @@
-182669c0decd69f7fcd16add278eeb816bfc9890
+3fd3ea153c5444df2376de8ee87594a52a45bf52
diff --git a/Documentation/config-gitweb.txt b/Documentation/config-gitweb.txt
index 7ba15b8..711174f 100644
--- a/Documentation/config-gitweb.txt
+++ b/Documentation/config-gitweb.txt
@@ -245,6 +245,10 @@
 After updating `'$site_path'/etc/gerrit.config`, the Gerrit server must
 be restarted and clients must reload the host page to see the change.
 
+Note that when using a custom gitweb configuration, values must be
+specified for all of the `project`, `revision`, `branch` and `filehistory`
+settings, otherwise the configuration will not be used.
+
 Access Control
 ++++++++++++++
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
index 9960a29..b8963e2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
@@ -133,9 +133,9 @@
     return fileCommentRow.getOffsetHeight() + patchSetSelectBoxA.getOffsetHeight();
   }
 
-  void setUpPatchSetNav(JsArray<RevisionInfo> list) {
-    patchSetSelectBoxA.setUpPatchSetNav(list);
-    patchSetSelectBoxB.setUpPatchSetNav(list);
+  void setUpPatchSetNav(JsArray<RevisionInfo> list, DiffInfo info) {
+    patchSetSelectBoxA.setUpPatchSetNav(list, info.meta_a());
+    patchSetSelectBoxB.setUpPatchSetNav(list, info.meta_b());
   }
 
   void add(Widget widget) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox2.java
index d258735..4a09615 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox2.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.client.patches.PatchUtil;
 import com.google.gerrit.client.ui.InlineHyperlink;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JsArray;
@@ -28,14 +29,14 @@
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Anchor;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.HTMLPanel;
 import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.ImageResourceRenderer;
+import com.google.gwtorm.client.KeyUtil;
 
-/**
- * HTMLPanel to select among patch sets
- * TODO: Implement download link.
- */
+/** HTMLPanel to select among patch sets */
 class PatchSetSelectBox2 extends Composite {
   interface Binder extends UiBinder<HTMLPanel, PatchSetSelectBox2> {}
   private static final Binder uiBinder = GWT.create(Binder.class);
@@ -71,7 +72,7 @@
     this.path = path;
   }
 
-  void setUpPatchSetNav(JsArray<RevisionInfo> list) {
+  void setUpPatchSetNav(JsArray<RevisionInfo> list, DiffInfo.FileMeta meta) {
     InlineHyperlink baseLink = null;
     InlineHyperlink selectedLink = null;
     if (sideA) {
@@ -92,6 +93,9 @@
     } else if (sideA) {
       baseLink.setStyleName(style.selected());
     }
+    if (meta != null && !Patch.COMMIT_MSG.equals(path)) {
+      linkPanel.add(createDownloadLink());
+    }
   }
 
   static void link(PatchSetSelectBox2 a, PatchSetSelectBox2 b) {
@@ -110,6 +114,17 @@
         path));
   }
 
+  private Anchor createDownloadLink() {
+    PatchSet.Id id = (idActive == null) ? other.idActive : idActive;
+    String sideURL = (idActive == null) ? "1" : "0";
+    String base = GWT.getHostPageBaseURL() + "cat/";
+    Anchor anchor = new Anchor(
+        new ImageResourceRenderer().render(Gerrit.RESOURCES.downloadIcon()),
+        base + KeyUtil.encode(id + "," + path) + "^" + sideURL);
+    anchor.setTitle(PatchUtil.C.download());
+    return anchor;
+  }
+
   @UiHandler("icon")
   void onIconClick(ClickEvent e) {
     table.createOrEditFileComment(side);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java
index 7caf76d..09b4b49 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java
@@ -204,6 +204,18 @@
       CommentApi.drafts(revision, group.add(getCommentCallback(DisplaySide.B, true)));
     }
 
+    RestApi call = ChangeApi.detail(changeId.get());
+    ChangeList.addOptions(call, EnumSet.of(
+        ListChangesOption.ALL_REVISIONS));
+    call.get(group.add(new GerritCallback<ChangeInfo>() {
+      @Override
+      public void onSuccess(ChangeInfo info) {
+        info.revisions().copyKeysIntoChildren("name");
+        JsArray<RevisionInfo> list = info.revisions().values();
+        RevisionInfo.sortRevisionInfoByNumber(list);
+        diffTable.setUpPatchSetNav(list, diff);
+      }}));
+
     ConfigInfoCache.get(changeId, group.addFinal(
         new ScreenLoadCallback<ConfigInfoCache.Entry>(SideBySide2.this) {
           @Override
@@ -216,18 +228,6 @@
             display(diffInfo);
           }
         }));
-
-    RestApi call = ChangeApi.detail(changeId.get());
-    ChangeList.addOptions(call, EnumSet.of(
-        ListChangesOption.ALL_REVISIONS));
-    call.get(new GerritCallback<ChangeInfo>() {
-      @Override
-      public void onSuccess(ChangeInfo info) {
-        info.revisions().copyKeysIntoChildren("name");
-        JsArray<RevisionInfo> list = info.revisions().values();
-        RevisionInfo.sortRevisionInfoByNumber(list);
-        diffTable.setUpPatchSetNav(list);
-      }});
   }
 
   @Override
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
index 28e361c..2dde16e 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
@@ -168,7 +168,7 @@
     Element userlistElement = HtmlDomUtil.find(doc, "userlist");
     ReviewDb db = schema.open();
     try {
-      ResultSet<Account> accounts = db.accounts().firstNById(5);
+      ResultSet<Account> accounts = db.accounts().firstNById(100);
       for (Account a : accounts) {
         String displayName;
         if (a.getUserName() != null) {
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
index 4f77b0f..bd22146 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java
@@ -75,7 +75,9 @@
   @Override
   public int run() throws Exception {
     final SiteInit init = createSiteInit();
-    beforeInit(init);
+    if (beforeInit(init)) {
+      return 0;
+    }
 
     init.flags.autoStart = getAutoStart() && init.site.isNew;
     init.flags.skipPlugins = skipPlugins();
@@ -108,7 +110,8 @@
     return false;
   }
 
-  protected void beforeInit(SiteInit init) throws Exception {
+  protected boolean beforeInit(SiteInit init) throws Exception {
+    return false;
   }
 
   protected void afterInit(SiteRun run) throws Exception {
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 cbf50b9..68e0f6a 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
@@ -76,11 +76,12 @@
   }
 
   @Override
-  protected void beforeInit(SiteInit init) throws Exception {
+  protected boolean beforeInit(SiteInit init) throws Exception {
     ErrorLogFile.errorOnlyConsole();
 
     if (!skipPlugins) {
-      final List<PluginData> plugins = InitPlugins.listPlugins(init.site);
+      final List<PluginData> plugins =
+          InitPlugins.listPluginsAndRemoveTempFiles(init.site);
       ConsoleUI ui = ConsoleUI.getInstance(false);
       verifyInstallPluginList(ui, plugins);
       if (listPlugins) {
@@ -92,8 +93,10 @@
         } else {
           ui.message("No plugins found.\n");
         }
+        return true;
       }
     }
+    return false;
   }
 
   @Override
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java
index ee0d799..9c8305a 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java
@@ -51,6 +51,14 @@
   }
 
   public static List<PluginData> listPlugins(SitePaths site) throws IOException {
+    return listPlugins(site, false);
+  }
+
+  public static List<PluginData> listPluginsAndRemoveTempFiles(SitePaths site) throws IOException {
+    return listPlugins(site, true);
+  }
+
+  private static List<PluginData> listPlugins(SitePaths site, boolean deleteTempPluginFile) throws IOException {
     final File myWar = GerritLauncher.getDistributionArchive();
     final List<PluginData> result = Lists.newArrayList();
     try {
@@ -69,6 +77,9 @@
             final InputStream in = zf.getInputStream(ze);
             final File tmpPlugin = PluginLoader.storeInTemp(pluginName, in, site);
             final String pluginVersion = getVersion(tmpPlugin);
+            if (deleteTempPluginFile) {
+              tmpPlugin.delete();
+            }
 
             result.add(new PluginData(pluginName, pluginVersion, tmpPlugin));
           }
diff --git a/lib/gwt/compiler.py b/lib/gwt/compiler.py
index 29a8609..32fd91c 100755
--- a/lib/gwt/compiler.py
+++ b/lib/gwt/compiler.py
@@ -16,14 +16,14 @@
 from __future__ import print_function
 
 from multiprocessing import cpu_count
-from os import environ, makedirs, mkdir, path
+from os import makedirs, mkdir, path
 from subprocess import Popen, PIPE
 from sys import argv, stderr
 
-cp, opt, end, TMP = [], [], False, environ['TMP']
-module, outzip = argv[1], argv[2]
+cp, opt, end = [], [], False
+module, TMP, outzip = argv[1], argv[2], argv[3]
 
-for a in argv[3:]:
+for a in argv[4:]:
   if end:
     if a.endswith('.jar'):
       cp.append(path.expandvars(a))
diff --git a/tools/build.defs b/tools/build.defs
index 053d529..b62c850 100644
--- a/tools/build.defs
+++ b/tools/build.defs
@@ -38,7 +38,7 @@
     context = [],
     visibility = []
     ):
-  cmd = ['$(exe //tools:pack_war)', '-o', '$OUT']
+  cmd = ['$(exe //tools:pack_war)', '-o', '$OUT', '--tmp', '$TMP']
   for l in libs:
     cmd.extend(['--lib', l])
   for l in pgmlibs:
diff --git a/tools/default.defs b/tools/default.defs
index e7981dd..4ed5635 100644
--- a/tools/default.defs
+++ b/tools/default.defs
@@ -54,7 +54,7 @@
     compiler_jvm_flags = [],
     deps = [],
     visibility = []):
-  cmd = ['$(exe //lib/gwt:compiler)', module_target, '$OUT']
+  cmd = ['$(exe //lib/gwt:compiler)', module_target, '$TMP', '$OUT']
   cmd += compiler_opts + ['--', '$DEPS']
   genrule(
     name = name,
diff --git a/tools/eclipse/project.py b/tools/eclipse/project.py
index e20798e..4707de5 100755
--- a/tools/eclipse/project.py
+++ b/tools/eclipse/project.py
@@ -21,6 +21,7 @@
 from subprocess import Popen, PIPE, CalledProcessError, check_call
 from xml.dom import minidom
 import re
+import sys
 
 MAIN = ['//tools/eclipse:classpath']
 GWT = ['//gerrit-gwtui:ui_module']
@@ -141,17 +142,21 @@
   with open(p, 'w') as fd:
     doc.writexml(fd, addindent='  ', newl='\n', encoding='UTF-8')
 
-if args.src:
+try:
+  if args.src:
+    try:
+      check_call([path.join(ROOT, 'tools', 'download_all.py'), '--src'])
+    except CalledProcessError as err:
+      exit(1)
+
+  gen_project()
+  gen_classpath()
+
   try:
-    check_call([path.join(ROOT, 'tools', 'download_all.py'), '--src'])
+    targets = ['//tools:buck.properties'] + MAIN + GWT
+    check_call(['buck', 'build'] + targets)
   except CalledProcessError as err:
     exit(1)
-
-gen_project()
-gen_classpath()
-
-try:
-  targets = ['//tools:buck.properties'] + MAIN + GWT
-  check_call(['buck', 'build'] + targets)
-except CalledProcessError as err:
+except KeyboardInterrupt:
+  print('Interrupted by user', file=sys.stderr)
   exit(1)
diff --git a/tools/pack_war.py b/tools/pack_war.py
index 4c5cd89..6c71d81 100755
--- a/tools/pack_war.py
+++ b/tools/pack_war.py
@@ -13,18 +13,21 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from __future__ import print_function
 from optparse import OptionParser
-from os import environ, makedirs, path, symlink
+from os import makedirs, path, symlink
 from subprocess import check_call
+import sys
 from util import check_output
 
 opts = OptionParser()
 opts.add_option('-o', help='path to write WAR to')
 opts.add_option('--lib', action='append', help='target for WEB-INF/lib')
 opts.add_option('--pgmlib', action='append', help='target for WEB-INF/pgm-lib')
+opts.add_option('--tmp', help='temporary directory')
 args, ctx = opts.parse_args()
 
-war = environ['TMP']
+war = args.tmp
 root = war[:war.index('buck-out')]
 jars = set()
 
@@ -43,6 +46,10 @@
   link_jars(args.lib, path.join(war, 'WEB-INF', 'lib'))
 if args.pgmlib:
   link_jars(args.pgmlib, path.join(war, 'WEB-INF', 'pgm-lib'))
-for s in ctx:
-  check_call(['unzip', '-q', '-d', war, s])
-check_call(['zip', '-9qr', args.o, '.'], cwd = war)
+try:
+  for s in ctx:
+    check_call(['unzip', '-q', '-d', war, s])
+  check_call(['zip', '-9qr', args.o, '.'], cwd = war)
+except KeyboardInterrupt:
+  print('Interrupted by user', file=sys.stderr)
+  exit(1)