Merge "Revert: Respect user preference for WIP by default"
diff --git a/java/com/google/gerrit/acceptance/DisabledChangeIndex.java b/java/com/google/gerrit/acceptance/DisabledChangeIndex.java
index 4748e31..a5757ef 100644
--- a/java/com/google/gerrit/acceptance/DisabledChangeIndex.java
+++ b/java/com/google/gerrit/acceptance/DisabledChangeIndex.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.acceptance;
 
 import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
 import com.google.gerrit.index.QueryOptions;
 import com.google.gerrit.index.Schema;
 import com.google.gerrit.index.query.DataSource;
@@ -72,6 +73,11 @@
   }
 
   @Override
+  public void deleteAllForProject(Project.NameKey project) {
+    throw new UnsupportedOperationException("ChangeIndex is disabled");
+  }
+
+  @Override
   public void deleteAll() {
     throw new UnsupportedOperationException("ChangeIndex is disabled");
   }
diff --git a/java/com/google/gerrit/acceptance/AssertUtil.java b/java/com/google/gerrit/acceptance/PreferencesAssertionUtil.java
similarity index 74%
rename from java/com/google/gerrit/acceptance/AssertUtil.java
rename to java/com/google/gerrit/acceptance/PreferencesAssertionUtil.java
index f72c6d3..8893e31 100644
--- a/java/com/google/gerrit/acceptance/AssertUtil.java
+++ b/java/com/google/gerrit/acceptance/PreferencesAssertionUtil.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.acceptance;
 
 import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.gerrit.extensions.client.NullableBooleanPreferencesFieldComparator.equalBooleanPreferencesFields;
 import static com.google.gerrit.server.config.ConfigUtil.skipField;
 
 import java.lang.reflect.Field;
@@ -22,7 +23,9 @@
 import java.util.HashSet;
 import java.util.Set;
 
-public class AssertUtil {
+/** Utility class for preferences assertion. */
+public class PreferencesAssertionUtil {
+  /** Asserts preferences classes equality, ignoring the specified fields. */
   public static <T> void assertPrefs(T actual, T expected, String... fieldsToExclude)
       throws IllegalArgumentException, IllegalAccessException {
     Set<String> excludedFields = new HashSet<>(Arrays.asList(fieldsToExclude));
@@ -33,12 +36,10 @@
       Object actualVal = field.get(actual);
       Object expectedVal = field.get(expected);
       if (field.getType().isAssignableFrom(Boolean.class)) {
-        if (actualVal == null) {
-          actualVal = false;
-        }
-        if (expectedVal == null) {
-          expectedVal = false;
-        }
+        assertWithMessage("%s [actual: %s, expected: %s]", field.getName(), actualVal, expectedVal)
+            .that(equalBooleanPreferencesFields((Boolean) expectedVal, (Boolean) actualVal))
+            .isTrue();
+        continue;
       }
       assertWithMessage(field.getName()).that(actualVal).isEqualTo(expectedVal);
     }
diff --git a/java/com/google/gerrit/extensions/client/DiffPreferencesInfo.java b/java/com/google/gerrit/extensions/client/DiffPreferencesInfo.java
index ad494cb..8de9826 100644
--- a/java/com/google/gerrit/extensions/client/DiffPreferencesInfo.java
+++ b/java/com/google/gerrit/extensions/client/DiffPreferencesInfo.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.extensions.client;
 
+import static com.google.gerrit.extensions.client.NullableBooleanPreferencesFieldComparator.equalBooleanPreferencesFields;
+
 import com.google.common.base.MoreObjects;
 import com.google.gerrit.common.ConvertibleToProto;
 import java.util.Objects;
@@ -76,25 +78,26 @@
         && Objects.equals(this.fontSize, other.fontSize)
         && Objects.equals(this.lineLength, other.lineLength)
         && Objects.equals(this.cursorBlinkRate, other.cursorBlinkRate)
-        && Objects.equals(this.expandAllComments, other.expandAllComments)
-        && Objects.equals(this.intralineDifference, other.intralineDifference)
-        && Objects.equals(this.manualReview, other.manualReview)
-        && Objects.equals(this.showLineEndings, other.showLineEndings)
-        && Objects.equals(this.showTabs, other.showTabs)
-        && Objects.equals(this.showWhitespaceErrors, other.showWhitespaceErrors)
-        && Objects.equals(this.syntaxHighlighting, other.syntaxHighlighting)
-        && Objects.equals(this.hideTopMenu, other.hideTopMenu)
-        && Objects.equals(this.autoHideDiffTableHeader, other.autoHideDiffTableHeader)
-        && Objects.equals(this.hideLineNumbers, other.hideLineNumbers)
-        && Objects.equals(this.renderEntireFile, other.renderEntireFile)
-        && Objects.equals(this.hideEmptyPane, other.hideEmptyPane)
-        && Objects.equals(this.matchBrackets, other.matchBrackets)
-        && Objects.equals(this.lineWrapping, other.lineWrapping)
+        && equalBooleanPreferencesFields(this.expandAllComments, other.expandAllComments)
+        && equalBooleanPreferencesFields(this.intralineDifference, other.intralineDifference)
+        && equalBooleanPreferencesFields(this.manualReview, other.manualReview)
+        && equalBooleanPreferencesFields(this.showLineEndings, other.showLineEndings)
+        && equalBooleanPreferencesFields(this.showTabs, other.showTabs)
+        && equalBooleanPreferencesFields(this.showWhitespaceErrors, other.showWhitespaceErrors)
+        && equalBooleanPreferencesFields(this.syntaxHighlighting, other.syntaxHighlighting)
+        && equalBooleanPreferencesFields(this.hideTopMenu, other.hideTopMenu)
+        && equalBooleanPreferencesFields(
+            this.autoHideDiffTableHeader, other.autoHideDiffTableHeader)
+        && equalBooleanPreferencesFields(this.hideLineNumbers, other.hideLineNumbers)
+        && equalBooleanPreferencesFields(this.renderEntireFile, other.renderEntireFile)
+        && equalBooleanPreferencesFields(this.hideEmptyPane, other.hideEmptyPane)
+        && equalBooleanPreferencesFields(this.matchBrackets, other.matchBrackets)
+        && equalBooleanPreferencesFields(this.lineWrapping, other.lineWrapping)
         && Objects.equals(this.ignoreWhitespace, other.ignoreWhitespace)
-        && Objects.equals(this.retainHeader, other.retainHeader)
-        && Objects.equals(this.skipDeleted, other.skipDeleted)
-        && Objects.equals(this.skipUnchanged, other.skipUnchanged)
-        && Objects.equals(this.skipUncommented, other.skipUncommented);
+        && equalBooleanPreferencesFields(this.retainHeader, other.retainHeader)
+        && equalBooleanPreferencesFields(this.skipDeleted, other.skipDeleted)
+        && equalBooleanPreferencesFields(this.skipUnchanged, other.skipUnchanged)
+        && equalBooleanPreferencesFields(this.skipUncommented, other.skipUncommented);
   }
 
   @Override
diff --git a/java/com/google/gerrit/extensions/client/EditPreferencesInfo.java b/java/com/google/gerrit/extensions/client/EditPreferencesInfo.java
index 5da211e..6e3d097 100644
--- a/java/com/google/gerrit/extensions/client/EditPreferencesInfo.java
+++ b/java/com/google/gerrit/extensions/client/EditPreferencesInfo.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.extensions.client;
 
+import static com.google.gerrit.extensions.client.NullableBooleanPreferencesFieldComparator.equalBooleanPreferencesFields;
+
 import com.google.common.base.MoreObjects;
 import com.google.gerrit.common.ConvertibleToProto;
 import java.util.Objects;
@@ -46,16 +48,16 @@
         && Objects.equals(this.lineLength, other.lineLength)
         && Objects.equals(this.indentUnit, other.indentUnit)
         && Objects.equals(this.cursorBlinkRate, other.cursorBlinkRate)
-        && Objects.equals(this.hideTopMenu, other.hideTopMenu)
-        && Objects.equals(this.showTabs, other.showTabs)
-        && Objects.equals(this.showWhitespaceErrors, other.showWhitespaceErrors)
-        && Objects.equals(this.syntaxHighlighting, other.syntaxHighlighting)
-        && Objects.equals(this.hideLineNumbers, other.hideLineNumbers)
-        && Objects.equals(this.matchBrackets, other.matchBrackets)
-        && Objects.equals(this.lineWrapping, other.lineWrapping)
-        && Objects.equals(this.indentWithTabs, other.indentWithTabs)
-        && Objects.equals(this.autoCloseBrackets, other.autoCloseBrackets)
-        && Objects.equals(this.showBase, other.showBase);
+        && equalBooleanPreferencesFields(this.hideTopMenu, other.hideTopMenu)
+        && equalBooleanPreferencesFields(this.showTabs, other.showTabs)
+        && equalBooleanPreferencesFields(this.showWhitespaceErrors, other.showWhitespaceErrors)
+        && equalBooleanPreferencesFields(this.syntaxHighlighting, other.syntaxHighlighting)
+        && equalBooleanPreferencesFields(this.hideLineNumbers, other.hideLineNumbers)
+        && equalBooleanPreferencesFields(this.matchBrackets, other.matchBrackets)
+        && equalBooleanPreferencesFields(this.lineWrapping, other.lineWrapping)
+        && equalBooleanPreferencesFields(this.indentWithTabs, other.indentWithTabs)
+        && equalBooleanPreferencesFields(this.autoCloseBrackets, other.autoCloseBrackets)
+        && equalBooleanPreferencesFields(this.showBase, other.showBase);
   }
 
   @Override
diff --git a/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java b/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java
index 44fc4d5..ffdc276 100644
--- a/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java
+++ b/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.extensions.client;
 
+import static com.google.gerrit.extensions.client.NullableBooleanPreferencesFieldComparator.equalBooleanPreferencesFields;
+
 import com.google.common.base.MoreObjects;
 import com.google.gerrit.common.ConvertibleToProto;
 import java.util.List;
@@ -121,6 +123,7 @@
 
   /** Number of changes to show in a screen. */
   public Integer changesPerPage;
+
   /** Type of download URL the user prefers to use. */
   public String downloadScheme;
 
@@ -201,26 +204,32 @@
         && Objects.equals(this.theme, other.theme)
         && Objects.equals(this.dateFormat, other.dateFormat)
         && Objects.equals(this.timeFormat, other.timeFormat)
-        && Objects.equals(this.expandInlineDiffs, other.expandInlineDiffs)
-        && Objects.equals(this.relativeDateInChangeTable, other.relativeDateInChangeTable)
+        && equalBooleanPreferencesFields(this.expandInlineDiffs, other.expandInlineDiffs)
+        && equalBooleanPreferencesFields(
+            this.relativeDateInChangeTable, other.relativeDateInChangeTable)
         && Objects.equals(this.diffView, other.diffView)
-        && Objects.equals(this.sizeBarInChangeTable, other.sizeBarInChangeTable)
-        && Objects.equals(this.legacycidInChangeTable, other.legacycidInChangeTable)
-        && Objects.equals(this.muteCommonPathPrefixes, other.muteCommonPathPrefixes)
-        && Objects.equals(this.signedOffBy, other.signedOffBy)
+        && equalBooleanPreferencesFields(this.sizeBarInChangeTable, other.sizeBarInChangeTable)
+        && equalBooleanPreferencesFields(this.legacycidInChangeTable, other.legacycidInChangeTable)
+        && equalBooleanPreferencesFields(this.muteCommonPathPrefixes, other.muteCommonPathPrefixes)
+        && equalBooleanPreferencesFields(this.signedOffBy, other.signedOffBy)
         && Objects.equals(this.emailStrategy, other.emailStrategy)
         && Objects.equals(this.emailFormat, other.emailFormat)
         && Objects.equals(this.defaultBaseForMerges, other.defaultBaseForMerges)
-        && Objects.equals(this.publishCommentsOnPush, other.publishCommentsOnPush)
-        && Objects.equals(this.disableKeyboardShortcuts, other.disableKeyboardShortcuts)
-        && Objects.equals(this.disableTokenHighlighting, other.disableTokenHighlighting)
-        && Objects.equals(this.workInProgressByDefault, other.workInProgressByDefault)
+        && equalBooleanPreferencesFields(this.publishCommentsOnPush, other.publishCommentsOnPush)
+        && equalBooleanPreferencesFields(
+            this.disableKeyboardShortcuts, other.disableKeyboardShortcuts)
+        && equalBooleanPreferencesFields(
+            this.disableTokenHighlighting, other.disableTokenHighlighting)
+        && equalBooleanPreferencesFields(
+            this.workInProgressByDefault, other.workInProgressByDefault)
         && Objects.equals(this.my, other.my)
         && Objects.equals(this.changeTable, other.changeTable)
-        && Objects.equals(this.allowBrowserNotifications, other.allowBrowserNotifications)
-        && Objects.equals(
+        && equalBooleanPreferencesFields(
+            this.allowBrowserNotifications, other.allowBrowserNotifications)
+        && equalBooleanPreferencesFields(
             this.allowSuggestCodeWhileCommenting, other.allowSuggestCodeWhileCommenting)
-        && Objects.equals(this.allowAutocompletingComments, other.allowAutocompletingComments)
+        && equalBooleanPreferencesFields(
+            this.allowAutocompletingComments, other.allowAutocompletingComments)
         && Objects.equals(this.diffPageSidebar, other.diffPageSidebar);
   }
 
diff --git a/java/com/google/gerrit/extensions/client/NullableBooleanPreferencesFieldComparator.java b/java/com/google/gerrit/extensions/client/NullableBooleanPreferencesFieldComparator.java
new file mode 100644
index 0000000..ccccceb
--- /dev/null
+++ b/java/com/google/gerrit/extensions/client/NullableBooleanPreferencesFieldComparator.java
@@ -0,0 +1,56 @@
+// Copyright (C) 2024 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.extensions.client;
+
+import com.google.gerrit.common.Nullable;
+import java.util.Objects;
+
+/**
+ * Utility class to compare nullable {@link Boolean} preferences fields.
+ *
+ * <p>This class only meant to be used for comparing preferences fields that are potentially loaded
+ * using {@link com.google.gerrit.server.config.ConfigUtil} (such as {@link GeneralPreferencesInfo},
+ * {@link DiffPreferencesInfo} and {@link EditPreferencesInfo}).
+ */
+public class NullableBooleanPreferencesFieldComparator {
+
+  /**
+   * Compare 2 nullable {@link Boolean} preferences fields, regard to {@code null} as {@code false}.
+   *
+   * <p>{@link com.google.gerrit.server.config.ConfigUtil#loadSection} sets the following values for
+   * Boolean fields, relating to {@code null} as {@code false} the same way:
+   *
+   * <table>
+   *   <tr><th> user-def </th> <th> default </th> <th> result </th></tr>
+   *   <tr><td> true </td> <td> true </td> <td> true </td></tr>
+   *   <tr><td> true </td> <td> false </td> <td> true </td></tr>
+   *   <tr><td> true </td> <td> null </td> <td> true </td></tr>
+   *   <tr><td> false </td> <td> true </td> <td> false </td></tr>
+   *   <tr><td> false </td> <td> false </td> <td> null </td></tr>
+   *   <tr><td> false </td> <td> null </td> <td> null </td></tr>
+   *   <tr><td> null </td> <td> true </td> <td> true </td></tr>
+   *   <tr><td> null </td> <td> false </td> <td> null </td></tr>
+   *   <tr><td> null </td> <td> null </td> <td> null </td></tr>
+   * </table>
+   *
+   * When reading the values, the readers always check whether the value is {@code true},
+   * practically referring to {@code null} values as {@code false} anyway. Preferences equality
+   * methods should reflect this state.
+   */
+  public static boolean equalBooleanPreferencesFields(@Nullable Boolean a, @Nullable Boolean b) {
+    return Objects.equals(
+        Objects.requireNonNullElse(a, false), Objects.requireNonNullElse(b, false));
+  }
+}
diff --git a/java/com/google/gerrit/index/testing/AbstractFakeIndex.java b/java/com/google/gerrit/index/testing/AbstractFakeIndex.java
index b06143e..68ba59b 100644
--- a/java/com/google/gerrit/index/testing/AbstractFakeIndex.java
+++ b/java/com/google/gerrit/index/testing/AbstractFakeIndex.java
@@ -26,7 +26,9 @@
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.InternalGroup;
 import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.Project.NameKey;
 import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexConfig;
 import com.google.gerrit.index.QueryOptions;
 import com.google.gerrit.index.Schema;
 import com.google.gerrit.index.SchemaFieldDefs;
@@ -48,6 +50,7 @@
 import com.google.gerrit.server.index.change.ChangeIndex;
 import com.google.gerrit.server.index.group.GroupIndex;
 import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangePredicates;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 import java.time.Instant;
@@ -56,6 +59,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.IntStream;
 import java.util.stream.Stream;
 import org.eclipse.jgit.annotations.Nullable;
@@ -247,6 +251,7 @@
       extends AbstractFakeIndex<Change.Id, ChangeData, Map<String, Object>> implements ChangeIndex {
     private final ChangeData.Factory changeDataFactory;
     private final boolean skipMergable;
+    private final IndexConfig indexConfig;
 
     @Inject
     @VisibleForTesting
@@ -254,10 +259,12 @@
         SitePaths sitePaths,
         ChangeData.Factory changeDataFactory,
         @Assisted Schema<ChangeData> schema,
-        @GerritServerConfig Config cfg) {
+        @GerritServerConfig Config cfg,
+        IndexConfig indexConfig) {
       super(schema, sitePaths, "changes");
       this.changeDataFactory = changeDataFactory;
       this.skipMergable = !MergeabilityComputationBehavior.fromConfig(cfg).includeInIndex();
+      this.indexConfig = indexConfig;
     }
 
     @Override
@@ -314,6 +321,16 @@
     public void deleteByValue(ChangeData value) {
       delete(ChangeIndex.ENTITY_TO_KEY.apply(value));
     }
+
+    @Override
+    public void deleteAllForProject(NameKey project) {
+      QueryOptions opts = QueryOptions.create(indexConfig, 0, Integer.MAX_VALUE, Set.of());
+      DataSource<ChangeData> result = getSource(ChangePredicates.project(project), opts);
+      for (FieldBundle f : result.readRaw().toList()) {
+        int changeNum = f.<Integer>getValue(ChangeField.CHANGENUM_SPEC).intValue();
+        delete(Change.id(changeNum));
+      }
+    }
   }
 
   /** Fake implementation of {@link AccountIndex} where all filtering happens in-memory. */
diff --git a/java/com/google/gerrit/lucene/ChangeSubIndex.java b/java/com/google/gerrit/lucene/ChangeSubIndex.java
index 024b102..2bc29d9 100644
--- a/java/com/google/gerrit/lucene/ChangeSubIndex.java
+++ b/java/com/google/gerrit/lucene/ChangeSubIndex.java
@@ -22,6 +22,7 @@
 
 import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
 import com.google.gerrit.index.QueryOptions;
 import com.google.gerrit.index.Schema;
 import com.google.gerrit.index.Schema.Values;
@@ -105,6 +106,11 @@
   }
 
   @Override
+  public void deleteAllForProject(Project.NameKey project) {
+    throw new UnsupportedOperationException("don't use ChangeSubIndex directly");
+  }
+
+  @Override
   public DataSource<ChangeData> getSource(Predicate<ChangeData> p, QueryOptions opts)
       throws QueryParseException {
     throw new UnsupportedOperationException("don't use ChangeSubIndex directly");
diff --git a/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index e5f7787..7b88f45 100644
--- a/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -259,6 +259,16 @@
   }
 
   @Override
+  public void deleteAllForProject(Project.NameKey project) {
+    Term allForProject = new Term(ChangeField.PROJECT_SPEC.getName(), project.get());
+    try {
+      Futures.allAsList(openIndex.delete(allForProject), closedIndex.delete(allForProject)).get();
+    } catch (ExecutionException | InterruptedException e) {
+      throw new StorageException(e);
+    }
+  }
+
+  @Override
   public void deleteAll() {
     openIndex.deleteAll();
     closedIndex.deleteAll();
diff --git a/java/com/google/gerrit/server/config/ConfigUtil.java b/java/com/google/gerrit/server/config/ConfigUtil.java
index e76207c..121c62e 100644
--- a/java/com/google/gerrit/server/config/ConfigUtil.java
+++ b/java/com/google/gerrit/server/config/ConfigUtil.java
@@ -371,8 +371,12 @@
         } else if (isLong(t)) {
           f.set(s, cfg.getLong(section, sub, n, (Long) d));
         } else if (isBoolean(t)) {
+          // Sets the field if:
+          // - 'cfg' value is 'true'.
+          // - the default value is 'true'.
+          // - i is set.
           boolean b = cfg.getBoolean(section, sub, n, (Boolean) d);
-          if (b || i != null) {
+          if (b || (Boolean) d || i != null) {
             f.set(s, b);
           }
         } else if (t.isEnum()) {
@@ -427,10 +431,13 @@
             requireNonNull(val, "Default cannot be null for: " + n);
           }
         }
-        if (!isBoolean(t) || (boolean) val) {
+        if (!isBoolean(t) || (boolean) val || (Boolean) f.get(defaults)) {
           // To reproduce the same behavior as in the loadSection method above, values are
           // explicitly set for all types, except the boolean type. For the boolean type, the value
-          // is set only if it is 'true' (so, the false value is omitted in the result object).
+          // is set only in the following cases:
+          // - 'cfg' value is 'true'.
+          // - the default value is 'true'.
+          // Otherwise, false values are omitted in the result object.
           f.set(s, val);
         }
       }
diff --git a/java/com/google/gerrit/server/index/change/ChangeIndex.java b/java/com/google/gerrit/server/index/change/ChangeIndex.java
index 74e9af1..cb44a3b 100644
--- a/java/com/google/gerrit/server/index/change/ChangeIndex.java
+++ b/java/com/google/gerrit/server/index/change/ChangeIndex.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.index.change;
 
 import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
 import com.google.gerrit.index.Index;
 import com.google.gerrit.index.IndexDefinition;
 import com.google.gerrit.index.query.Predicate;
@@ -35,4 +36,6 @@
   }
 
   Function<ChangeData, Change.Id> ENTITY_TO_KEY = ChangeData::getId;
+
+  public void deleteAllForProject(Project.NameKey project);
 }
diff --git a/java/com/google/gerrit/server/restapi/change/ApplyProvidedFix.java b/java/com/google/gerrit/server/restapi/change/ApplyProvidedFix.java
index 4e05420..13b8b00 100644
--- a/java/com/google/gerrit/server/restapi/change/ApplyProvidedFix.java
+++ b/java/com/google/gerrit/server/restapi/change/ApplyProvidedFix.java
@@ -56,6 +56,7 @@
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.patch.PatchApplier;
+import org.eclipse.jgit.patch.PatchApplier.Result.Error;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.treewalk.TreeWalk;
@@ -185,9 +186,20 @@
       try (ObjectInserter oi = repository.newObjectInserter()) {
         ApplyPatchInput inp = new ApplyPatchInput();
         inp.patch = patch;
-        inp.allowConflicts = false;
+        // Allow conflicts for showing more precise error message.
+        inp.allowConflicts = true;
         result =
             ApplyPatchUtil.applyPatch(repository, oi, inp, repository.parseCommit(targetCommit));
+        if (!result.getErrors().isEmpty()) {
+          String errorMessage =
+              (result.getErrors().stream().anyMatch(Error::isGitConflict)
+                      ? "Merge conflict while applying a fix:\n"
+                      : "Error while applying a fix:\n")
+                  + result.getErrors().stream()
+                      .map(Error::toString)
+                      .collect(Collectors.joining("\n"));
+          throw new ResourceConflictException(errorMessage);
+        }
         oi.flush();
         for (String path : result.getPaths()) {
           try (TreeWalk tw = TreeWalk.forPath(rw.getObjectReader(), path, result.getTreeId())) {
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 55a2023..6b540b2 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -71,6 +71,7 @@
 import com.google.gerrit.acceptance.ExtensionRegistry;
 import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
 import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.PushOneCommit.Result;
 import com.google.gerrit.acceptance.Sandboxed;
 import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.acceptance.UseClockStep;
@@ -2672,6 +2673,23 @@
   }
 
   @Test
+  @UseClockStep
+  public void updateDrafts() throws Exception {
+    try {
+      PushOneCommit.Result r1 = createChange();
+
+      requestScopeOperations.setApiUser(user.id());
+      CommentInfo draft = createDraft(r1, PushOneCommit.FILE_NAME, "draft creation");
+      updateDraft(r1, draft, "draft update");
+      assertThat(gApi.changes().id(r1.getChangeId()).current().draftsAsList()).hasSize(1);
+      assertThat(gApi.changes().id(r1.getChangeId()).current().draftsAsList().get(0).message)
+          .isEqualTo("draft update");
+    } finally {
+      cleanUpDrafts();
+    }
+  }
+
+  @Test
   public void userCanGenerateNewHttpPassword() throws Exception {
     sender.clear();
     String newPassword = gApi.accounts().self().generateHttpPassword();
@@ -2937,12 +2955,22 @@
         .isEqualTo(postUpdateStatus);
   }
 
-  private void createDraft(PushOneCommit.Result r, String path, String message) throws Exception {
+  @CanIgnoreReturnValue
+  private CommentInfo createDraft(Result r, String path, String message) throws Exception {
     DraftInput in = new DraftInput();
     in.path = path;
     in.line = 1;
     in.message = message;
-    gApi.changes().id(r.getChangeId()).current().createDraft(in);
+    return gApi.changes().id(r.getChangeId()).current().createDraft(in).get();
+  }
+
+  @CanIgnoreReturnValue
+  private CommentInfo updateDraft(Result r, CommentInfo orig, String newMessage) throws Exception {
+    DraftInput in = new DraftInput();
+    in.path = orig.path;
+    in.line = orig.line;
+    in.message = newMessage;
+    return gApi.changes().id(r.getChangeId()).current().draft(orig.id).update(in);
   }
 
   private void cleanUpDrafts() throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/DiffPreferencesIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/DiffPreferencesIT.java
index e3380c0..7c6e5ed 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/DiffPreferencesIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/DiffPreferencesIT.java
@@ -15,7 +15,7 @@
 package com.google.gerrit.acceptance.api.accounts;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.acceptance.AssertUtil.assertPrefs;
+import static com.google.gerrit.acceptance.PreferencesAssertionUtil.assertPrefs;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.NoHttpd;
@@ -64,6 +64,9 @@
 
     DiffPreferencesInfo o = gApi.accounts().id(admin.id().get()).setDiffPreferences(i);
     assertPrefs(o, i);
+    // Re-getting the preferences should yield the same fields
+    o = gApi.accounts().id(admin.id().get()).getDiffPreferences();
+    assertPrefs(o, i);
 
     // Partially fill input record
     i = new DiffPreferencesInfo();
@@ -74,6 +77,38 @@
   }
 
   @Test
+  public void setDiffPreferences_booleanHandling() throws Exception {
+    DiffPreferencesInfo update = new DiffPreferencesInfo();
+    update.showLineEndings = true; // Default is 'true'
+    update.lineWrapping = true; // Default is 'false'
+    update.showTabs = false; // Default is 'true'
+    update.hideTopMenu = false; // Default is 'false'
+    update.intralineDifference = null; // Default is 'true'
+    update.retainHeader = null; // Default is 'false'
+
+    DiffPreferencesInfo o = gApi.accounts().id(admin.id().get()).setDiffPreferences(update);
+
+    // Explicitly assert configured values
+    assertThat(o.showLineEndings).isTrue();
+    assertThat(o.lineWrapping).isTrue();
+    assertThat(o.showTabs).isFalse();
+    assertThat(o.hideTopMenu).isNull(); // Both new value and default are false, omitted
+    assertThat(o.intralineDifference).isTrue();
+    assertThat(o.retainHeader).isNull(); // new value is 'null' and default is false, omitted
+
+    // assert unaffected fields
+    assertPrefs(
+        o,
+        DiffPreferencesInfo.defaults(),
+        "showLineEndings",
+        "lineWrapping",
+        "showTabs",
+        "hideTopMenu",
+        "intralineDifference",
+        "retainHeader");
+  }
+
+  @Test
   public void getDiffPreferencesWithConfiguredDefaults() throws Exception {
     DiffPreferencesInfo d = DiffPreferencesInfo.defaults();
     int newLineLength = d.lineLength + 10;
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/EditPreferencesIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/EditPreferencesIT.java
index 74829a3..d38c09a 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/EditPreferencesIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/EditPreferencesIT.java
@@ -15,7 +15,7 @@
 package com.google.gerrit.acceptance.api.accounts;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.acceptance.AssertUtil.assertPrefs;
+import static com.google.gerrit.acceptance.PreferencesAssertionUtil.assertPrefs;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.NoHttpd;
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java
index afa9bca..db038d1 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java
@@ -15,7 +15,7 @@
 package com.google.gerrit.acceptance.api.accounts;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.acceptance.AssertUtil.assertPrefs;
+import static com.google.gerrit.acceptance.PreferencesAssertionUtil.assertPrefs;
 import static com.google.gerrit.testing.GerritJUnit.assertThrows;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
diff --git a/javatests/com/google/gerrit/acceptance/api/config/DiffPreferencesIT.java b/javatests/com/google/gerrit/acceptance/api/config/DiffPreferencesIT.java
index 70aa557..35166e9 100644
--- a/javatests/com/google/gerrit/acceptance/api/config/DiffPreferencesIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/config/DiffPreferencesIT.java
@@ -15,7 +15,7 @@
 package com.google.gerrit.acceptance.api.config;
 
 import static com.google.common.truth.Truth.assertWithMessage;
-import static com.google.gerrit.acceptance.AssertUtil.assertPrefs;
+import static com.google.gerrit.acceptance.PreferencesAssertionUtil.assertPrefs;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.NoHttpd;
diff --git a/javatests/com/google/gerrit/acceptance/api/config/EditPreferencesIT.java b/javatests/com/google/gerrit/acceptance/api/config/EditPreferencesIT.java
index 02f1ec3..04059a6 100644
--- a/javatests/com/google/gerrit/acceptance/api/config/EditPreferencesIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/config/EditPreferencesIT.java
@@ -15,7 +15,7 @@
 package com.google.gerrit.acceptance.api.config;
 
 import static com.google.common.truth.Truth.assertWithMessage;
-import static com.google.gerrit.acceptance.AssertUtil.assertPrefs;
+import static com.google.gerrit.acceptance.PreferencesAssertionUtil.assertPrefs;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.NoHttpd;
diff --git a/javatests/com/google/gerrit/acceptance/api/config/GeneralPreferencesIT.java b/javatests/com/google/gerrit/acceptance/api/config/GeneralPreferencesIT.java
index 221e171..1baf119 100644
--- a/javatests/com/google/gerrit/acceptance/api/config/GeneralPreferencesIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/config/GeneralPreferencesIT.java
@@ -15,7 +15,7 @@
 package com.google.gerrit.acceptance.api.config;
 
 import static com.google.common.truth.Truth.assertWithMessage;
-import static com.google.gerrit.acceptance.AssertUtil.assertPrefs;
+import static com.google.gerrit.acceptance.PreferencesAssertionUtil.assertPrefs;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.NoHttpd;
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/ApplyProvidedFixIT.java b/javatests/com/google/gerrit/acceptance/api/revision/ApplyProvidedFixIT.java
index d66a0a5..b74e026 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/ApplyProvidedFixIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/ApplyProvidedFixIT.java
@@ -450,6 +450,44 @@
   }
 
   @Test
+  public void applyProvidedFixOnNewerPatchset_mergeConflictThrowsResourceConflictException()
+      throws Exception {
+    // Remember patch set and add another one.
+    int previousRevision = gApi.changes().id(changeId).get().currentRevisionNumber;
+    // Change first 2 lines;
+    String modifiedContent =
+        "abc\ndef\n"
+            + FILE_CONTENT.substring(
+                FILE_CONTENT.indexOf("\n", FILE_CONTENT.indexOf("\n") + 1) + 1);
+    amendChange(
+        changeId,
+        "refs/for/master",
+        admin,
+        testRepo,
+        PushOneCommit.SUBJECT,
+        FILE_NAME,
+        modifiedContent);
+    FixReplacementInfo fixReplacementInfo1 = new FixReplacementInfo();
+    fixReplacementInfo1.path = FILE_NAME;
+    fixReplacementInfo1.range = createRange(2, 0, 2, 0);
+    fixReplacementInfo1.replacement = "First modification\n";
+
+    ApplyProvidedFixInput applyProvidedFixInput = new ApplyProvidedFixInput();
+
+    applyProvidedFixInput.fixReplacementInfos = Arrays.asList(fixReplacementInfo1);
+    applyProvidedFixInput.originalPatchsetForFix = previousRevision;
+
+    ResourceConflictException thrown =
+        assertThrows(
+            ResourceConflictException.class,
+            () -> gApi.changes().id(changeId).current().applyFix(applyProvidedFixInput));
+
+    assertThat(thrown.getMessage()).contains("Merge conflict");
+    // Change edit must not be created
+    assertThat(gApi.changes().id(changeId).edit().get()).isEmpty();
+  }
+
+  @Test
   public void applyProvidedFixOnCommitMessageCanBeAppliedToNewerPatchset() throws Exception {
     // Set a dedicated commit message.
     String footer = "\nChange-Id: " + changeId + "\n";
diff --git a/javatests/com/google/gerrit/acceptance/server/index/change/LuceneChangeIndexerIT.java b/javatests/com/google/gerrit/acceptance/server/index/change/LuceneChangeIndexerIT.java
index b8af367..d6aa709 100644
--- a/javatests/com/google/gerrit/acceptance/server/index/change/LuceneChangeIndexerIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/index/change/LuceneChangeIndexerIT.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.config.GerritConfig;
 import com.google.gerrit.entities.Project.NameKey;
+import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.index.IndexDefinition;
 import com.google.gerrit.index.RefState;
 import com.google.gerrit.index.SiteIndexer.Result;
@@ -35,6 +36,7 @@
 import com.google.gerrit.testing.ConfigSuite;
 import com.google.inject.Inject;
 import java.util.Collection;
+import java.util.List;
 import java.util.Set;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ObjectId;
@@ -104,6 +106,21 @@
     }
   }
 
+  @Test
+  public void deleteAllForProjectDeletesFromIndex() throws Exception {
+    createChange();
+    createChange();
+    createChange();
+
+    List<ChangeInfo> result = gApi.changes().query("project:" + project.get()).get();
+    assertThat(result).hasSize(3);
+
+    index.deleteAllForProject(project);
+
+    result = gApi.changes().query("project:" + project.get()).get();
+    assertThat(result).isEmpty();
+  }
+
   private void createIndexWithMissingChangeAndReindex(ChangeIndexedCounter changeIndexedCounter)
       throws Exception {
     PushOneCommit.Result res = createChange();
diff --git a/javatests/com/google/gerrit/acceptance/ssh/CustomIndexIT.java b/javatests/com/google/gerrit/acceptance/ssh/CustomIndexIT.java
index 7fead5c..448897c 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/CustomIndexIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/CustomIndexIT.java
@@ -17,6 +17,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.index.IndexConfig;
 import com.google.gerrit.index.IndexType;
 import com.google.gerrit.index.Schema;
 import com.google.gerrit.index.project.ProjectIndex;
@@ -108,7 +109,8 @@
       SitePaths sitePaths,
       ChangeData.Factory changeDataFactory,
       @Assisted Schema<ChangeData> schema,
-      @GerritServerConfig Config cfg) {
-    super(sitePaths, changeDataFactory, schema, cfg);
+      @GerritServerConfig Config cfg,
+      IndexConfig indexConfig) {
+    super(sitePaths, changeDataFactory, schema, cfg, indexConfig);
   }
 }
diff --git a/javatests/com/google/gerrit/server/config/CachedPreferencesTest.java b/javatests/com/google/gerrit/server/config/CachedPreferencesTest.java
index 131fb23..36f0058 100644
--- a/javatests/com/google/gerrit/server/config/CachedPreferencesTest.java
+++ b/javatests/com/google/gerrit/server/config/CachedPreferencesTest.java
@@ -50,7 +50,9 @@
     CachedPreferences pref = CachedPreferences.fromLegacyConfig(originalCfg);
     GeneralPreferencesInfo general = CachedPreferences.general(Optional.empty(), pref);
 
-    assertThat(general.changesPerPage).isEqualTo(2);
+    GeneralPreferencesInfo expected = GeneralPreferencesInfo.defaults();
+    expected.changesPerPage = 2;
+    assertThat(cleanGeneralPreferences(general)).isEqualTo(expected);
   }
 
   @Test
@@ -61,7 +63,9 @@
     CachedPreferences pref = CachedPreferences.fromLegacyConfig(originalCfg);
     DiffPreferencesInfo diff = CachedPreferences.diff(Optional.empty(), pref);
 
-    assertThat(diff.context).isEqualTo(3);
+    DiffPreferencesInfo expected = DiffPreferencesInfo.defaults();
+    expected.context = 3;
+    assertThat(diff).isEqualTo(expected);
   }
 
   @Test
@@ -72,7 +76,9 @@
     CachedPreferences pref = CachedPreferences.fromLegacyConfig(originalCfg);
     EditPreferencesInfo edit = CachedPreferences.edit(Optional.empty(), pref);
 
-    assertThat(edit.tabSize).isEqualTo(5);
+    EditPreferencesInfo expected = EditPreferencesInfo.defaults();
+    expected.tabSize = 5;
+    assertThat(edit).isEqualTo(expected);
   }
 
   @Test
@@ -100,7 +106,9 @@
     CachedPreferences pref = CachedPreferences.fromUserPreferencesProto(originalProto);
     GeneralPreferencesInfo general = CachedPreferences.general(Optional.empty(), pref);
 
-    assertThat(general.changesPerPage).isEqualTo(11);
+    GeneralPreferencesInfo expected = GeneralPreferencesInfo.defaults();
+    expected.changesPerPage = 11;
+    assertThat(cleanGeneralPreferences(general)).isEqualTo(expected);
   }
 
   @Test
@@ -113,7 +121,9 @@
     CachedPreferences pref = CachedPreferences.fromUserPreferencesProto(originalProto);
     DiffPreferencesInfo diff = CachedPreferences.diff(Optional.empty(), pref);
 
-    assertThat(diff.context).isEqualTo(13);
+    DiffPreferencesInfo expected = DiffPreferencesInfo.defaults();
+    expected.context = 13;
+    assertThat(diff).isEqualTo(expected);
   }
 
   @Test
@@ -126,7 +136,9 @@
     CachedPreferences pref = CachedPreferences.fromUserPreferencesProto(originalProto);
     EditPreferencesInfo edit = CachedPreferences.edit(Optional.empty(), pref);
 
-    assertThat(edit.tabSize).isEqualTo(17);
+    EditPreferencesInfo expected = EditPreferencesInfo.defaults();
+    expected.tabSize = 17;
+    assertThat(edit).isEqualTo(expected);
   }
 
   @Test
@@ -155,10 +167,12 @@
     UserPreferences originalProto =
         UserPreferences.newBuilder()
             .setGeneralPreferencesInfo(
-                UserPreferences.GeneralPreferencesInfo.newBuilder().setChangesPerPage(19))
+                UserPreferences.GeneralPreferencesInfo.newBuilder()
+                    .setChangesPerPage(19)
+                    .setAllowAutocompletingComments(false))
             .build();
     Config originalCfg = new Config();
-    originalCfg.fromText("[general]\n\tchangesPerPage = 19");
+    originalCfg.fromText("[general]\n\tchangesPerPage = 19\n\tallowAutocompletingComments = false");
 
     CachedPreferences protoPref = CachedPreferences.fromUserPreferencesProto(originalProto);
     GeneralPreferencesInfo protoGeneral = CachedPreferences.general(Optional.empty(), protoPref);
@@ -172,10 +186,13 @@
   public void bothPreferencesTypes_getDiffPreferencesAreEqual() throws Exception {
     UserPreferences originalProto =
         UserPreferences.newBuilder()
-            .setDiffPreferencesInfo(UserPreferences.DiffPreferencesInfo.newBuilder().setContext(23))
+            .setDiffPreferencesInfo(
+                UserPreferences.DiffPreferencesInfo.newBuilder()
+                    .setContext(23)
+                    .setHideTopMenu(false))
             .build();
     Config originalCfg = new Config();
-    originalCfg.fromText("[diff]\n\tcontext = 23");
+    originalCfg.fromText("[diff]\n\tcontext = 23\n\thideTopMenu = false");
 
     CachedPreferences protoPref = CachedPreferences.fromUserPreferencesProto(originalProto);
     DiffPreferencesInfo protoDiff = CachedPreferences.diff(Optional.empty(), protoPref);
@@ -189,10 +206,13 @@
   public void bothPreferencesTypes_getEditPreferencesAreEqual() throws Exception {
     UserPreferences originalProto =
         UserPreferences.newBuilder()
-            .setEditPreferencesInfo(UserPreferences.EditPreferencesInfo.newBuilder().setTabSize(27))
+            .setEditPreferencesInfo(
+                UserPreferences.EditPreferencesInfo.newBuilder()
+                    .setTabSize(27)
+                    .setAutoCloseBrackets(true))
             .build();
     Config originalCfg = new Config();
-    originalCfg.fromText("[edit]\n\ttabSize = 27");
+    originalCfg.fromText("[edit]\n\ttabSize = 27\n\tautoCloseBrackets = true");
 
     CachedPreferences protoPref = CachedPreferences.fromUserPreferencesProto(originalProto);
     EditPreferencesInfo protoEdit = CachedPreferences.edit(Optional.empty(), protoPref);
@@ -231,4 +251,15 @@
         StorageException.class,
         () -> CachedPreferences.edit(Optional.of(defaults), userPreferences));
   }
+
+  /**
+   * {@link PreferencesParserUtil#parseGeneralPreferences} sets explicit values to {@link
+   * GeneralPreferencesInfo#my} and {@link GeneralPreferencesInfo#changeTable} in case of null
+   * defaults. Set these back to {@code null} for comparing with the defaults.
+   */
+  private static GeneralPreferencesInfo cleanGeneralPreferences(GeneralPreferencesInfo pref) {
+    pref.my = null;
+    pref.changeTable = null;
+    return pref;
+  }
 }
diff --git a/javatests/com/google/gerrit/server/config/ConfigUtilTest.java b/javatests/com/google/gerrit/server/config/ConfigUtilTest.java
index 035878c..f8c04a9 100644
--- a/javatests/com/google/gerrit/server/config/ConfigUtilTest.java
+++ b/javatests/com/google/gerrit/server/config/ConfigUtilTest.java
@@ -107,7 +107,7 @@
     assertThat(out.ld).isEqualTo(d.ld);
     assertThat(out.b).isEqualTo(in.b);
     assertThat(out.bb).isEqualTo(in.bb);
-    assertThat(out.bd).isNull();
+    assertThat(out.bd).isFalse();
     assertThat(out.s).isEqualTo(in.s);
     assertThat(out.sd).isEqualTo(d.sd);
     assertThat(out.nd).isNull();
diff --git a/javatests/com/google/gerrit/server/index/change/FakeChangeIndex.java b/javatests/com/google/gerrit/server/index/change/FakeChangeIndex.java
index 1a51c00..e4ccfdb 100644
--- a/javatests/com/google/gerrit/server/index/change/FakeChangeIndex.java
+++ b/javatests/com/google/gerrit/server/index/change/FakeChangeIndex.java
@@ -18,6 +18,7 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
 import com.google.gerrit.index.IndexedField;
 import com.google.gerrit.index.QueryOptions;
 import com.google.gerrit.index.Schema;
@@ -105,6 +106,11 @@
   }
 
   @Override
+  public void deleteAllForProject(Project.NameKey project) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
   public void deleteAll() {
     throw new UnsupportedOperationException();
   }
diff --git a/plugins/delete-project b/plugins/delete-project
index bd49c1b..08bc142 160000
--- a/plugins/delete-project
+++ b/plugins/delete-project
@@ -1 +1 @@
-Subproject commit bd49c1bf4212a166d1246774cb8c70d54ead31ba
+Subproject commit 08bc142c829aed843b1145b27fab5bb1d973cb51
diff --git a/polygerrit-ui/app/constants/reporting.ts b/polygerrit-ui/app/constants/reporting.ts
index 98dd592..ee9b041 100644
--- a/polygerrit-ui/app/constants/reporting.ts
+++ b/polygerrit-ui/app/constants/reporting.ts
@@ -118,6 +118,7 @@
   CHECKS_CHIP_CLICKED = 'checks-chip-clicked',
   CHECKS_CHIP_LINK_CLICKED = 'checks-chip-link-clicked',
   CHECKS_RESULT_ROW_TOGGLE = 'checks-result-row-toggle',
+  CHECKS_RESULT_DIFF_RENDERED = 'checks-result-diff-rendered',
   CHECKS_ACTION_TRIGGERED = 'checks-action-triggered',
   CHECKS_TAG_CLICKED = 'checks-tag-clicked',
   CHECKS_RESULT_FILTER_CHANGED = 'checks-result-filter-changed',
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
index f1ef362..72e8667 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -683,7 +683,7 @@
     );
     subscribe(
       this,
-      () => this.getChangeModel().latestRevision$,
+      () => this.getChangeModel().latestRevisionWithEdit$,
       revision => {
         this.latestCommitMessage = this.prepareCommitMsgForLinkify(
           revision?.commit?.message ?? ''
diff --git a/polygerrit-ui/app/elements/checks/gr-diff-check-result.ts b/polygerrit-ui/app/elements/checks/gr-diff-check-result.ts
index 16d3d3e..2a6f024 100644
--- a/polygerrit-ui/app/elements/checks/gr-diff-check-result.ts
+++ b/polygerrit-ui/app/elements/checks/gr-diff-check-result.ts
@@ -24,6 +24,8 @@
 import {commentsModelToken} from '../../models/comments/comments-model';
 import {subscribe} from '../lit/subscription-controller';
 import {changeModelToken} from '../../models/change/change-model';
+import {getAppContext} from '../../services/app-context';
+import {Interaction} from '../../constants/reporting';
 
 @customElement('gr-diff-check-result')
 export class GrDiffCheckResult extends LitElement {
@@ -50,6 +52,8 @@
 
   private readonly getCommentsModel = resolve(this, commentsModelToken);
 
+  private readonly reporting = getAppContext().reportingService;
+
   static override get styles() {
     return [
       fontStyles,
@@ -144,6 +148,14 @@
     );
   }
 
+  protected override firstUpdated(_changedProperties: PropertyValues): void {
+    // This component is only used in gr-diff-host, so we can assume that the
+    // result is always rendered in the diff.
+    this.reporting.reportInteraction(Interaction.CHECKS_RESULT_DIFF_RENDERED, {
+      checkName: this.result?.checkName,
+    });
+  }
+
   override render() {
     if (!this.result) return;
     const cat = this.result.category.toLowerCase();
diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
index 5319c90..3bb89d4 100644
--- a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
+++ b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
@@ -441,6 +441,8 @@
       fileExtension: getFileExtension(
         this.fixSuggestions?.[0].replacements?.[0].path ?? ''
       ),
+      success: res.ok,
+      status: res.status,
     });
   }
 }
diff --git a/polygerrit-ui/app/elements/shared/gr-suggestion-diff-preview/gr-suggestion-diff-preview.ts b/polygerrit-ui/app/elements/shared/gr-suggestion-diff-preview/gr-suggestion-diff-preview.ts
index 93f17c8..336680c 100644
--- a/polygerrit-ui/app/elements/shared/gr-suggestion-diff-preview/gr-suggestion-diff-preview.ts
+++ b/polygerrit-ui/app/elements/shared/gr-suggestion-diff-preview/gr-suggestion-diff-preview.ts
@@ -130,6 +130,14 @@
     );
     subscribe(
       this,
+      () => this.getChangeModel().revisions$,
+      revisions =>
+        (this.hasEdit = Object.values(revisions).some(
+          info => info._number === EDIT
+        ))
+    );
+    subscribe(
+      this,
       () => this.getChangeModel().latestPatchNum$,
       x => (this.latestPatchNum = x)
     );
@@ -280,6 +288,8 @@
         fixSuggestion?.replacements?.[0].path ?? ''
       ),
       commentId: this.commentId ?? '',
+      success: res.ok,
+      status: res.status,
     });
     if (res?.ok) {
       this.getNavigation().setUrl(
diff --git a/polygerrit-ui/app/models/change/change-model.ts b/polygerrit-ui/app/models/change/change-model.ts
index 04fa0d6..dd292e6 100644
--- a/polygerrit-ui/app/models/change/change-model.ts
+++ b/polygerrit-ui/app/models/change/change-model.ts
@@ -353,6 +353,10 @@
 
   public readonly latestRevision$ = this.selectRevision(this.latestPatchNum$);
 
+  public readonly latestRevisionWithEdit$ = this.selectRevision(
+    this.latestPatchNumWithEdit$
+  );
+
   public readonly isOwner$: Observable<boolean> = select(
     combineLatest([this.change$, this.userModel.account$]),
     ([change, account]) => isOwner(change, account)
diff --git a/polygerrit-ui/app/models/checks/checks-fakes.ts b/polygerrit-ui/app/models/checks/checks-fakes.ts
index f6fe242..2b187f4 100644
--- a/polygerrit-ui/app/models/checks/checks-fakes.ts
+++ b/polygerrit-ui/app/models/checks/checks-fakes.ts
@@ -101,7 +101,7 @@
   checkName: 'FAKE Super Check',
   startedTimestamp: new Date(new Date().getTime() - 5 * 60 * 1000),
   finishedTimestamp: new Date(new Date().getTime() + 5 * 60 * 1000),
-  patchset: 3,
+  patchset: 1,
   labelName: 'Verified',
   isSingleAttempt: true,
   isLatestAttempt: true,