Merge changes from topic "git-wire-protocol-v2"

* changes:
  Support tracing on clone, fetch and ls-refs
  GitProtocolV2IT: Fix git version check for Google environment
  Add integration test for git protocol version 2
diff --git a/java/com/google/gerrit/acceptance/ExtensionRegistry.java b/java/com/google/gerrit/acceptance/ExtensionRegistry.java
index fa047a8..32c0fd1 100644
--- a/java/com/google/gerrit/acceptance/ExtensionRegistry.java
+++ b/java/com/google/gerrit/acceptance/ExtensionRegistry.java
@@ -22,17 +22,22 @@
 import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
 import com.google.gerrit.extensions.events.GroupIndexedListener;
 import com.google.gerrit.extensions.events.ProjectIndexedListener;
+import com.google.gerrit.extensions.events.RevisionCreatedListener;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.registration.PrivateInternals_DynamicMapImpl;
 import com.google.gerrit.extensions.registration.RegistrationHandle;
+import com.google.gerrit.extensions.webui.FileHistoryWebLink;
+import com.google.gerrit.extensions.webui.PatchSetWebLink;
 import com.google.gerrit.server.ExceptionHook;
+import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.change.ChangeETagComputation;
 import com.google.gerrit.server.git.ChangeMessageModifier;
 import com.google.gerrit.server.git.validators.CommitValidationListener;
 import com.google.gerrit.server.git.validators.RefOperationValidationListener;
 import com.google.gerrit.server.logging.PerformanceLogger;
 import com.google.gerrit.server.rules.SubmitRule;
+import com.google.gerrit.server.validators.AccountActivationValidationListener;
 import com.google.gerrit.server.validators.ProjectCreationValidationListener;
 import com.google.inject.Inject;
 import com.google.inject.util.Providers;
@@ -56,6 +61,12 @@
   private final DynamicSet<RefOperationValidationListener> refOperationValidationListeners;
   private final DynamicSet<CommentAddedListener> commentAddedListeners;
   private final DynamicSet<GitReferenceUpdatedListener> refUpdatedListeners;
+  private final DynamicSet<FileHistoryWebLink> fileHistoryWebLinks;
+  private final DynamicSet<PatchSetWebLink> patchSetWebLinks;
+  private final DynamicSet<RevisionCreatedListener> revisionCreatedListeners;
+  private final DynamicSet<GroupBackend> groupBackends;
+  private final DynamicSet<AccountActivationValidationListener>
+      accountActivationValidationListeners;
 
   @Inject
   ExtensionRegistry(
@@ -74,7 +85,12 @@
       DynamicMap<DownloadScheme> downloadSchemes,
       DynamicSet<RefOperationValidationListener> refOperationValidationListeners,
       DynamicSet<CommentAddedListener> commentAddedListeners,
-      DynamicSet<GitReferenceUpdatedListener> refUpdatedListeners) {
+      DynamicSet<GitReferenceUpdatedListener> refUpdatedListeners,
+      DynamicSet<FileHistoryWebLink> fileHistoryWebLinks,
+      DynamicSet<PatchSetWebLink> patchSetWebLinks,
+      DynamicSet<RevisionCreatedListener> revisionCreatedListeners,
+      DynamicSet<GroupBackend> groupBackends,
+      DynamicSet<AccountActivationValidationListener> accountActivationValidationListeners) {
     this.accountIndexedListeners = accountIndexedListeners;
     this.changeIndexedListeners = changeIndexedListeners;
     this.groupIndexedListeners = groupIndexedListeners;
@@ -91,6 +107,11 @@
     this.refOperationValidationListeners = refOperationValidationListeners;
     this.commentAddedListeners = commentAddedListeners;
     this.refUpdatedListeners = refUpdatedListeners;
+    this.fileHistoryWebLinks = fileHistoryWebLinks;
+    this.patchSetWebLinks = patchSetWebLinks;
+    this.revisionCreatedListeners = revisionCreatedListeners;
+    this.groupBackends = groupBackends;
+    this.accountActivationValidationListeners = accountActivationValidationListeners;
   }
 
   public Registration newRegistration() {
@@ -169,6 +190,27 @@
       return add(refUpdatedListeners, refUpdatedListener);
     }
 
+    public Registration add(FileHistoryWebLink fileHistoryWebLink) {
+      return add(fileHistoryWebLinks, fileHistoryWebLink);
+    }
+
+    public Registration add(PatchSetWebLink patchSetWebLink) {
+      return add(patchSetWebLinks, patchSetWebLink);
+    }
+
+    public Registration add(RevisionCreatedListener revisionCreatedListener) {
+      return add(revisionCreatedListeners, revisionCreatedListener);
+    }
+
+    public Registration add(GroupBackend groupBackend) {
+      return add(groupBackends, groupBackend);
+    }
+
+    public Registration add(
+        AccountActivationValidationListener accountActivationValidationListener) {
+      return add(accountActivationValidationListeners, accountActivationValidationListener);
+    }
+
     private <T> Registration add(DynamicSet<T> dynamicSet, T extension) {
       return add(dynamicSet, extension, "gerrit");
     }
diff --git a/java/com/google/gerrit/index/query/QueryProcessor.java b/java/com/google/gerrit/index/query/QueryProcessor.java
index 61d609b..9501e52 100644
--- a/java/com/google/gerrit/index/query/QueryProcessor.java
+++ b/java/com/google/gerrit/index/query/QueryProcessor.java
@@ -218,7 +218,7 @@
 
     logger.atFine().log(
         "Executing %d %s index queries for %s",
-        cnt, schemaDef.getName(), callerFinder.findCaller());
+        cnt, schemaDef.getName(), callerFinder.findCallerLazy());
     List<QueryResult<T>> out;
     try {
       // Parse and rewrite all queries.
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java b/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
index 2c72f56..d75aa17 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
@@ -675,7 +675,8 @@
   @Override
   protected void onLoad() throws IOException, ConfigInvalidException {
     if (revision != null) {
-      logger.atFine().log("Reading external ID note map (caller: %s)", callerFinder.findCaller());
+      logger.atFine().log(
+          "Reading external ID note map (caller: %s)", callerFinder.findCallerLazy());
       noteMap = NoteMap.read(reader, revision);
     } else {
       noteMap = NoteMap.newEmptyMap();
diff --git a/java/com/google/gerrit/server/change/testing/TestChangeETagComputation.java b/java/com/google/gerrit/server/change/testing/TestChangeETagComputation.java
new file mode 100644
index 0000000..344b9b3
--- /dev/null
+++ b/java/com/google/gerrit/server/change/testing/TestChangeETagComputation.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2019 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.testing;
+
+import com.google.gerrit.server.change.ChangeETagComputation;
+
+public class TestChangeETagComputation {
+
+  public static ChangeETagComputation withETag(String etag) {
+    return (p, id) -> etag;
+  }
+
+  public static ChangeETagComputation withException(RuntimeException e) {
+    return (p, id) -> {
+      throw e;
+    };
+  }
+}
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index e061b65..07bbade 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -3342,7 +3342,7 @@
                 }
 
                 logger.atFine().log(
-                    "Auto-closing %s changes with existing patch sets and %s with new patch sets",
+                    "Auto-closing %d changes with existing patch sets and %d with new patch sets",
                     existingPatchSets, newPatchSets);
                 bu.execute();
               } catch (IOException | StorageException | PermissionBackendException e) {
diff --git a/java/com/google/gerrit/server/logging/CallerFinder.java b/java/com/google/gerrit/server/logging/CallerFinder.java
index 73ffeb5..bd7e608 100644
--- a/java/com/google/gerrit/server/logging/CallerFinder.java
+++ b/java/com/google/gerrit/server/logging/CallerFinder.java
@@ -142,7 +142,7 @@
 
   /**
    * The minimum number of calls known to have occurred between the first call to the target class
-   * and the call of {@link #findCaller()}. If in doubt, specify zero here to avoid accidentally
+   * and the call of {@link #findCallerLazy()}. If in doubt, specify zero here to avoid accidentally
    * skipping past the caller.
    *
    * @return the number of stack elements to skip when computing the caller
@@ -195,7 +195,7 @@
     public abstract CallerFinder build();
   }
 
-  public LazyArg<String> findCaller() {
+  public LazyArg<String> findCallerLazy() {
     return lazy(
         () ->
             targets().stream()
diff --git a/java/com/google/gerrit/server/logging/Metadata.java b/java/com/google/gerrit/server/logging/Metadata.java
index dae78c0..4defded 100644
--- a/java/com/google/gerrit/server/logging/Metadata.java
+++ b/java/com/google/gerrit/server/logging/Metadata.java
@@ -18,11 +18,14 @@
 import com.google.common.base.MoreObjects;
 import com.google.common.base.MoreObjects.ToStringHelper;
 import com.google.common.collect.ImmutableList;
+import com.google.common.flogger.LazyArg;
+import com.google.common.flogger.LazyArgs;
 import com.google.gerrit.common.Nullable;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.Arrays;
+import java.util.Comparator;
 import java.util.Optional;
 
 /** Metadata that is provided to {@link PerformanceLogger}s as context for performance records. */
@@ -141,7 +144,8 @@
   public abstract Optional<String> username();
 
   /**
-   * Returns a string representation of this instance that is suitable for logging.
+   * Returns a string representation of this instance that is suitable for logging. This is wrapped
+   * in a {@link LazyArg} because it is expensive to evaluate.
    *
    * <p>{@link #toString()} formats the {@link Optional} fields as {@code key=Optional[value]} or
    * {@code key=Optional.empty}. Since this class has many optional fields from which usually only a
@@ -178,59 +182,64 @@
    *
    * @return string representation of this instance that is suitable for logging
    */
-  public String toStringForLogging() {
-    // Append class name.
-    String className = getClass().getSimpleName();
-    if (className.startsWith("AutoValue_")) {
-      className = className.substring(10);
-    }
-    ToStringHelper stringHelper = MoreObjects.toStringHelper(className);
+  LazyArg<String> toStringForLoggingLazy() {
+    return LazyArgs.lazy(
+        () -> {
+          // Append class name.
+          String className = getClass().getSimpleName();
+          if (className.startsWith("AutoValue_")) {
+            className = className.substring(10);
+          }
+          ToStringHelper stringHelper = MoreObjects.toStringHelper(className);
 
-    // Append key-value pairs for field which are set.
-    Method[] methods = Metadata.class.getDeclaredMethods();
-    Arrays.<Method>sort(methods, (m1, m2) -> m1.getName().compareTo(m2.getName()));
-    for (Method method : methods) {
-      if (Modifier.isStatic(method.getModifiers())) {
-        // skip static method
-        continue;
-      }
+          // Append key-value pairs for field which are set.
+          Method[] methods = Metadata.class.getDeclaredMethods();
+          Arrays.sort(methods, Comparator.comparing(Method::getName));
+          for (Method method : methods) {
+            if (Modifier.isStatic(method.getModifiers())) {
+              // skip static method
+              continue;
+            }
 
-      if (method.getName().equals(Thread.currentThread().getStackTrace()[1].getMethodName())) {
-        // skip this method (toStringForLogging() method)
-        continue;
-      }
+            if (method.getName().matches("(lambda\\$)?toStringForLoggingLazy(\\$0)?")) {
+              // skip toStringForLoggingLazy() and the lambda itself
+              continue;
+            }
 
-      if (method.getReturnType().equals(Void.TYPE) || method.getParameterCount() > 0) {
-        // skip method since it's not a getter
-        continue;
-      }
+            if (method.getReturnType().equals(Void.TYPE) || method.getParameterCount() > 0) {
+              // skip method since it's not a getter
+              continue;
+            }
 
-      method.setAccessible(true);
+            method.setAccessible(true);
 
-      Object returnValue;
-      try {
-        returnValue = method.invoke(this);
-      } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
-        // should never happen
-        throw new IllegalStateException(e);
-      }
+            Object returnValue;
+            try {
+              returnValue = method.invoke(this);
+            } catch (IllegalArgumentException
+                | IllegalAccessException
+                | InvocationTargetException e) {
+              // should never happen
+              throw new IllegalStateException(e);
+            }
 
-      if (returnValue instanceof Optional) {
-        Optional<?> fieldValueOptional = (Optional<?>) returnValue;
-        if (!fieldValueOptional.isPresent()) {
-          // drop this key-value pair
-          continue;
-        }
+            if (returnValue instanceof Optional) {
+              Optional<?> fieldValueOptional = (Optional<?>) returnValue;
+              if (!fieldValueOptional.isPresent()) {
+                // drop this key-value pair
+                continue;
+              }
 
-        // format as 'key=value' instead of 'key=Optional[value]'
-        stringHelper.add(method.getName(), fieldValueOptional.get());
-      } else {
-        // not an Optional value, keep as is
-        stringHelper.add(method.getName(), returnValue);
-      }
-    }
+              // format as 'key=value' instead of 'key=Optional[value]'
+              stringHelper.add(method.getName(), fieldValueOptional.get());
+            } else {
+              // not an Optional value, keep as is
+              stringHelper.add(method.getName(), returnValue);
+            }
+          }
 
-    return stringHelper.toString();
+          return stringHelper.toString();
+        });
   }
 
   public static Metadata.Builder builder() {
diff --git a/java/com/google/gerrit/server/logging/TraceContext.java b/java/com/google/gerrit/server/logging/TraceContext.java
index 30c5250..21a4ce6 100644
--- a/java/com/google/gerrit/server/logging/TraceContext.java
+++ b/java/com/google/gerrit/server/logging/TraceContext.java
@@ -196,13 +196,13 @@
       this(
           () ->
               logger.atFine().log(
-                  "Starting timer for %s (%s)", operation, metadata.toStringForLogging()),
+                  "Starting timer for %s (%s)", operation, metadata.toStringForLoggingLazy()),
           elapsedMs -> {
             LoggingContext.getInstance()
                 .addPerformanceLogRecord(
                     () -> PerformanceLogRecord.create(operation, elapsedMs, metadata));
             logger.atFine().log(
-                "%s (%s) done (%d ms)", operation, metadata.toStringForLogging(), elapsedMs);
+                "%s (%s) done (%d ms)", operation, metadata.toStringForLoggingLazy(), elapsedMs);
           });
     }
 
diff --git a/java/com/google/gerrit/server/permissions/RefControl.java b/java/com/google/gerrit/server/permissions/RefControl.java
index 9a2ecdd..694bb82 100644
--- a/java/com/google/gerrit/server/permissions/RefControl.java
+++ b/java/com/google/gerrit/server/permissions/RefControl.java
@@ -395,7 +395,7 @@
           withForce,
           projectControl.getProject().getName(),
           refName,
-          callerFinder.findCaller());
+          callerFinder.findCallerLazy());
       return false;
     }
 
@@ -408,7 +408,7 @@
             withForce,
             projectControl.getProject().getName(),
             refName,
-            callerFinder.findCaller());
+            callerFinder.findCallerLazy());
         return true;
       }
     }
@@ -420,7 +420,7 @@
         withForce,
         projectControl.getProject().getName(),
         refName,
-        callerFinder.findCaller());
+        callerFinder.findCallerLazy());
     return false;
   }
 
diff --git a/javatests/com/google/gerrit/acceptance/TestGroupBackendTest.java b/javatests/com/google/gerrit/acceptance/TestGroupBackendTest.java
index fc42474..d8ed29a 100644
--- a/javatests/com/google/gerrit/acceptance/TestGroupBackendTest.java
+++ b/javatests/com/google/gerrit/acceptance/TestGroupBackendTest.java
@@ -16,18 +16,16 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.account.UniversalGroupBackend;
 import com.google.gerrit.server.group.testing.TestGroupBackend;
 import com.google.inject.Inject;
 import org.junit.Test;
 
 public class TestGroupBackendTest extends AbstractDaemonTest {
-  @Inject private DynamicSet<GroupBackend> groupBackends;
   @Inject private UniversalGroupBackend universalGroupBackend;
+  @Inject private ExtensionRegistry extensionRegistry;
 
   private final TestGroupBackend testGroupBackend = new TestGroupBackend();
   private final AccountGroup.UUID testUUID = AccountGroup.uuid("testbackend:test");
@@ -39,11 +37,8 @@
 
   @Test
   public void universalGroupBackendHandlesTestGroup() throws Exception {
-    RegistrationHandle registrationHandle = groupBackends.add("gerrit", testGroupBackend);
-    try {
+    try (Registration registration = extensionRegistry.newRegistration().add(testGroupBackend)) {
       assertThat(universalGroupBackend.handles(testUUID)).isTrue();
-    } finally {
-      registrationHandle.remove();
     }
   }
 
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 1fdf3d6..6c61ae9 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -100,7 +100,6 @@
 import com.google.gerrit.extensions.events.AccountIndexedListener;
 import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -231,9 +230,6 @@
 
   @Inject private AccountOperations accountOperations;
 
-  @Inject
-  private DynamicSet<AccountActivationValidationListener> accountActivationValidationListeners;
-
   @Inject protected GroupOperations groupOperations;
 
   @After
@@ -598,27 +594,26 @@
         accountOperations.newAccount().inactive().preferredEmail("foo@activatable.com").create();
     Account.Id deactivatableAccountId =
         accountOperations.newAccount().preferredEmail("foo@deactivatable.com").create();
-    RegistrationHandle registrationHandle =
-        accountActivationValidationListeners.add(
-            "gerrit",
-            new AccountActivationValidationListener() {
-              @Override
-              public void validateActivation(AccountState account) throws ValidationException {
-                String preferredEmail = account.account().preferredEmail();
-                if (preferredEmail == null || !preferredEmail.endsWith("@activatable.com")) {
-                  throw new ValidationException("not allowed to active account");
-                }
-              }
 
-              @Override
-              public void validateDeactivation(AccountState account) throws ValidationException {
-                String preferredEmail = account.account().preferredEmail();
-                if (preferredEmail == null || !preferredEmail.endsWith("@deactivatable.com")) {
-                  throw new ValidationException("not allowed to deactive account");
-                }
-              }
-            });
-    try {
+    AccountActivationValidationListener listener =
+        new AccountActivationValidationListener() {
+          @Override
+          public void validateActivation(AccountState account) throws ValidationException {
+            String preferredEmail = account.account().preferredEmail();
+            if (preferredEmail == null || !preferredEmail.endsWith("@activatable.com")) {
+              throw new ValidationException("not allowed to active account");
+            }
+          }
+
+          @Override
+          public void validateDeactivation(AccountState account) throws ValidationException {
+            String preferredEmail = account.account().preferredEmail();
+            if (preferredEmail == null || !preferredEmail.endsWith("@deactivatable.com")) {
+              throw new ValidationException("not allowed to deactive account");
+            }
+          }
+        };
+    try (Registration registration = extensionRegistry.newRegistration().add(listener)) {
       /* Test account that can be activated, but not deactivated */
       // Deactivate account that is already inactive
       ResourceConflictException thrown =
@@ -668,8 +663,6 @@
               () -> gApi.accounts().id(deactivatableAccountId.get()).setActive(true));
       assertThat(thrown).hasMessageThat().isEqualTo("not allowed to active account");
       assertThat(accountOperations.account(deactivatableAccountId).get().active()).isFalse();
-    } finally {
-      registrationHandle.remove();
     }
   }
 
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 70d5e99..9767e08 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -161,6 +161,7 @@
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.StarredChangesUtil;
 import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.change.testing.TestChangeETagComputation;
 import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.gerrit.server.index.change.ChangeIndex;
 import com.google.gerrit.server.index.change.ChangeIndexCollection;
@@ -2256,7 +2257,8 @@
     PushOneCommit.Result r = createChange();
     String oldETag = parseResource(r).getETag();
 
-    try (Registration registration = extensionRegistry.newRegistration().add((p, id) -> "foo")) {
+    try (Registration registration =
+        extensionRegistry.newRegistration().add(TestChangeETagComputation.withETag("foo"))) {
       assertThat(parseResource(r).getETag()).isNotEqualTo(oldETag);
     }
 
@@ -2268,7 +2270,8 @@
     PushOneCommit.Result r = createChange();
     String oldETag = parseResource(r).getETag();
 
-    try (Registration registration = extensionRegistry.newRegistration().add((p, id) -> null)) {
+    try (Registration registration =
+        extensionRegistry.newRegistration().add(TestChangeETagComputation.withETag(null))) {
       assertThat(parseResource(r).getETag()).isEqualTo(oldETag);
     }
   }
@@ -2282,9 +2285,8 @@
         extensionRegistry
             .newRegistration()
             .add(
-                (p, id) -> {
-                  throw new StorageException("exception during test");
-                })) {
+                TestChangeETagComputation.withException(
+                    new StorageException("exception during test")))) {
       assertThat(parseResource(r).getETag()).isEqualTo(oldETag);
     }
   }
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index b7517a0..47eec0d 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -77,8 +77,6 @@
 import com.google.gerrit.extensions.common.RevisionInfo;
 import com.google.gerrit.extensions.common.WebLinkInfo;
 import com.google.gerrit.extensions.events.ChangeIndexedListener;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.BinaryResult;
@@ -119,7 +117,6 @@
 import org.junit.Test;
 
 public class RevisionIT extends AbstractDaemonTest {
-  @Inject private DynamicSet<PatchSetWebLink> patchSetLinks;
   @Inject private GetRevisionActions getRevisionActions;
   @Inject private ProjectOperations projectOperations;
   @Inject private RequestScopeOperations requestScopeOperations;
@@ -1314,10 +1311,14 @@
   @Test
   public void commit() throws Exception {
     WebLinkInfo expectedWebLinkInfo = new WebLinkInfo("foo", "imageUrl", "url");
-    RegistrationHandle handle =
-        patchSetLinks.add("gerrit", (projectName, commit) -> expectedWebLinkInfo);
-
-    try {
+    PatchSetWebLink link =
+        new PatchSetWebLink() {
+          @Override
+          public WebLinkInfo getPatchSetWebLink(String projectName, String commit) {
+            return expectedWebLinkInfo;
+          }
+        };
+    try (Registration registration = extensionRegistry.newRegistration().add(link)) {
       PushOneCommit.Result r = createChange();
       RevCommit c = r.getCommit();
 
@@ -1339,8 +1340,6 @@
       assertThat(webLinkInfo.imageUrl).isEqualTo(expectedWebLinkInfo.imageUrl);
       assertThat(webLinkInfo.url).isEqualTo(expectedWebLinkInfo.url);
       assertThat(webLinkInfo.target).isEqualTo(expectedWebLinkInfo.target);
-    } finally {
-      handle.remove();
     }
   }
 
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index e8ab515..8afa0e0 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -50,6 +50,8 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Streams;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
 import com.google.gerrit.acceptance.GerritConfig;
 import com.google.gerrit.acceptance.GitUtil;
 import com.google.gerrit.acceptance.PushOneCommit;
@@ -82,8 +84,6 @@
 import com.google.gerrit.extensions.common.EditInfo;
 import com.google.gerrit.extensions.common.LabelInfo;
 import com.google.gerrit.extensions.common.RevisionInfo;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
 import com.google.gerrit.git.ObjectIds;
 import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -150,12 +150,11 @@
 
   @Inject private ProjectOperations projectOperations;
   @Inject private RequestScopeOperations requestScopeOperations;
+  @Inject private ExtensionRegistry extensionRegistry;
 
   private static String NEW_CHANGE_INDICATOR = " [NEW]";
   private LabelType patchSetLock;
 
-  @Inject private DynamicSet<CommitValidationListener> commitValidators;
-
   @Before
   public void setUpPatchSetLock() throws Exception {
     try (ProjectConfigUpdate u = updateProject(project)) {
@@ -2206,24 +2205,16 @@
   @GerritConfig(name = "receive.maxBatchCommits", value = "2")
   @Test
   public void maxBatchCommitsWithDefaultValidator() throws Exception {
-    TestValidator validator = new TestValidator();
-    RegistrationHandle handle = commitValidators.add("test-validator", validator);
-    try {
+    try (Registration registration = extensionRegistry.newRegistration().add(new TestValidator())) {
       testMaxBatchCommits();
-    } finally {
-      handle.remove();
     }
   }
 
   @GerritConfig(name = "receive.maxBatchCommits", value = "2")
   @Test
   public void maxBatchCommitsWithValidateAllCommitsValidator() throws Exception {
-    TestValidator validator = new TestValidator(true);
-    RegistrationHandle handle = commitValidators.add("test-validator", validator);
-    try {
+    try (Registration registration = extensionRegistry.newRegistration().add(new TestValidator())) {
       testMaxBatchCommits();
-    } finally {
-      handle.remove();
     }
   }
 
@@ -2281,10 +2272,7 @@
   public void skipValidation() throws Exception {
     String master = "refs/heads/master";
     TestValidator validator = new TestValidator();
-    RegistrationHandle handle = commitValidators.add("test-validator", validator);
-    RegistrationHandle handle2 = null;
-
-    try {
+    try (Registration registration = extensionRegistry.newRegistration().add(validator)) {
       // Validation listener is called on normal push
       PushOneCommit push =
           pushFactory.create(admin.newIdent(), testRepo, "change1", "a.txt", "content");
@@ -2313,20 +2301,16 @@
       // Validation listener that needs to validate all commits gets called even
       // when the skip option is used.
       TestValidator validator2 = new TestValidator(true);
-      handle2 = commitValidators.add("test-validator-2", validator2);
-      PushOneCommit push4 =
-          pushFactory.create(admin.newIdent(), testRepo, "change2", "b.txt", "content");
-      push4.setPushOptions(ImmutableList.of(PUSH_OPTION_SKIP_VALIDATION));
-      r = push4.to(master);
-      r.assertOkStatus();
-      // First listener was not called; its count remains the same.
-      assertThat(validator.count()).isEqualTo(1);
-      // Second listener was called.
-      assertThat(validator2.count()).isEqualTo(1);
-    } finally {
-      handle.remove();
-      if (handle2 != null) {
-        handle2.remove();
+      try (Registration registration2 = extensionRegistry.newRegistration().add(validator2)) {
+        PushOneCommit push4 =
+            pushFactory.create(admin.newIdent(), testRepo, "change2", "b.txt", "content");
+        push4.setPushOptions(ImmutableList.of(PUSH_OPTION_SKIP_VALIDATION));
+        r = push4.to(master);
+        r.assertOkStatus();
+        // First listener was not called; its count remains the same.
+        assertThat(validator.count()).isEqualTo(1);
+        // Second listener was called.
+        assertThat(validator2.count()).isEqualTo(1);
       }
     }
   }
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java
index aeb5a4b..f2b8468 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java
@@ -38,6 +38,7 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.server.change.RevisionJson;
+import com.google.gerrit.server.change.testing.TestChangeETagComputation;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.testing.ConfigSuite;
 import com.google.inject.Inject;
@@ -195,7 +196,8 @@
     String change = createChange().getChangeId();
     String oldETag = getETag(change);
 
-    try (Registration registration = extensionRegistry.newRegistration().add((p, id) -> "foo")) {
+    try (Registration registration =
+        extensionRegistry.newRegistration().add(TestChangeETagComputation.withETag("foo"))) {
       assertThat(getETag(change)).isNotEqualTo(oldETag);
     }
 
@@ -207,7 +209,8 @@
     String change = createChange().getChangeId();
     String oldETag = getETag(change);
 
-    try (Registration registration = extensionRegistry.newRegistration().add((p, id) -> null)) {
+    try (Registration registration =
+        extensionRegistry.newRegistration().add(TestChangeETagComputation.withETag(null))) {
       assertThat(getETag(change)).isEqualTo(oldETag);
     }
   }
@@ -221,9 +224,8 @@
         extensionRegistry
             .newRegistration()
             .add(
-                (p, id) -> {
-                  throw new StorageException("exception during test");
-                })) {
+                TestChangeETagComputation.withException(
+                    new StorageException("exception during test")))) {
       assertThat(getETag(change)).isEqualTo(oldETag);
     }
   }
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/IndexChangesIT.java b/javatests/com/google/gerrit/acceptance/rest/config/IndexChangesIT.java
index 7590532..a3c1722 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/IndexChangesIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/config/IndexChangesIT.java
@@ -21,87 +21,84 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.ChangeIndexedCounter;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.events.ChangeIndexedListener;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
 import com.google.gerrit.server.restapi.config.IndexChanges;
 import com.google.inject.Inject;
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
 
 public class IndexChangesIT extends AbstractDaemonTest {
 
-  @Inject private DynamicSet<ChangeIndexedListener> changeIndexedListeners;
   @Inject private ProjectOperations projectOperations;
-
-  private ChangeIndexedCounter changeIndexedCounter;
-  private RegistrationHandle changeIndexedCounterHandle;
-
-  @Before
-  public void addChangeIndexedCounter() {
-    changeIndexedCounter = new ChangeIndexedCounter();
-    changeIndexedCounterHandle = changeIndexedListeners.add("gerrit", changeIndexedCounter);
-  }
-
-  @After
-  public void removeChangeIndexedCounter() {
-    if (changeIndexedCounterHandle != null) {
-      changeIndexedCounterHandle.remove();
-    }
-  }
+  @Inject private ExtensionRegistry extensionRegistry;
 
   @Test
   public void indexRequestFromNonAdminRejected() throws Exception {
-    String changeId = createChange().getChangeId();
-    IndexChanges.Input in = new IndexChanges.Input();
-    in.changes = ImmutableSet.of(changeId);
-    changeIndexedCounter.clear();
-    userRestSession.post("/config/server/index.changes", in).assertForbidden();
-    assertThat(changeIndexedCounter.getCount(info(changeId))).isEqualTo(0);
+    ChangeIndexedCounter changeIndexedCounter = new ChangeIndexedCounter();
+    try (Registration registration =
+        extensionRegistry.newRegistration().add(changeIndexedCounter)) {
+      String changeId = createChange().getChangeId();
+      IndexChanges.Input in = new IndexChanges.Input();
+      in.changes = ImmutableSet.of(changeId);
+      changeIndexedCounter.clear();
+      userRestSession.post("/config/server/index.changes", in).assertForbidden();
+      assertThat(changeIndexedCounter.getCount(info(changeId))).isEqualTo(0);
+    }
   }
 
   @Test
   public void indexVisibleChange() throws Exception {
-    String changeId = createChange().getChangeId();
-    IndexChanges.Input in = new IndexChanges.Input();
-    in.changes = ImmutableSet.of(changeId);
-    changeIndexedCounter.clear();
-    adminRestSession.post("/config/server/index.changes", in).assertOK();
-    assertThat(changeIndexedCounter.getCount(info(changeId))).isEqualTo(1);
+    ChangeIndexedCounter changeIndexedCounter = new ChangeIndexedCounter();
+    try (Registration registration =
+        extensionRegistry.newRegistration().add(changeIndexedCounter)) {
+      String changeId = createChange().getChangeId();
+      IndexChanges.Input in = new IndexChanges.Input();
+      in.changes = ImmutableSet.of(changeId);
+      changeIndexedCounter.clear();
+      adminRestSession.post("/config/server/index.changes", in).assertOK();
+      assertThat(changeIndexedCounter.getCount(info(changeId))).isEqualTo(1);
+    }
   }
 
   @Test
   public void indexNonVisibleChange() throws Exception {
-    String changeId = createChange().getChangeId();
-    ChangeInfo changeInfo = info(changeId);
-    projectOperations
-        .project(project)
-        .forUpdate()
-        .add(block(Permission.READ).ref("refs/heads/master").group(REGISTERED_USERS))
-        .update();
-    IndexChanges.Input in = new IndexChanges.Input();
-    changeIndexedCounter.clear();
-    in.changes = ImmutableSet.of(changeId);
-    adminRestSession.post("/config/server/index.changes", in).assertOK();
-    assertThat(changeIndexedCounter.getCount(changeInfo)).isEqualTo(1);
+    ChangeIndexedCounter changeIndexedCounter = new ChangeIndexedCounter();
+    try (Registration registration =
+        extensionRegistry.newRegistration().add(changeIndexedCounter)) {
+      String changeId = createChange().getChangeId();
+      ChangeInfo changeInfo = info(changeId);
+      projectOperations
+          .project(project)
+          .forUpdate()
+          .add(block(Permission.READ).ref("refs/heads/master").group(REGISTERED_USERS))
+          .update();
+      IndexChanges.Input in = new IndexChanges.Input();
+      changeIndexedCounter.clear();
+      in.changes = ImmutableSet.of(changeId);
+      adminRestSession.post("/config/server/index.changes", in).assertOK();
+      assertThat(changeIndexedCounter.getCount(changeInfo)).isEqualTo(1);
+    }
   }
 
   @Test
   public void indexMultipleChanges() throws Exception {
-    ImmutableSet.Builder<String> changeIds = ImmutableSet.builder();
-    for (int i = 0; i < 10; i++) {
-      changeIds.add(createChange().getChangeId());
-    }
-    IndexChanges.Input in = new IndexChanges.Input();
-    in.changes = changeIds.build();
-    changeIndexedCounter.clear();
-    adminRestSession.post("/config/server/index.changes", in).assertOK();
-    for (String changeId : in.changes) {
-      assertThat(changeIndexedCounter.getCount(info(changeId))).isEqualTo(1);
+    ChangeIndexedCounter changeIndexedCounter = new ChangeIndexedCounter();
+    try (Registration registration =
+        extensionRegistry.newRegistration().add(changeIndexedCounter)) {
+      ImmutableSet.Builder<String> changeIds = ImmutableSet.builder();
+      for (int i = 0; i < 10; i++) {
+        changeIds.add(createChange().getChangeId());
+      }
+      IndexChanges.Input in = new IndexChanges.Input();
+      in.changes = changeIds.build();
+      changeIndexedCounter.clear();
+      adminRestSession.post("/config/server/index.changes", in).assertOK();
+      for (String changeId : in.changes) {
+        assertThat(changeIndexedCounter.getCount(info(changeId))).isEqualTo(1);
+      }
     }
   }
 }
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java b/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java
index bb043c2..c6c26c9 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java
@@ -22,6 +22,8 @@
 import static com.google.gerrit.truth.ConfigSubject.assertThat;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
 import com.google.gerrit.acceptance.GitUtil;
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
@@ -40,8 +42,6 @@
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.GroupInfo;
 import com.google.gerrit.extensions.common.WebLinkInfo;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -72,9 +72,9 @@
 
   private static final String LABEL_CODE_REVIEW = "Code-Review";
 
-  @Inject private DynamicSet<FileHistoryWebLink> fileHistoryWebLinkDynamicSet;
   @Inject private ProjectOperations projectOperations;
   @Inject private RequestScopeOperations requestScopeOperations;
+  @Inject private ExtensionRegistry extensionRegistry;
 
   private Project.NameKey newProjectName;
 
@@ -89,39 +89,39 @@
     assertThat(inheritedName).isEqualTo(AllProjectsNameProvider.DEFAULT);
   }
 
+  private Registration newFileHistoryWebLink() {
+    FileHistoryWebLink weblink =
+        new FileHistoryWebLink() {
+          @Override
+          public WebLinkInfo getFileHistoryWebLink(
+              String projectName, String revision, String fileName) {
+            return new WebLinkInfo(
+                "name", "imageURL", "http://view/" + projectName + "/" + fileName);
+          }
+        };
+    return extensionRegistry.newRegistration().add(weblink);
+  }
+
   @Test
   public void webLink() throws Exception {
-    RegistrationHandle handle =
-        fileHistoryWebLinkDynamicSet.add(
-            "gerrit",
-            (projectName, revision, fileName) ->
-                new WebLinkInfo("name", "imageURL", "http://view/" + projectName + "/" + fileName));
-    try {
+    try (Registration registration = newFileHistoryWebLink()) {
       ProjectAccessInfo info = pApi().access();
       assertThat(info.configWebLinks).hasSize(1);
       assertThat(info.configWebLinks.get(0).url)
           .isEqualTo("http://view/" + newProjectName + "/project.config");
-    } finally {
-      handle.remove();
     }
   }
 
   @Test
   public void webLinkNoRefsMetaConfig() throws Exception {
-    RegistrationHandle handle =
-        fileHistoryWebLinkDynamicSet.add(
-            "gerrit",
-            (projectName, revision, fileName) ->
-                new WebLinkInfo("name", "imageURL", "http://view/" + projectName + "/" + fileName));
-    try (Repository repo = repoManager.openRepository(newProjectName)) {
+    try (Repository repo = repoManager.openRepository(newProjectName);
+        Registration registration = newFileHistoryWebLink()) {
       RefUpdate u = repo.updateRef(RefNames.REFS_CONFIG);
       u.setForceUpdate(true);
       assertThat(u.delete()).isEqualTo(Result.FORCED);
 
       // This should not crash.
       pApi().access();
-    } finally {
-      handle.remove();
     }
   }
 
diff --git a/javatests/com/google/gerrit/acceptance/server/event/EventPayloadIT.java b/javatests/com/google/gerrit/acceptance/server/event/EventPayloadIT.java
index 5ff1c32..8744cfad 100644
--- a/javatests/com/google/gerrit/acceptance/server/event/EventPayloadIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/event/EventPayloadIT.java
@@ -17,49 +17,48 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
 import com.google.gerrit.acceptance.GerritConfig;
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.extensions.events.RevisionCreatedListener;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
 import com.google.inject.Inject;
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
 
 @NoHttpd
 public class EventPayloadIT extends AbstractDaemonTest {
-  @Inject private DynamicSet<RevisionCreatedListener> revisionCreatedListeners;
-
-  private RegistrationHandle eventListenerRegistration;
-  private RevisionCreatedListener.Event lastEvent;
-
-  @Before
-  public void setUp() throws Exception {
-    eventListenerRegistration = revisionCreatedListeners.add("gerrit", event -> lastEvent = event);
-  }
-
-  @After
-  public void cleanup() {
-    eventListenerRegistration.remove();
-  }
+  @Inject private ExtensionRegistry extensionRegistry;
 
   @Test
   public void defaultOptions() throws Exception {
-    createChange();
-
-    assertThat(lastEvent.getChange().submittable).isNotNull();
-    assertThat(lastEvent.getRevision().files).isNotEmpty();
+    RevisionCreatedListener listener =
+        new RevisionCreatedListener() {
+          @Override
+          public void onRevisionCreated(Event event) {
+            assertThat(event.getChange().submittable).isNotNull();
+            assertThat(event.getRevision().files).isNotEmpty();
+          }
+        };
+    try (Registration registration = extensionRegistry.newRegistration().add(listener)) {
+      createChange();
+    }
   }
 
   @Test
   @GerritConfig(name = "event.payload.listChangeOptions", value = "SKIP_MERGEABLE")
   public void configuredOptions() throws Exception {
-    createChange();
-
-    assertThat(lastEvent.getChange().submittable).isNull();
-    assertThat(lastEvent.getChange().mergeable).isNull();
-    assertThat(lastEvent.getRevision().files).isNull();
-    assertThat(lastEvent.getChange().subject).isNotEmpty();
+    RevisionCreatedListener listener =
+        new RevisionCreatedListener() {
+          @Override
+          public void onRevisionCreated(Event event) {
+            assertThat(event.getChange().submittable).isNull();
+            assertThat(event.getChange().mergeable).isNull();
+            assertThat(event.getRevision().files).isNull();
+            assertThat(event.getChange().subject).isNotEmpty();
+          }
+        };
+    try (Registration registration = extensionRegistry.newRegistration().add(listener)) {
+      createChange();
+    }
   }
 }
diff --git a/javatests/com/google/gerrit/server/logging/MetadataTest.java b/javatests/com/google/gerrit/server/logging/MetadataTest.java
index 89e5690..f9ae2c1 100644
--- a/javatests/com/google/gerrit/server/logging/MetadataTest.java
+++ b/javatests/com/google/gerrit/server/logging/MetadataTest.java
@@ -23,7 +23,7 @@
   @Test
   public void stringForLoggingOmitsEmptyOptionalValuesAndReformatsOptionalValuesThatArePresent() {
     Metadata metadata = Metadata.builder().accountId(1000001).branchName("refs/heads/foo").build();
-    assertThat(metadata.toStringForLogging())
+    assertThat(metadata.toStringForLoggingLazy().evaluate())
         .isEqualTo("Metadata{accountId=1000001, branchName=refs/heads/foo, pluginMetadata=[]}");
   }
 
@@ -31,6 +31,7 @@
   public void
       stringForLoggingOmitsEmptyOptionalValuesAndReformatsOptionalValuesThatArePresentNoFieldsSet() {
     Metadata metadata = Metadata.builder().build();
-    assertThat(metadata.toStringForLogging()).isEqualTo("Metadata{pluginMetadata=[]}");
+    assertThat(metadata.toStringForLoggingLazy().evaluate())
+        .isEqualTo("Metadata{pluginMetadata=[]}");
   }
 }
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
index 9e6997c..180d1a3 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
@@ -303,11 +303,11 @@
     },
 
     _calculatePatchChange(files) {
-      const filesNoCommitMsg = files.filter(files => {
-        return files.__path !== '/COMMIT_MSG';
+      const magicFilesExcluded = files.filter(files => {
+        return files.__path !== '/COMMIT_MSG' && files.__path !== '/MERGE_LIST';
       });
 
-      return filesNoCommitMsg.reduce((acc, obj) => {
+      return magicFilesExcluded.reduce((acc, obj) => {
         const inserted = obj.lines_inserted ? obj.lines_inserted : 0;
         const deleted = obj.lines_deleted ? obj.lines_deleted : 0;
         const total_size = (obj.size && obj.binary) ? obj.size : 0;
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
index 3f95fd0..50e7701 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
@@ -167,6 +167,9 @@
         '/COMMIT_MSG': {
           lines_inserted: 9,
         },
+        '/MERGE_LIST': {
+          lines_inserted: 9,
+        },
         'file_added_in_rev2.txt': {
           lines_inserted: 1,
           lines_deleted: 1,
@@ -200,6 +203,9 @@
         '/COMMIT_MSG': {
           lines_inserted: 9,
         },
+        '/MERGE_LIST': {
+          lines_inserted: 9,
+        },
         'myfile.txt': {
           lines_inserted: 1,
           lines_deleted: 1,