Merge "Permit cache.diff.intraline to disable MyersDiff"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 592dfb3..4d29c64 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -354,6 +354,20 @@
 
 See also link:cmd-flush-caches.html[gerrit flush-caches].
 
+[[cache_options]]Cache Options
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+cache.diff.intraline::
++
+Boolean to enable or disable the computation of intraline differences
+when populating a diff cache entry.  Changing this setting in the
+server configuration requires flushing the "diff" cache after a
+restart, otherwise older cache entries stored on disk may not reflect
+the current server setting.  This flag is provided primarily as a
+backdoor to disable the intraline difference feature if necessary.
++
+Default is true, enabled.
+
 
 [[commentlink]]Section commentlink
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java
index 8f9f1f5..2c1fd72 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java
@@ -50,12 +50,14 @@
   protected CommentDetail comments;
   protected List<Patch> history;
   protected boolean hugeFile;
+  protected boolean intralineDifference;
 
   public PatchScript(final Change.Key ck, final ChangeType ct, final String on,
       final String nn, final List<String> h, final PatchScriptSettings s,
       final SparseFileContent ca, final SparseFileContent cb,
       final List<Edit> e, final DisplayMethod ma, final DisplayMethod mb,
-      final CommentDetail cd, final List<Patch> hist, final boolean hf) {
+      final CommentDetail cd, final List<Patch> hist, final boolean hf,
+      final boolean id) {
     changeId = ck;
     changeType = ct;
     oldName = on;
@@ -70,6 +72,7 @@
     comments = cd;
     history = hist;
     hugeFile = hf;
+    intralineDifference = id;
   }
 
   protected PatchScript() {
@@ -127,6 +130,10 @@
     return settings.getWhitespace() != Whitespace.IGNORE_NONE;
   }
 
+  public boolean hasIntralineDifference() {
+    return intralineDifference;
+  }
+
   public SparseFileContent getA() {
     return a;
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java
index 4d70acc..dfcbb7e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java
@@ -448,6 +448,7 @@
     }
     showPatch(hasDifferences);
     settingsPanel.setEnableSmallFileFeatures(!script.isHugeFile());
+    settingsPanel.setEnableIntralineDifference(script.hasIntralineDifference());
     settingsPanel.setEnabled(true);
     lastScript = script;
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.java
index 1f5815a..c656390 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.java
@@ -54,6 +54,7 @@
   }
 
   private PatchScriptSettings value;
+  private boolean enableIntralineDifference = true;
   private boolean enableSmallFileFeatures = true;
 
   @UiField
@@ -144,11 +145,21 @@
     toggleEnabledStatus(update.isEnabled());
   }
 
-  private void toggleEnabledStatus(boolean on) {
-    on &= enableSmallFileFeatures;
+  public void setEnableIntralineDifference(final boolean on) {
+    enableIntralineDifference = on;
+    if (enableIntralineDifference) {
+      final PrettySettings p = getValue().getPrettySettings();
+      intralineDifference.setValue(p.isIntralineDifference());
+    } else {
+      intralineDifference.setValue(false);
+    }
+    toggleEnabledStatus(update.isEnabled());
+  }
 
-    syntaxHighlighting.setEnabled(on);
-    showFullFile.setEnabled(on);
+  private void toggleEnabledStatus(final boolean on) {
+    intralineDifference.setEnabled(on & enableIntralineDifference);
+    syntaxHighlighting.setEnabled(on & enableSmallFileFeatures);
+    showFullFile.setEnabled(on & enableSmallFileFeatures);
 
     final String title =
         enableSmallFileFeatures ? null : PatchUtil.C.disabledOnLargeFiles();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java
index a666a9d..9b42573 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java
@@ -74,6 +74,9 @@
     final SparseHtmlFile b = script.getSparseHtmlFileB();
     final ArrayList<PatchLine> lines = new ArrayList<PatchLine>();
     final SafeHtmlBuilder nc = new SafeHtmlBuilder();
+    final boolean intraline =
+        script.getSettings().getPrettySettings().isIntralineDifference()
+            && script.hasIntralineDifference();
 
     appendHeader(script, nc);
     lines.add(null);
@@ -105,8 +108,7 @@
           final boolean del = hunk.isDeletedA();
           final boolean ins = hunk.isInsertedB();
           final boolean full =
-              script.getSettings().getPrettySettings().isIntralineDifference()
-                  && hunk.getCurEdit().getType() != Edit.Type.REPLACE;
+              intraline && hunk.getCurEdit().getType() != Edit.Type.REPLACE;
           openLine(nc);
 
           if (del) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java
index d4cb8ab..bca7599 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java
@@ -109,8 +109,8 @@
   }
 
   PatchScript toPatchScript(final PatchListEntry content,
-      final CommentDetail comments, final List<Patch> history)
-      throws IOException {
+      final boolean intralineDifference, final CommentDetail comments,
+      final List<Patch> history) throws IOException {
     if (content.getPatchType() == PatchType.N_WAY) {
       // For a diff --cc format we don't support converting it into
       // a patch script. Instead treat everything as a file header.
@@ -118,7 +118,7 @@
       return new PatchScript(change.getKey(), content.getChangeType(), content
           .getOldName(), content.getNewName(), content.getHeaderLines(),
           settings, a.dst, b.dst, Collections.<Edit> emptyList(),
-          a.displayMethod, b.displayMethod, comments, history, false);
+          a.displayMethod, b.displayMethod, comments, history, false, false);
     }
 
     a.path = oldName(content);
@@ -168,7 +168,7 @@
     return new PatchScript(change.getKey(), content.getChangeType(), content
         .getOldName(), content.getNewName(), content.getHeaderLines(),
         settings, a.dst, b.dst, edits, a.displayMethod, b.displayMethod,
-        comments, history, hugeFile);
+        comments, history, hugeFile, intralineDifference);
   }
 
   private static String oldName(final PatchListEntry entry) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
index 5977fe0..bfc2074 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
@@ -144,6 +144,7 @@
     }
     try {
       final PatchList list = listFor(keyFor(settings.getWhitespace()));
+      final boolean intraline = list.hasIntralineDifference();
       final PatchScriptBuilder b = newBuilder(list, git);
       final PatchListEntry content = list.get(patchKey.getFileName());
 
@@ -152,7 +153,7 @@
           content.getNewName());
 
       try {
-        return b.toPatchScript(content, comments, history);
+        return b.toPatchScript(content, intraline, comments, history);
       } catch (IOException e) {
         log.error("File content unavailable", e);
         throw new NoSuchChangeException(changeId, e);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java
index dc600b7..a5121e9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java
@@ -59,12 +59,14 @@
   @Nullable
   private transient ObjectId oldId;
   private transient ObjectId newId;
+  private transient boolean intralineDifference;
   private transient PatchListEntry[] patches;
 
   PatchList(@Nullable final AnyObjectId oldId, final AnyObjectId newId,
-      final PatchListEntry[] patches) {
+      final boolean intralineDifference, final PatchListEntry[] patches) {
     this.oldId = oldId != null ? oldId.copy() : null;
     this.newId = newId.copy();
+    this.intralineDifference = intralineDifference;
 
     Arrays.sort(patches, PATCH_CMP);
     this.patches = patches;
@@ -86,6 +88,11 @@
     return Collections.unmodifiableList(Arrays.asList(patches));
   }
 
+  /** @return true if this list was computed with intraline difference enabled. */
+  public boolean hasIntralineDifference() {
+    return intralineDifference;
+  }
+
   /**
    * Get a sorted, modifiable list of all files in this list.
    * <p>
@@ -136,6 +143,7 @@
     try {
       writeCanBeNull(out, oldId);
       writeNotNull(out, newId);
+      writeVarInt32(out, intralineDifference ? 1 : 0);
       writeVarInt32(out, patches.length);
       for (PatchListEntry p : patches) {
         p.writeTo(out);
@@ -152,6 +160,7 @@
     try {
       oldId = readCanBeNull(in);
       newId = readNotNull(in);
+      intralineDifference = readVarInt32(in) != 0;
       final int cnt = readVarInt32(in);
       final PatchListEntry[] all = new PatchListEntry[cnt];
       for (int i = 0; i < all.length; i++) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
index 6bb6012..835db51 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
@@ -24,6 +24,7 @@
 import com.google.gerrit.server.cache.CacheModule;
 import com.google.gerrit.server.cache.EvictionPolicy;
 import com.google.gerrit.server.cache.SelfPopulatingCache;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.inject.Inject;
 import com.google.inject.Module;
@@ -37,6 +38,7 @@
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
@@ -86,11 +88,14 @@
 
   private final GitRepositoryManager repoManager;
   private final SelfPopulatingCache<PatchListKey, PatchList> self;
+  private final boolean computeIntraline;
 
   @Inject
   PatchListCacheImpl(final GitRepositoryManager grm,
+      @GerritServerConfig final Config config,
       @Named(CACHE_NAME) final Cache<PatchListKey, PatchList> raw) {
     repoManager = grm;
+    computeIntraline = config.getBoolean("cache", "diff", "intraline", true);
     self = new SelfPopulatingCache<PatchListKey, PatchList>(raw) {
       @Override
       protected PatchList createEntry(final PatchListKey key) throws Exception {
@@ -197,10 +202,10 @@
     for (int i = 0; i < cnt; i++) {
       entries[i] = newEntry(repo, aTree, bTree, p.getFiles().get(i));
     }
-    return new PatchList(a, b, entries);
+    return new PatchList(a, b, computeIntraline, entries);
   }
 
-  private static PatchListEntry newEntry(Repository repo, RevTree aTree,
+  private PatchListEntry newEntry(Repository repo, RevTree aTree,
       RevTree bTree, FileHeader fileHeader) throws IOException {
     final FileMode oldMode = fileHeader.getOldMode();
     final FileMode newMode = fileHeader.getNewMode();
@@ -219,6 +224,9 @@
     if (edits.isEmpty()) {
       return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
     }
+    if (!computeIntraline) {
+      return new PatchListEntry(fileHeader, edits);
+    }
 
     switch (fileHeader.getChangeType()) {
       case ADD:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java
index 754992b..1644b16 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java
@@ -35,7 +35,7 @@
 import javax.annotation.Nullable;
 
 public class PatchListKey implements Serializable {
-  static final long serialVersionUID = 12L;
+  static final long serialVersionUID = 13L;
 
   private transient ObjectId oldId;
   private transient ObjectId newId;