Extract DiffInfoCreator from GetDiff class
Extract DiffInfoCreator for reuse in robot's preview fix. Change-Id: I32e6f41007a38e0d758b209046cbd45c8cf6b49d
This commit is contained in:
		@@ -7,5 +7,7 @@ java_library(
 | 
			
		||||
    deps = [
 | 
			
		||||
        "//lib:guava",
 | 
			
		||||
        "//lib:jgit",
 | 
			
		||||
        "//lib/auto:auto-value",
 | 
			
		||||
        "//lib/auto:auto-value-annotations",
 | 
			
		||||
    ],
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -14,160 +14,175 @@
 | 
			
		||||
 | 
			
		||||
package com.google.gerrit.prettify.common;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import com.google.auto.value.AutoValue;
 | 
			
		||||
import com.google.common.annotations.VisibleForTesting;
 | 
			
		||||
import com.google.common.collect.ImmutableList;
 | 
			
		||||
 | 
			
		||||
public class SparseFileContent {
 | 
			
		||||
  protected List<Range> ranges;
 | 
			
		||||
  protected int size;
 | 
			
		||||
/**
 | 
			
		||||
 * A class to store subset of a file's lines in a memory efficient way. Internally, it stores lines
 | 
			
		||||
 * as a list of ranges. Each range represents continuous set of lines and has information about line
 | 
			
		||||
 * numbers in original file (zero-based).
 | 
			
		||||
 *
 | 
			
		||||
 * <p>{@link SparseFileContent.Accessor} must be used to work with the stored content.
 | 
			
		||||
 */
 | 
			
		||||
@AutoValue
 | 
			
		||||
public abstract class SparseFileContent {
 | 
			
		||||
  abstract ImmutableList<Range> getRanges();
 | 
			
		||||
 | 
			
		||||
  private transient int currentRangeIdx;
 | 
			
		||||
  public abstract int getSize();
 | 
			
		||||
 | 
			
		||||
  public SparseFileContent() {
 | 
			
		||||
    ranges = new ArrayList<>();
 | 
			
		||||
  public static SparseFileContent create(ImmutableList<Range> ranges, int size) {
 | 
			
		||||
    return new AutoValue_SparseFileContent(ranges, size);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public int size() {
 | 
			
		||||
    return size;
 | 
			
		||||
  @VisibleForTesting
 | 
			
		||||
  public int getRangesCount() {
 | 
			
		||||
    return getRanges().size();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setSize(int s) {
 | 
			
		||||
    size = s;
 | 
			
		||||
  public Accessor createAccessor() {
 | 
			
		||||
    return new Accessor(this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public String get(int idx) {
 | 
			
		||||
    final String line = getLine(idx);
 | 
			
		||||
    if (line == null) {
 | 
			
		||||
      throw new ArrayIndexOutOfBoundsException(idx);
 | 
			
		||||
    }
 | 
			
		||||
    return line;
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
   * Provide a methods to work with the content of a {@link SparseFileContent}.
 | 
			
		||||
   *
 | 
			
		||||
   * <p>The class hides internal representation of a {@link SparseFileContent} and provides
 | 
			
		||||
   * convenient way for accessing a content.
 | 
			
		||||
   */
 | 
			
		||||
  public static class Accessor {
 | 
			
		||||
    private final SparseFileContent content;
 | 
			
		||||
    private int currentRangeIdx;
 | 
			
		||||
 | 
			
		||||
  public boolean contains(int idx) {
 | 
			
		||||
    return getLine(idx) != null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public int first() {
 | 
			
		||||
    return ranges.isEmpty() ? size() : ranges.get(0).base;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public int next(int idx) {
 | 
			
		||||
    // Most requests are sequential in nature, fetching the next
 | 
			
		||||
    // line from the current range, or the immediate next range.
 | 
			
		||||
    //
 | 
			
		||||
    int high = ranges.size();
 | 
			
		||||
    if (currentRangeIdx < high) {
 | 
			
		||||
      Range cur = ranges.get(currentRangeIdx);
 | 
			
		||||
      if (cur.contains(idx + 1)) {
 | 
			
		||||
        return idx + 1;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (++currentRangeIdx < high) {
 | 
			
		||||
        // Its not plus one, its the base of the next range.
 | 
			
		||||
        //
 | 
			
		||||
        return ranges.get(currentRangeIdx).base;
 | 
			
		||||
      }
 | 
			
		||||
    private Accessor(SparseFileContent content) {
 | 
			
		||||
      this.content = content;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Binary search for the current value, since we know its a sorted list.
 | 
			
		||||
    //
 | 
			
		||||
    int low = 0;
 | 
			
		||||
    do {
 | 
			
		||||
      final int mid = (low + high) / 2;
 | 
			
		||||
      final Range cur = ranges.get(mid);
 | 
			
		||||
    public String get(int idx) {
 | 
			
		||||
      final String line = getLine(idx);
 | 
			
		||||
      if (line == null) {
 | 
			
		||||
        throw new ArrayIndexOutOfBoundsException(idx);
 | 
			
		||||
      }
 | 
			
		||||
      return line;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
      if (cur.contains(idx)) {
 | 
			
		||||
    public int getSize() {
 | 
			
		||||
      return content.getSize();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean contains(int idx) {
 | 
			
		||||
      return getLine(idx) != null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int first() {
 | 
			
		||||
      return content.getRanges().isEmpty() ? getSize() : content.getRanges().get(0).getBase();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int next(int idx) {
 | 
			
		||||
      // Most requests are sequential in nature, fetching the next
 | 
			
		||||
      // line from the current range, or the immediate next range.
 | 
			
		||||
      //
 | 
			
		||||
      ImmutableList<Range> ranges = content.getRanges();
 | 
			
		||||
      int high = ranges.size();
 | 
			
		||||
      if (currentRangeIdx < high) {
 | 
			
		||||
        Range cur = ranges.get(currentRangeIdx);
 | 
			
		||||
        if (cur.contains(idx + 1)) {
 | 
			
		||||
          // Trivial plus one case above failed due to wrong currentRangeIdx.
 | 
			
		||||
          // Reset the cache so we don't miss in the future.
 | 
			
		||||
          //
 | 
			
		||||
          currentRangeIdx = mid;
 | 
			
		||||
          return idx + 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (mid + 1 < ranges.size()) {
 | 
			
		||||
          // Its the base of the next range.
 | 
			
		||||
          currentRangeIdx = mid + 1;
 | 
			
		||||
          return ranges.get(currentRangeIdx).base;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // No more lines in the file.
 | 
			
		||||
        //
 | 
			
		||||
        return size();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (idx < cur.base) {
 | 
			
		||||
        high = mid;
 | 
			
		||||
      } else {
 | 
			
		||||
        low = mid + 1;
 | 
			
		||||
      }
 | 
			
		||||
    } while (low < high);
 | 
			
		||||
 | 
			
		||||
    return size();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private String getLine(int idx) {
 | 
			
		||||
    // Most requests are sequential in nature, fetching the next
 | 
			
		||||
    // line from the current range, or the next range.
 | 
			
		||||
    //
 | 
			
		||||
    int high = ranges.size();
 | 
			
		||||
    if (currentRangeIdx < high) {
 | 
			
		||||
      Range cur = ranges.get(currentRangeIdx);
 | 
			
		||||
      if (cur.contains(idx)) {
 | 
			
		||||
        return cur.get(idx);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (++currentRangeIdx < high) {
 | 
			
		||||
        final Range next = ranges.get(currentRangeIdx);
 | 
			
		||||
        if (next.contains(idx)) {
 | 
			
		||||
          return next.get(idx);
 | 
			
		||||
        if (++currentRangeIdx < high) {
 | 
			
		||||
          // Its not plus one, its the base of the next range.
 | 
			
		||||
          //
 | 
			
		||||
          return ranges.get(currentRangeIdx).getBase();
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Binary search for the current value, since we know its a sorted list.
 | 
			
		||||
      //
 | 
			
		||||
      int low = 0;
 | 
			
		||||
      do {
 | 
			
		||||
        final int mid = (low + high) / 2;
 | 
			
		||||
        final Range cur = ranges.get(mid);
 | 
			
		||||
 | 
			
		||||
        if (cur.contains(idx)) {
 | 
			
		||||
          if (cur.contains(idx + 1)) {
 | 
			
		||||
            // Trivial plus one case above failed due to wrong currentRangeIdx.
 | 
			
		||||
            // Reset the cache so we don't miss in the future.
 | 
			
		||||
            //
 | 
			
		||||
            currentRangeIdx = mid;
 | 
			
		||||
            return idx + 1;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (mid + 1 < ranges.size()) {
 | 
			
		||||
            // Its the base of the next range.
 | 
			
		||||
            currentRangeIdx = mid + 1;
 | 
			
		||||
            return ranges.get(currentRangeIdx).getBase();
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          // No more lines in the file.
 | 
			
		||||
          //
 | 
			
		||||
          return getSize();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (idx < cur.getBase()) {
 | 
			
		||||
          high = mid;
 | 
			
		||||
        } else {
 | 
			
		||||
          low = mid + 1;
 | 
			
		||||
        }
 | 
			
		||||
      } while (low < high);
 | 
			
		||||
 | 
			
		||||
      return getSize();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Binary search for the range, since we know its a sorted list.
 | 
			
		||||
    //
 | 
			
		||||
    if (ranges.isEmpty()) {
 | 
			
		||||
    private String getLine(int idx) {
 | 
			
		||||
      // Most requests are sequential in nature, fetching the next
 | 
			
		||||
      // line from the current range, or the next range.
 | 
			
		||||
      //
 | 
			
		||||
      ImmutableList<Range> ranges = content.getRanges();
 | 
			
		||||
      int high = ranges.size();
 | 
			
		||||
      if (currentRangeIdx < high) {
 | 
			
		||||
        Range cur = ranges.get(currentRangeIdx);
 | 
			
		||||
        if (cur.contains(idx)) {
 | 
			
		||||
          return cur.get(idx);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (++currentRangeIdx < high) {
 | 
			
		||||
          final Range next = ranges.get(currentRangeIdx);
 | 
			
		||||
          if (next.contains(idx)) {
 | 
			
		||||
            return next.get(idx);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Binary search for the range, since we know its a sorted list.
 | 
			
		||||
      //
 | 
			
		||||
      if (ranges.isEmpty()) {
 | 
			
		||||
        return null;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      int low = 0;
 | 
			
		||||
      do {
 | 
			
		||||
        final int mid = (low + high) / 2;
 | 
			
		||||
        final Range cur = ranges.get(mid);
 | 
			
		||||
        if (cur.contains(idx)) {
 | 
			
		||||
          currentRangeIdx = mid;
 | 
			
		||||
          return cur.get(idx);
 | 
			
		||||
        }
 | 
			
		||||
        if (idx < cur.getBase()) {
 | 
			
		||||
          high = mid;
 | 
			
		||||
        } else {
 | 
			
		||||
          low = mid + 1;
 | 
			
		||||
        }
 | 
			
		||||
      } while (low < high);
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int low = 0;
 | 
			
		||||
    do {
 | 
			
		||||
      final int mid = (low + high) / 2;
 | 
			
		||||
      final Range cur = ranges.get(mid);
 | 
			
		||||
      if (cur.contains(idx)) {
 | 
			
		||||
        currentRangeIdx = mid;
 | 
			
		||||
        return cur.get(idx);
 | 
			
		||||
      }
 | 
			
		||||
      if (idx < cur.base) {
 | 
			
		||||
        high = mid;
 | 
			
		||||
      } else {
 | 
			
		||||
        low = mid + 1;
 | 
			
		||||
      }
 | 
			
		||||
    } while (low < high);
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void addLine(int i, String content) {
 | 
			
		||||
    final Range r;
 | 
			
		||||
    if (!ranges.isEmpty() && i == last().end()) {
 | 
			
		||||
      r = last();
 | 
			
		||||
    } else {
 | 
			
		||||
      r = new Range(i);
 | 
			
		||||
      ranges.add(r);
 | 
			
		||||
    }
 | 
			
		||||
    r.lines.add(content);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private Range last() {
 | 
			
		||||
    return ranges.get(ranges.size() - 1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public String toString() {
 | 
			
		||||
  public final String toString() {
 | 
			
		||||
    final StringBuilder b = new StringBuilder();
 | 
			
		||||
    b.append("SparseFileContent[\n");
 | 
			
		||||
    for (Range r : ranges) {
 | 
			
		||||
    for (Range r : getRanges()) {
 | 
			
		||||
      b.append("  ");
 | 
			
		||||
      b.append(r.toString());
 | 
			
		||||
      b.append('\n');
 | 
			
		||||
@@ -176,33 +191,32 @@ public class SparseFileContent {
 | 
			
		||||
    return b.toString();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static class Range {
 | 
			
		||||
    protected int base;
 | 
			
		||||
    protected List<String> lines;
 | 
			
		||||
 | 
			
		||||
    private Range(int b) {
 | 
			
		||||
      base = b;
 | 
			
		||||
      lines = new ArrayList<>();
 | 
			
		||||
  @AutoValue
 | 
			
		||||
  abstract static class Range {
 | 
			
		||||
    static Range create(int base, ImmutableList<String> lines) {
 | 
			
		||||
      return new AutoValue_SparseFileContent_Range(base, lines);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected Range() {}
 | 
			
		||||
    abstract int getBase();
 | 
			
		||||
 | 
			
		||||
    abstract ImmutableList<String> getLines();
 | 
			
		||||
 | 
			
		||||
    private String get(int i) {
 | 
			
		||||
      return lines.get(i - base);
 | 
			
		||||
      return getLines().get(i - getBase());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private int end() {
 | 
			
		||||
      return base + lines.size();
 | 
			
		||||
      return getBase() + getLines().size();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean contains(int i) {
 | 
			
		||||
      return base <= i && i < end();
 | 
			
		||||
      return getBase() <= i && i < end();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
    public final String toString() {
 | 
			
		||||
      // Usage of [ and ) is intentional to denote inclusive/exclusive range
 | 
			
		||||
      return "Range[" + base + "," + end() + ")";
 | 
			
		||||
      return "Range[" + getBase() + "," + end() + ")";
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,90 @@
 | 
			
		||||
// 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.prettify.common;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.ImmutableList;
 | 
			
		||||
import com.google.gerrit.prettify.common.SparseFileContent.Range;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A builder for creating immutable {@link SparseFileContent}. Lines can be only be added in
 | 
			
		||||
 * sequential (increased) order
 | 
			
		||||
 */
 | 
			
		||||
public class SparseFileContentBuilder {
 | 
			
		||||
  private final ImmutableList.Builder<Range> ranges;
 | 
			
		||||
  private final int size;
 | 
			
		||||
  private int lastRangeBase;
 | 
			
		||||
  private int lastRangeEnd;
 | 
			
		||||
  private ImmutableList.Builder<String> lastRangeLines;
 | 
			
		||||
 | 
			
		||||
  public SparseFileContentBuilder(int size) {
 | 
			
		||||
    ranges = new ImmutableList.Builder<>();
 | 
			
		||||
    startNextRange(0);
 | 
			
		||||
    this.size = size;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void addLine(int lineNumber, String content) {
 | 
			
		||||
    if (lineNumber < 0) {
 | 
			
		||||
      throw new IllegalArgumentException("Line number must be non-negative");
 | 
			
		||||
    }
 | 
			
		||||
    //    if (lineNumber >= size) {
 | 
			
		||||
    //     The following 4 tests are failed if you uncomment this condition:
 | 
			
		||||
    //
 | 
			
		||||
    //
 | 
			
		||||
    // diffOfFileWithMultilineRebaseHunkRemovingNewlineAtEndOfFileAndWithCommentReturnsFileContents
 | 
			
		||||
    //
 | 
			
		||||
    // diffOfFileWithMultilineRebaseHunkAddingNewlineAtEndOfFileAndWithCommentReturnsFileContents
 | 
			
		||||
    //
 | 
			
		||||
    //
 | 
			
		||||
    // diffOfFileWithMultilineRebaseHunkRemovingNewlineAtEndOfFileAndWithCommentReturnsFileContents
 | 
			
		||||
    //
 | 
			
		||||
    // diffOfFileWithMultilineRebaseHunkAddingNewlineAtEndOfFileAndWithCommentReturnsFileContents
 | 
			
		||||
    //     Tests are failed because there are some bug with diff calculation.
 | 
			
		||||
    //     The condition must be uncommented after all these bugs are fixed.
 | 
			
		||||
    //     Also don't forget to remove ignore from for SparseFileContentBuilder
 | 
			
		||||
    //      throw new IllegalArgumentException(String.format("The zero-based line number %d is after
 | 
			
		||||
    // the end of file. The file size is %d line(s).", lineNumber, size));
 | 
			
		||||
    //    }
 | 
			
		||||
    if (lineNumber < lastRangeEnd) {
 | 
			
		||||
      throw new IllegalArgumentException(
 | 
			
		||||
          String.format(
 | 
			
		||||
              "Invalid line number %d. You are trying to add a line before an already added line"
 | 
			
		||||
                  + " %d",
 | 
			
		||||
              lineNumber, lastRangeEnd));
 | 
			
		||||
    }
 | 
			
		||||
    if (lineNumber > lastRangeEnd) {
 | 
			
		||||
      finishLastRange();
 | 
			
		||||
      startNextRange(lineNumber);
 | 
			
		||||
    }
 | 
			
		||||
    lastRangeLines.add(content);
 | 
			
		||||
    lastRangeEnd++;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void startNextRange(int base) {
 | 
			
		||||
    lastRangeLines = new ImmutableList.Builder<>();
 | 
			
		||||
    lastRangeBase = lastRangeEnd = base;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void finishLastRange() {
 | 
			
		||||
    if (lastRangeEnd > lastRangeBase) {
 | 
			
		||||
      ranges.add(Range.create(lastRangeBase, lastRangeLines.build()));
 | 
			
		||||
      lastRangeLines = null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public SparseFileContent build() {
 | 
			
		||||
    finishLastRange();
 | 
			
		||||
    return SparseFileContent.create(ranges.build(), size);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								java/com/google/gerrit/prettify/common/testing/BUILD
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								java/com/google/gerrit/prettify/common/testing/BUILD
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
load("@rules_java//java:defs.bzl", "java_library")
 | 
			
		||||
 | 
			
		||||
package(default_testonly = True)
 | 
			
		||||
 | 
			
		||||
java_library(
 | 
			
		||||
    name = "testing",
 | 
			
		||||
    srcs = glob(["*.java"]),
 | 
			
		||||
    visibility = ["//visibility:public"],
 | 
			
		||||
    deps = [
 | 
			
		||||
        "//java/com/google/gerrit/prettify:server",
 | 
			
		||||
        "//lib:guava",
 | 
			
		||||
        "//lib/truth",
 | 
			
		||||
    ],
 | 
			
		||||
)
 | 
			
		||||
@@ -0,0 +1,65 @@
 | 
			
		||||
// 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.prettify.common.testing;
 | 
			
		||||
 | 
			
		||||
import static com.google.common.truth.Truth.assertAbout;
 | 
			
		||||
 | 
			
		||||
import com.google.common.truth.FailureMetadata;
 | 
			
		||||
import com.google.common.truth.IntegerSubject;
 | 
			
		||||
import com.google.common.truth.MapSubject;
 | 
			
		||||
import com.google.common.truth.Subject;
 | 
			
		||||
import com.google.gerrit.prettify.common.SparseFileContent;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
public class SparseFileContentSubject extends Subject {
 | 
			
		||||
  public static SparseFileContentSubject assertThat(SparseFileContent sparseFileContent) {
 | 
			
		||||
    return assertAbout(sparseFileContent()).that(sparseFileContent);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private final SparseFileContent sparseFileContent;
 | 
			
		||||
 | 
			
		||||
  private SparseFileContentSubject(FailureMetadata metadata, SparseFileContent actual) {
 | 
			
		||||
    super(metadata, actual);
 | 
			
		||||
    this.sparseFileContent = actual;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static Subject.Factory<SparseFileContentSubject, SparseFileContent> sparseFileContent() {
 | 
			
		||||
    return SparseFileContentSubject::new;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public IntegerSubject getSize() {
 | 
			
		||||
    isNotNull();
 | 
			
		||||
    return check("size()").that(sparseFileContent.getSize());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public IntegerSubject getRangesCount() {
 | 
			
		||||
    isNotNull();
 | 
			
		||||
    return check("rangesCount()").that(sparseFileContent.getRangesCount());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public MapSubject lines() {
 | 
			
		||||
    isNotNull();
 | 
			
		||||
    Map<Integer, String> lines = new HashMap<>();
 | 
			
		||||
    SparseFileContent.Accessor accessor = sparseFileContent.createAccessor();
 | 
			
		||||
    int size = accessor.getSize();
 | 
			
		||||
    int idx = accessor.first();
 | 
			
		||||
    while (idx < size) {
 | 
			
		||||
      lines.put(idx, accessor.get(idx));
 | 
			
		||||
      idx = accessor.next(idx);
 | 
			
		||||
    }
 | 
			
		||||
    return check("lines()").that(lines);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										299
									
								
								java/com/google/gerrit/server/diff/DiffInfoCreator.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										299
									
								
								java/com/google/gerrit/server/diff/DiffInfoCreator.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,299 @@
 | 
			
		||||
// 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.diff;
 | 
			
		||||
 | 
			
		||||
import static com.google.common.base.Preconditions.checkState;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.ImmutableList;
 | 
			
		||||
import com.google.common.collect.ImmutableMap;
 | 
			
		||||
import com.google.common.collect.Lists;
 | 
			
		||||
import com.google.common.collect.Maps;
 | 
			
		||||
import com.google.common.flogger.FluentLogger;
 | 
			
		||||
import com.google.gerrit.common.data.PatchScript;
 | 
			
		||||
import com.google.gerrit.common.data.PatchScript.DisplayMethod;
 | 
			
		||||
import com.google.gerrit.common.data.PatchScript.PatchScriptFileInfo;
 | 
			
		||||
import com.google.gerrit.entities.Patch;
 | 
			
		||||
import com.google.gerrit.extensions.common.ChangeType;
 | 
			
		||||
import com.google.gerrit.extensions.common.DiffInfo;
 | 
			
		||||
import com.google.gerrit.extensions.common.DiffInfo.ContentEntry;
 | 
			
		||||
import com.google.gerrit.extensions.common.DiffInfo.FileMeta;
 | 
			
		||||
import com.google.gerrit.extensions.common.DiffInfo.IntraLineStatus;
 | 
			
		||||
import com.google.gerrit.extensions.common.DiffWebLinkInfo;
 | 
			
		||||
import com.google.gerrit.extensions.common.WebLinkInfo;
 | 
			
		||||
import com.google.gerrit.jgit.diff.ReplaceEdit;
 | 
			
		||||
import com.google.gerrit.prettify.common.SparseFileContent;
 | 
			
		||||
import com.google.gerrit.server.change.FileContentUtil;
 | 
			
		||||
import com.google.gerrit.server.project.ProjectState;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import org.eclipse.jgit.diff.Edit;
 | 
			
		||||
 | 
			
		||||
/** Creates and fills a new {@link DiffInfo} object based on diff between files. */
 | 
			
		||||
public class DiffInfoCreator {
 | 
			
		||||
  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 | 
			
		||||
 | 
			
		||||
  private static final ImmutableMap<Patch.ChangeType, ChangeType> CHANGE_TYPE =
 | 
			
		||||
      Maps.immutableEnumMap(
 | 
			
		||||
          new ImmutableMap.Builder<Patch.ChangeType, ChangeType>()
 | 
			
		||||
              .put(Patch.ChangeType.ADDED, ChangeType.ADDED)
 | 
			
		||||
              .put(Patch.ChangeType.MODIFIED, ChangeType.MODIFIED)
 | 
			
		||||
              .put(Patch.ChangeType.DELETED, ChangeType.DELETED)
 | 
			
		||||
              .put(Patch.ChangeType.RENAMED, ChangeType.RENAMED)
 | 
			
		||||
              .put(Patch.ChangeType.COPIED, ChangeType.COPIED)
 | 
			
		||||
              .put(Patch.ChangeType.REWRITE, ChangeType.REWRITE)
 | 
			
		||||
              .build());
 | 
			
		||||
 | 
			
		||||
  private final DiffWebLinksProvider webLinksProvider;
 | 
			
		||||
  private final boolean intraline;
 | 
			
		||||
  private final ProjectState state;
 | 
			
		||||
 | 
			
		||||
  public DiffInfoCreator(
 | 
			
		||||
      ProjectState state, DiffWebLinksProvider webLinksProvider, boolean intraline) {
 | 
			
		||||
    this.webLinksProvider = webLinksProvider;
 | 
			
		||||
    this.state = state;
 | 
			
		||||
    this.intraline = intraline;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* Returns the {@link DiffInfo} to display for end-users */
 | 
			
		||||
  public DiffInfo create(PatchScript ps, DiffSide sideA, DiffSide sideB) {
 | 
			
		||||
    DiffInfo result = new DiffInfo();
 | 
			
		||||
 | 
			
		||||
    ImmutableList<DiffWebLinkInfo> links = webLinksProvider.getDiffLinks();
 | 
			
		||||
    result.webLinks = links.isEmpty() ? null : links;
 | 
			
		||||
 | 
			
		||||
    if (ps.isBinary()) {
 | 
			
		||||
      result.binary = true;
 | 
			
		||||
    }
 | 
			
		||||
    result.metaA = createFileMeta(sideA).orElse(null);
 | 
			
		||||
    result.metaB = createFileMeta(sideB).orElse(null);
 | 
			
		||||
 | 
			
		||||
    if (intraline) {
 | 
			
		||||
      if (ps.hasIntralineTimeout()) {
 | 
			
		||||
        result.intralineStatus = IntraLineStatus.TIMEOUT;
 | 
			
		||||
      } else if (ps.hasIntralineFailure()) {
 | 
			
		||||
        result.intralineStatus = IntraLineStatus.FAILURE;
 | 
			
		||||
      } else {
 | 
			
		||||
        result.intralineStatus = IntraLineStatus.OK;
 | 
			
		||||
      }
 | 
			
		||||
      logger.atFine().log("intralineStatus = %s", result.intralineStatus);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    result.changeType = CHANGE_TYPE.get(ps.getChangeType());
 | 
			
		||||
    logger.atFine().log("changeType = %s", result.changeType);
 | 
			
		||||
    if (result.changeType == null) {
 | 
			
		||||
      throw new IllegalStateException("unknown change type: " + ps.getChangeType());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (ps.getPatchHeader().size() > 0) {
 | 
			
		||||
      result.diffHeader = ps.getPatchHeader();
 | 
			
		||||
    }
 | 
			
		||||
    result.content = calculateDiffContentEntries(ps);
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static List<ContentEntry> calculateDiffContentEntries(PatchScript ps) {
 | 
			
		||||
    ContentCollector contentCollector = new ContentCollector(ps);
 | 
			
		||||
    Set<Edit> editsDueToRebase = ps.getEditsDueToRebase();
 | 
			
		||||
    for (Edit edit : ps.getEdits()) {
 | 
			
		||||
      logger.atFine().log("next edit = %s", edit);
 | 
			
		||||
 | 
			
		||||
      if (edit.getType() == Edit.Type.EMPTY) {
 | 
			
		||||
        logger.atFine().log("skip empty edit");
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      contentCollector.addCommon(edit.getBeginA());
 | 
			
		||||
 | 
			
		||||
      checkState(
 | 
			
		||||
          contentCollector.nextA == edit.getBeginA(),
 | 
			
		||||
          "nextA = %s; want %s",
 | 
			
		||||
          contentCollector.nextA,
 | 
			
		||||
          edit.getBeginA());
 | 
			
		||||
      checkState(
 | 
			
		||||
          contentCollector.nextB == edit.getBeginB(),
 | 
			
		||||
          "nextB = %s; want %s",
 | 
			
		||||
          contentCollector.nextB,
 | 
			
		||||
          edit.getBeginB());
 | 
			
		||||
      switch (edit.getType()) {
 | 
			
		||||
        case DELETE:
 | 
			
		||||
        case INSERT:
 | 
			
		||||
        case REPLACE:
 | 
			
		||||
          List<Edit> internalEdit =
 | 
			
		||||
              edit instanceof ReplaceEdit ? ((ReplaceEdit) edit).getInternalEdits() : null;
 | 
			
		||||
          boolean dueToRebase = editsDueToRebase.contains(edit);
 | 
			
		||||
          contentCollector.addDiff(edit.getEndA(), edit.getEndB(), internalEdit, dueToRebase);
 | 
			
		||||
          break;
 | 
			
		||||
        case EMPTY:
 | 
			
		||||
        default:
 | 
			
		||||
          throw new IllegalStateException();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    contentCollector.addCommon(ps.getA().getSize());
 | 
			
		||||
 | 
			
		||||
    return contentCollector.lines;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private Optional<FileMeta> createFileMeta(DiffSide side) {
 | 
			
		||||
    PatchScriptFileInfo fileInfo = side.fileInfo();
 | 
			
		||||
    if (fileInfo.displayMethod == DisplayMethod.NONE) {
 | 
			
		||||
      return Optional.empty();
 | 
			
		||||
    }
 | 
			
		||||
    FileMeta result = new FileMeta();
 | 
			
		||||
    result.name = side.fileName();
 | 
			
		||||
    result.contentType =
 | 
			
		||||
        FileContentUtil.resolveContentType(
 | 
			
		||||
            state, side.fileName(), fileInfo.mode, fileInfo.mimeType);
 | 
			
		||||
    result.lines = fileInfo.content.getSize();
 | 
			
		||||
    ImmutableList<WebLinkInfo> links = webLinksProvider.getFileWebLinks(side.type());
 | 
			
		||||
    result.webLinks = links.isEmpty() ? null : links;
 | 
			
		||||
    result.commitId = fileInfo.commitId;
 | 
			
		||||
    return Optional.of(result);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static class ContentCollector {
 | 
			
		||||
 | 
			
		||||
    private final List<ContentEntry> lines;
 | 
			
		||||
    private final SparseFileContent.Accessor fileA;
 | 
			
		||||
    private final SparseFileContent.Accessor fileB;
 | 
			
		||||
    private final boolean ignoreWS;
 | 
			
		||||
 | 
			
		||||
    private int nextA;
 | 
			
		||||
    private int nextB;
 | 
			
		||||
 | 
			
		||||
    ContentCollector(PatchScript ps) {
 | 
			
		||||
      lines = Lists.newArrayListWithExpectedSize(ps.getEdits().size() + 2);
 | 
			
		||||
      fileA = ps.getA().createAccessor();
 | 
			
		||||
      fileB = ps.getB().createAccessor();
 | 
			
		||||
      ignoreWS = ps.isIgnoreWhitespace();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void addCommon(int end) {
 | 
			
		||||
      logger.atFine().log("addCommon: end = %d", end);
 | 
			
		||||
 | 
			
		||||
      end = Math.min(end, fileA.getSize());
 | 
			
		||||
      logger.atFine().log("end = %d", end);
 | 
			
		||||
 | 
			
		||||
      if (nextA >= end) {
 | 
			
		||||
        logger.atFine().log("nextA >= end: nextA = %d, end = %d", nextA, end);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      while (nextA < end) {
 | 
			
		||||
        logger.atFine().log("nextA < end: nextA = %d, end = %d", nextA, end);
 | 
			
		||||
 | 
			
		||||
        if (!fileA.contains(nextA)) {
 | 
			
		||||
          logger.atFine().log("fileA does not contain nextA: nextA = %d", nextA);
 | 
			
		||||
 | 
			
		||||
          int endRegion = Math.min(end, nextA == 0 ? fileA.first() : fileA.next(nextA - 1));
 | 
			
		||||
          int len = endRegion - nextA;
 | 
			
		||||
          entry().skip = len;
 | 
			
		||||
          nextA = endRegion;
 | 
			
		||||
          nextB += len;
 | 
			
		||||
 | 
			
		||||
          logger.atFine().log("setting: nextA = %d, nextB = %d", nextA, nextB);
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ContentEntry e = null;
 | 
			
		||||
        for (int i = nextA; i == nextA && i < end; i = fileA.next(i), nextA++, nextB++) {
 | 
			
		||||
          if (ignoreWS && fileB.contains(nextB)) {
 | 
			
		||||
            if (e == null || e.common == null) {
 | 
			
		||||
              logger.atFine().log("create new common entry: nextA = %d, nextB = %d", nextA, nextB);
 | 
			
		||||
              e = entry();
 | 
			
		||||
              e.a = Lists.newArrayListWithCapacity(end - nextA);
 | 
			
		||||
              e.b = Lists.newArrayListWithCapacity(end - nextA);
 | 
			
		||||
              e.common = true;
 | 
			
		||||
            }
 | 
			
		||||
            e.a.add(fileA.get(nextA));
 | 
			
		||||
            e.b.add(fileB.get(nextB));
 | 
			
		||||
          } else {
 | 
			
		||||
            if (e == null || e.common != null) {
 | 
			
		||||
              logger.atFine().log(
 | 
			
		||||
                  "create new non-common entry: nextA = %d, nextB = %d", nextA, nextB);
 | 
			
		||||
              e = entry();
 | 
			
		||||
              e.ab = Lists.newArrayListWithCapacity(end - nextA);
 | 
			
		||||
            }
 | 
			
		||||
            e.ab.add(fileA.get(nextA));
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void addDiff(int endA, int endB, List<Edit> internalEdit, boolean dueToRebase) {
 | 
			
		||||
      logger.atFine().log(
 | 
			
		||||
          "addDiff: endA = %d, endB = %d, numberOfInternalEdits = %d, dueToRebase = %s",
 | 
			
		||||
          endA, endB, internalEdit != null ? internalEdit.size() : 0, dueToRebase);
 | 
			
		||||
 | 
			
		||||
      int lenA = endA - nextA;
 | 
			
		||||
      int lenB = endB - nextB;
 | 
			
		||||
      logger.atFine().log("lenA = %d, lenB = %d", lenA, lenB);
 | 
			
		||||
      checkState(lenA > 0 || lenB > 0);
 | 
			
		||||
 | 
			
		||||
      logger.atFine().log("create non-common entry");
 | 
			
		||||
      ContentEntry e = entry();
 | 
			
		||||
      if (lenA > 0) {
 | 
			
		||||
        logger.atFine().log("lenA > 0: lenA = %d", lenA);
 | 
			
		||||
        e.a = Lists.newArrayListWithCapacity(lenA);
 | 
			
		||||
        for (; nextA < endA; nextA++) {
 | 
			
		||||
          e.a.add(fileA.get(nextA));
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (lenB > 0) {
 | 
			
		||||
        logger.atFine().log("lenB > 0: lenB = %d", lenB);
 | 
			
		||||
        e.b = Lists.newArrayListWithCapacity(lenB);
 | 
			
		||||
        for (; nextB < endB; nextB++) {
 | 
			
		||||
          e.b.add(fileB.get(nextB));
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (internalEdit != null && !internalEdit.isEmpty()) {
 | 
			
		||||
        logger.atFine().log("processing internal edits");
 | 
			
		||||
 | 
			
		||||
        e.editA = Lists.newArrayListWithCapacity(internalEdit.size() * 2);
 | 
			
		||||
        e.editB = Lists.newArrayListWithCapacity(internalEdit.size() * 2);
 | 
			
		||||
        int lastA = 0;
 | 
			
		||||
        int lastB = 0;
 | 
			
		||||
        for (Edit edit : internalEdit) {
 | 
			
		||||
          logger.atFine().log("internal edit = %s", edit);
 | 
			
		||||
 | 
			
		||||
          if (edit.getBeginA() != edit.getEndA()) {
 | 
			
		||||
            logger.atFine().log(
 | 
			
		||||
                "edit.getBeginA() != edit.getEndA(): edit.getBeginA() = %d, edit.getEndA() = %d",
 | 
			
		||||
                edit.getBeginA(), edit.getEndA());
 | 
			
		||||
            e.editA.add(
 | 
			
		||||
                ImmutableList.of(edit.getBeginA() - lastA, edit.getEndA() - edit.getBeginA()));
 | 
			
		||||
            lastA = edit.getEndA();
 | 
			
		||||
            logger.atFine().log("lastA = %d", lastA);
 | 
			
		||||
          }
 | 
			
		||||
          if (edit.getBeginB() != edit.getEndB()) {
 | 
			
		||||
            logger.atFine().log(
 | 
			
		||||
                "edit.getBeginB() != edit.getEndB(): edit.getBeginB() = %d, edit.getEndB() = %d",
 | 
			
		||||
                edit.getBeginB(), edit.getEndB());
 | 
			
		||||
            e.editB.add(
 | 
			
		||||
                ImmutableList.of(edit.getBeginB() - lastB, edit.getEndB() - edit.getBeginB()));
 | 
			
		||||
            lastB = edit.getEndB();
 | 
			
		||||
            logger.atFine().log("lastB = %d", lastB);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      e.dueToRebase = dueToRebase ? true : null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ContentEntry entry() {
 | 
			
		||||
      ContentEntry e = new ContentEntry();
 | 
			
		||||
      lines.add(e);
 | 
			
		||||
      return e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								java/com/google/gerrit/server/diff/DiffSide.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								java/com/google/gerrit/server/diff/DiffSide.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
// 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.diff;
 | 
			
		||||
 | 
			
		||||
import com.google.auto.value.AutoValue;
 | 
			
		||||
import com.google.gerrit.common.data.PatchScript.PatchScriptFileInfo;
 | 
			
		||||
 | 
			
		||||
/** Contains settings for one of two sides in diff view. Each diff view has exactly 2 sides. */
 | 
			
		||||
@AutoValue
 | 
			
		||||
public abstract class DiffSide {
 | 
			
		||||
  public enum Type {
 | 
			
		||||
    SIDE_A,
 | 
			
		||||
    SIDE_B
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static DiffSide create(PatchScriptFileInfo fileInfo, String fileName, Type type) {
 | 
			
		||||
    return new AutoValue_DiffSide(fileInfo, fileName, type);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public abstract PatchScriptFileInfo fileInfo();
 | 
			
		||||
 | 
			
		||||
  public abstract String fileName();
 | 
			
		||||
 | 
			
		||||
  public abstract Type type();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								java/com/google/gerrit/server/diff/DiffWebLinksProvider.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								java/com/google/gerrit/server/diff/DiffWebLinksProvider.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
// 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.diff;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.ImmutableList;
 | 
			
		||||
import com.google.gerrit.extensions.common.DiffWebLinkInfo;
 | 
			
		||||
import com.google.gerrit.extensions.common.WebLinkInfo;
 | 
			
		||||
 | 
			
		||||
/** Provider for different types of links which can be displayed in a diff view. */
 | 
			
		||||
public interface DiffWebLinksProvider {
 | 
			
		||||
 | 
			
		||||
  /** Returns links associated with the diff view */
 | 
			
		||||
  ImmutableList<DiffWebLinkInfo> getDiffLinks();
 | 
			
		||||
 | 
			
		||||
  /** Returns links associated with the diff side */
 | 
			
		||||
  ImmutableList<WebLinkInfo> getFileWebLinks(DiffSide.Type fileInfoType);
 | 
			
		||||
}
 | 
			
		||||
@@ -28,7 +28,7 @@ import com.google.gerrit.entities.Project;
 | 
			
		||||
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
 | 
			
		||||
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
 | 
			
		||||
import com.google.gerrit.prettify.common.EditList;
 | 
			
		||||
import com.google.gerrit.prettify.common.SparseFileContent;
 | 
			
		||||
import com.google.gerrit.prettify.common.SparseFileContentBuilder;
 | 
			
		||||
import com.google.gerrit.server.mime.FileTypeRegistry;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import eu.medsea.mimeutil.MimeType;
 | 
			
		||||
@@ -198,8 +198,8 @@ class PatchScriptBuilder {
 | 
			
		||||
        b.fileMode,
 | 
			
		||||
        content.getHeaderLines(),
 | 
			
		||||
        diffPrefs,
 | 
			
		||||
        a.dst,
 | 
			
		||||
        b.dst,
 | 
			
		||||
        a.dst.build(),
 | 
			
		||||
        b.dst.build(),
 | 
			
		||||
        edits,
 | 
			
		||||
        editsDueToRebase,
 | 
			
		||||
        a.displayMethod,
 | 
			
		||||
@@ -478,9 +478,9 @@ class PatchScriptBuilder {
 | 
			
		||||
    final MimeType mimeType;
 | 
			
		||||
    final DisplayMethod displayMethod;
 | 
			
		||||
    final PatchScript.FileMode fileMode;
 | 
			
		||||
    final SparseFileContent dst;
 | 
			
		||||
    final SparseFileContentBuilder dst;
 | 
			
		||||
 | 
			
		||||
    public Side(
 | 
			
		||||
    private Side(
 | 
			
		||||
        String path,
 | 
			
		||||
        ObjectId id,
 | 
			
		||||
        FileMode mode,
 | 
			
		||||
@@ -497,8 +497,7 @@ class PatchScriptBuilder {
 | 
			
		||||
      this.mimeType = mimeType;
 | 
			
		||||
      this.displayMethod = displayMethod;
 | 
			
		||||
      this.fileMode = fileMode;
 | 
			
		||||
      dst = new SparseFileContent();
 | 
			
		||||
      dst.setSize(size());
 | 
			
		||||
      dst = new SparseFileContentBuilder(size());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int size() {
 | 
			
		||||
 
 | 
			
		||||
@@ -14,27 +14,18 @@
 | 
			
		||||
 | 
			
		||||
package com.google.gerrit.server.restapi.change;
 | 
			
		||||
 | 
			
		||||
import static com.google.common.base.Preconditions.checkState;
 | 
			
		||||
import static com.google.gerrit.util.cli.Localizable.localizable;
 | 
			
		||||
 | 
			
		||||
import com.google.common.base.MoreObjects;
 | 
			
		||||
import com.google.common.collect.ImmutableList;
 | 
			
		||||
import com.google.common.collect.ImmutableMap;
 | 
			
		||||
import com.google.common.collect.Lists;
 | 
			
		||||
import com.google.common.collect.Maps;
 | 
			
		||||
import com.google.common.flogger.FluentLogger;
 | 
			
		||||
import com.google.gerrit.common.Nullable;
 | 
			
		||||
import com.google.gerrit.common.data.PatchScript;
 | 
			
		||||
import com.google.gerrit.common.data.PatchScript.DisplayMethod;
 | 
			
		||||
import com.google.gerrit.entities.Patch;
 | 
			
		||||
import com.google.gerrit.entities.PatchSet;
 | 
			
		||||
import com.google.gerrit.entities.Project;
 | 
			
		||||
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
 | 
			
		||||
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
 | 
			
		||||
import com.google.gerrit.extensions.common.ChangeType;
 | 
			
		||||
import com.google.gerrit.extensions.common.DiffInfo;
 | 
			
		||||
import com.google.gerrit.extensions.common.DiffInfo.ContentEntry;
 | 
			
		||||
import com.google.gerrit.extensions.common.DiffInfo.FileMeta;
 | 
			
		||||
import com.google.gerrit.extensions.common.DiffInfo.IntraLineStatus;
 | 
			
		||||
import com.google.gerrit.extensions.common.DiffWebLinkInfo;
 | 
			
		||||
import com.google.gerrit.extensions.common.WebLinkInfo;
 | 
			
		||||
import com.google.gerrit.extensions.restapi.AuthException;
 | 
			
		||||
@@ -44,12 +35,12 @@ import com.google.gerrit.extensions.restapi.ResourceConflictException;
 | 
			
		||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 | 
			
		||||
import com.google.gerrit.extensions.restapi.Response;
 | 
			
		||||
import com.google.gerrit.extensions.restapi.RestReadView;
 | 
			
		||||
import com.google.gerrit.jgit.diff.ReplaceEdit;
 | 
			
		||||
import com.google.gerrit.prettify.common.SparseFileContent;
 | 
			
		||||
import com.google.gerrit.server.WebLinks;
 | 
			
		||||
import com.google.gerrit.server.change.FileContentUtil;
 | 
			
		||||
import com.google.gerrit.server.change.FileResource;
 | 
			
		||||
import com.google.gerrit.server.change.RevisionResource;
 | 
			
		||||
import com.google.gerrit.server.diff.DiffInfoCreator;
 | 
			
		||||
import com.google.gerrit.server.diff.DiffSide;
 | 
			
		||||
import com.google.gerrit.server.diff.DiffWebLinksProvider;
 | 
			
		||||
import com.google.gerrit.server.git.LargeObjectException;
 | 
			
		||||
import com.google.gerrit.server.notedb.ChangeNotes;
 | 
			
		||||
import com.google.gerrit.server.patch.PatchScriptFactory;
 | 
			
		||||
@@ -60,10 +51,7 @@ import com.google.gerrit.server.project.ProjectCache;
 | 
			
		||||
import com.google.gerrit.server.project.ProjectState;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
import org.eclipse.jgit.diff.Edit;
 | 
			
		||||
import org.kohsuke.args4j.CmdLineException;
 | 
			
		||||
import org.kohsuke.args4j.CmdLineParser;
 | 
			
		||||
import org.kohsuke.args4j.NamedOptionDef;
 | 
			
		||||
@@ -76,17 +64,6 @@ import org.kohsuke.args4j.spi.Setter;
 | 
			
		||||
public class GetDiff implements RestReadView<FileResource> {
 | 
			
		||||
  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 | 
			
		||||
 | 
			
		||||
  private static final ImmutableMap<Patch.ChangeType, ChangeType> CHANGE_TYPE =
 | 
			
		||||
      Maps.immutableEnumMap(
 | 
			
		||||
          new ImmutableMap.Builder<Patch.ChangeType, ChangeType>()
 | 
			
		||||
              .put(Patch.ChangeType.ADDED, ChangeType.ADDED)
 | 
			
		||||
              .put(Patch.ChangeType.MODIFIED, ChangeType.MODIFIED)
 | 
			
		||||
              .put(Patch.ChangeType.DELETED, ChangeType.DELETED)
 | 
			
		||||
              .put(Patch.ChangeType.RENAMED, ChangeType.RENAMED)
 | 
			
		||||
              .put(Patch.ChangeType.COPIED, ChangeType.COPIED)
 | 
			
		||||
              .put(Patch.ChangeType.REWRITE, ChangeType.REWRITE)
 | 
			
		||||
              .build());
 | 
			
		||||
 | 
			
		||||
  private final ProjectCache projectCache;
 | 
			
		||||
  private final PatchScriptFactory.Factory patchScriptFactoryFactory;
 | 
			
		||||
  private final Revisions revisions;
 | 
			
		||||
@@ -164,111 +141,18 @@ public class GetDiff implements RestReadView<FileResource> {
 | 
			
		||||
      psf.setLoadHistory(false);
 | 
			
		||||
      psf.setLoadComments(context != DiffPreferencesInfo.WHOLE_FILE_CONTEXT);
 | 
			
		||||
      PatchScript ps = psf.call();
 | 
			
		||||
      ContentCollector contentCollector = new ContentCollector(ps);
 | 
			
		||||
      Set<Edit> editsDueToRebase = ps.getEditsDueToRebase();
 | 
			
		||||
      for (Edit edit : ps.getEdits()) {
 | 
			
		||||
        logger.atFine().log("next edit = %s", edit);
 | 
			
		||||
 | 
			
		||||
        if (edit.getType() == Edit.Type.EMPTY) {
 | 
			
		||||
          logger.atFine().log("skip empty edit");
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
        contentCollector.addCommon(edit.getBeginA());
 | 
			
		||||
 | 
			
		||||
        checkState(
 | 
			
		||||
            contentCollector.nextA == edit.getBeginA(),
 | 
			
		||||
            "nextA = %s; want %s",
 | 
			
		||||
            contentCollector.nextA,
 | 
			
		||||
            edit.getBeginA());
 | 
			
		||||
        checkState(
 | 
			
		||||
            contentCollector.nextB == edit.getBeginB(),
 | 
			
		||||
            "nextB = %s; want %s",
 | 
			
		||||
            contentCollector.nextB,
 | 
			
		||||
            edit.getBeginB());
 | 
			
		||||
        switch (edit.getType()) {
 | 
			
		||||
          case DELETE:
 | 
			
		||||
          case INSERT:
 | 
			
		||||
          case REPLACE:
 | 
			
		||||
            List<Edit> internalEdit =
 | 
			
		||||
                edit instanceof ReplaceEdit ? ((ReplaceEdit) edit).getInternalEdits() : null;
 | 
			
		||||
            boolean dueToRebase = editsDueToRebase.contains(edit);
 | 
			
		||||
            contentCollector.addDiff(edit.getEndA(), edit.getEndB(), internalEdit, dueToRebase);
 | 
			
		||||
            break;
 | 
			
		||||
          case EMPTY:
 | 
			
		||||
          default:
 | 
			
		||||
            throw new IllegalStateException();
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      contentCollector.addCommon(ps.getA().size());
 | 
			
		||||
 | 
			
		||||
      ProjectState state = projectCache.get(resource.getRevision().getChange().getProject());
 | 
			
		||||
 | 
			
		||||
      DiffInfo result = new DiffInfo();
 | 
			
		||||
      String revA = basePatchSet != null ? basePatchSet.refName() : ps.getFileInfoA().commitId;
 | 
			
		||||
      String revB =
 | 
			
		||||
          resource.getRevision().getEdit().isPresent()
 | 
			
		||||
              ? resource.getRevision().getEdit().get().getRefName()
 | 
			
		||||
              : resource.getRevision().getPatchSet().refName();
 | 
			
		||||
      logger.atFine().log("revA = %s, revB = %s", revA, revB);
 | 
			
		||||
 | 
			
		||||
      ImmutableList<DiffWebLinkInfo> links =
 | 
			
		||||
          webLinks.getDiffLinks(
 | 
			
		||||
              state.getName(),
 | 
			
		||||
              resource.getPatchKey().patchSetId().changeId().get(),
 | 
			
		||||
              basePatchSet != null ? basePatchSet.id().get() : null,
 | 
			
		||||
              revA,
 | 
			
		||||
      Project.NameKey projectName = resource.getRevision().getChange().getProject();
 | 
			
		||||
      ProjectState state = projectCache.get(projectName);
 | 
			
		||||
      DiffSide sideA =
 | 
			
		||||
          DiffSide.create(
 | 
			
		||||
              ps.getFileInfoA(),
 | 
			
		||||
              MoreObjects.firstNonNull(ps.getOldName(), ps.getNewName()),
 | 
			
		||||
              resource.getPatchKey().patchSetId().get(),
 | 
			
		||||
              revB,
 | 
			
		||||
              ps.getNewName());
 | 
			
		||||
      result.webLinks = links.isEmpty() ? null : links;
 | 
			
		||||
 | 
			
		||||
      if (ps.isBinary()) {
 | 
			
		||||
        result.binary = true;
 | 
			
		||||
      }
 | 
			
		||||
      if (ps.getDisplayMethodA() != DisplayMethod.NONE) {
 | 
			
		||||
        result.metaA = new FileMeta();
 | 
			
		||||
        result.metaA.name = MoreObjects.firstNonNull(ps.getOldName(), ps.getNewName());
 | 
			
		||||
        result.metaA.contentType =
 | 
			
		||||
            FileContentUtil.resolveContentType(
 | 
			
		||||
                state, result.metaA.name, ps.getFileModeA(), ps.getMimeTypeA());
 | 
			
		||||
        result.metaA.lines = ps.getA().size();
 | 
			
		||||
        result.metaA.webLinks = getFileWebLinks(state.getProject(), revA, result.metaA.name);
 | 
			
		||||
        result.metaA.commitId = ps.getFileInfoA().commitId;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (ps.getDisplayMethodB() != DisplayMethod.NONE) {
 | 
			
		||||
        result.metaB = new FileMeta();
 | 
			
		||||
        result.metaB.name = ps.getNewName();
 | 
			
		||||
        result.metaB.contentType =
 | 
			
		||||
            FileContentUtil.resolveContentType(
 | 
			
		||||
                state, result.metaB.name, ps.getFileModeB(), ps.getMimeTypeB());
 | 
			
		||||
        result.metaB.lines = ps.getB().size();
 | 
			
		||||
        result.metaB.webLinks = getFileWebLinks(state.getProject(), revB, result.metaB.name);
 | 
			
		||||
        result.metaB.commitId = ps.getFileInfoB().commitId;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (intraline) {
 | 
			
		||||
        if (ps.hasIntralineTimeout()) {
 | 
			
		||||
          result.intralineStatus = IntraLineStatus.TIMEOUT;
 | 
			
		||||
        } else if (ps.hasIntralineFailure()) {
 | 
			
		||||
          result.intralineStatus = IntraLineStatus.FAILURE;
 | 
			
		||||
        } else {
 | 
			
		||||
          result.intralineStatus = IntraLineStatus.OK;
 | 
			
		||||
        }
 | 
			
		||||
        logger.atFine().log("intralineStatus = %s", result.intralineStatus);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      result.changeType = CHANGE_TYPE.get(ps.getChangeType());
 | 
			
		||||
      logger.atFine().log("changeType = %s", result.changeType);
 | 
			
		||||
      if (result.changeType == null) {
 | 
			
		||||
        throw new IllegalStateException("unknown change type: " + ps.getChangeType());
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (ps.getPatchHeader().size() > 0) {
 | 
			
		||||
        result.diffHeader = ps.getPatchHeader();
 | 
			
		||||
      }
 | 
			
		||||
      result.content = contentCollector.lines;
 | 
			
		||||
              DiffSide.Type.SIDE_A);
 | 
			
		||||
      DiffSide sideB = DiffSide.create(ps.getFileInfoB(), ps.getNewName(), DiffSide.Type.SIDE_B);
 | 
			
		||||
      DiffWebLinksProvider webLinksProvider =
 | 
			
		||||
          new DiffWebLinksProviderImpl(sideA, sideB, projectName, basePatchSet, webLinks, resource);
 | 
			
		||||
      DiffInfoCreator diffInfoCreator = new DiffInfoCreator(state, webLinksProvider, intraline);
 | 
			
		||||
      DiffInfo result = diffInfoCreator.create(ps, sideA, sideB);
 | 
			
		||||
 | 
			
		||||
      Response<DiffInfo> r = Response.ok(result);
 | 
			
		||||
      if (resource.isCacheable()) {
 | 
			
		||||
@@ -282,9 +166,69 @@ public class GetDiff implements RestReadView<FileResource> {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private List<WebLinkInfo> getFileWebLinks(Project project, String rev, String file) {
 | 
			
		||||
    ImmutableList<WebLinkInfo> links = webLinks.getFileLinks(project.getName(), rev, file);
 | 
			
		||||
    return links.isEmpty() ? null : links;
 | 
			
		||||
  private static class DiffWebLinksProviderImpl implements DiffWebLinksProvider {
 | 
			
		||||
 | 
			
		||||
    private final WebLinks webLinks;
 | 
			
		||||
    private final Project.NameKey projectName;
 | 
			
		||||
    private final DiffSide sideA;
 | 
			
		||||
    private final DiffSide sideB;
 | 
			
		||||
    private final String revA;
 | 
			
		||||
    private final String revB;
 | 
			
		||||
    private final FileResource resource;
 | 
			
		||||
    @Nullable private final PatchSet basePatchSet;
 | 
			
		||||
 | 
			
		||||
    DiffWebLinksProviderImpl(
 | 
			
		||||
        DiffSide sideA,
 | 
			
		||||
        DiffSide sideB,
 | 
			
		||||
        Project.NameKey projectName,
 | 
			
		||||
        @Nullable PatchSet basePatchSet,
 | 
			
		||||
        WebLinks webLinks,
 | 
			
		||||
        FileResource resource) {
 | 
			
		||||
      this.projectName = projectName;
 | 
			
		||||
      this.webLinks = webLinks;
 | 
			
		||||
      this.basePatchSet = basePatchSet;
 | 
			
		||||
      this.resource = resource;
 | 
			
		||||
      this.sideA = sideA;
 | 
			
		||||
      this.sideB = sideB;
 | 
			
		||||
 | 
			
		||||
      revA = basePatchSet != null ? basePatchSet.refName() : sideA.fileInfo().commitId;
 | 
			
		||||
 | 
			
		||||
      RevisionResource revision = resource.getRevision();
 | 
			
		||||
      revB =
 | 
			
		||||
          revision
 | 
			
		||||
              .getEdit()
 | 
			
		||||
              .map(edit -> edit.getRefName())
 | 
			
		||||
              .orElseGet(() -> revision.getPatchSet().refName());
 | 
			
		||||
 | 
			
		||||
      logger.atFine().log("revA = %s, revB = %s", revA, revB);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public ImmutableList<DiffWebLinkInfo> getDiffLinks() {
 | 
			
		||||
      return webLinks.getDiffLinks(
 | 
			
		||||
          projectName.get(),
 | 
			
		||||
          resource.getPatchKey().patchSetId().changeId().get(),
 | 
			
		||||
          basePatchSet != null ? basePatchSet.id().get() : null,
 | 
			
		||||
          revA,
 | 
			
		||||
          sideA.fileName(),
 | 
			
		||||
          resource.getPatchKey().patchSetId().get(),
 | 
			
		||||
          revB,
 | 
			
		||||
          sideB.fileName());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public ImmutableList<WebLinkInfo> getFileWebLinks(DiffSide.Type type) {
 | 
			
		||||
      String rev;
 | 
			
		||||
      DiffSide side;
 | 
			
		||||
      if (type == DiffSide.Type.SIDE_A) {
 | 
			
		||||
        rev = revA;
 | 
			
		||||
        side = sideA;
 | 
			
		||||
      } else {
 | 
			
		||||
        rev = revB;
 | 
			
		||||
        side = sideB;
 | 
			
		||||
      }
 | 
			
		||||
      return webLinks.getFileLinks(projectName.get(), rev, side.fileName());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public GetDiff setBase(String base) {
 | 
			
		||||
@@ -312,141 +256,6 @@ public class GetDiff implements RestReadView<FileResource> {
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static class ContentCollector {
 | 
			
		||||
    final List<ContentEntry> lines;
 | 
			
		||||
    final SparseFileContent fileA;
 | 
			
		||||
    final SparseFileContent fileB;
 | 
			
		||||
    final boolean ignoreWS;
 | 
			
		||||
 | 
			
		||||
    int nextA;
 | 
			
		||||
    int nextB;
 | 
			
		||||
 | 
			
		||||
    ContentCollector(PatchScript ps) {
 | 
			
		||||
      lines = Lists.newArrayListWithExpectedSize(ps.getEdits().size() + 2);
 | 
			
		||||
      fileA = ps.getA();
 | 
			
		||||
      fileB = ps.getB();
 | 
			
		||||
      ignoreWS = ps.isIgnoreWhitespace();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void addCommon(int end) {
 | 
			
		||||
      logger.atFine().log("addCommon: end = %d", end);
 | 
			
		||||
 | 
			
		||||
      end = Math.min(end, fileA.size());
 | 
			
		||||
      logger.atFine().log("end = %d", end);
 | 
			
		||||
 | 
			
		||||
      if (nextA >= end) {
 | 
			
		||||
        logger.atFine().log("nextA >= end: nextA = %d, end = %d", nextA, end);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      while (nextA < end) {
 | 
			
		||||
        logger.atFine().log("nextA < end: nextA = %d, end = %d", nextA, end);
 | 
			
		||||
 | 
			
		||||
        if (!fileA.contains(nextA)) {
 | 
			
		||||
          logger.atFine().log("fileA does not contain nextA: nextA = %d", nextA);
 | 
			
		||||
 | 
			
		||||
          int endRegion = Math.min(end, nextA == 0 ? fileA.first() : fileA.next(nextA - 1));
 | 
			
		||||
          int len = endRegion - nextA;
 | 
			
		||||
          entry().skip = len;
 | 
			
		||||
          nextA = endRegion;
 | 
			
		||||
          nextB += len;
 | 
			
		||||
 | 
			
		||||
          logger.atFine().log("setting: nextA = %d, nextB = %d", nextA, nextB);
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ContentEntry e = null;
 | 
			
		||||
        for (int i = nextA; i == nextA && i < end; i = fileA.next(i), nextA++, nextB++) {
 | 
			
		||||
          if (ignoreWS && fileB.contains(nextB)) {
 | 
			
		||||
            if (e == null || e.common == null) {
 | 
			
		||||
              logger.atFine().log("create new common entry: nextA = %d, nextB = %d", nextA, nextB);
 | 
			
		||||
              e = entry();
 | 
			
		||||
              e.a = Lists.newArrayListWithCapacity(end - nextA);
 | 
			
		||||
              e.b = Lists.newArrayListWithCapacity(end - nextA);
 | 
			
		||||
              e.common = true;
 | 
			
		||||
            }
 | 
			
		||||
            e.a.add(fileA.get(nextA));
 | 
			
		||||
            e.b.add(fileB.get(nextB));
 | 
			
		||||
          } else {
 | 
			
		||||
            if (e == null || e.common != null) {
 | 
			
		||||
              logger.atFine().log(
 | 
			
		||||
                  "create new non-common entry: nextA = %d, nextB = %d", nextA, nextB);
 | 
			
		||||
              e = entry();
 | 
			
		||||
              e.ab = Lists.newArrayListWithCapacity(end - nextA);
 | 
			
		||||
            }
 | 
			
		||||
            e.ab.add(fileA.get(nextA));
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        logger.atFine().log("nextA = %d, nextB = %d", nextA, nextB);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void addDiff(int endA, int endB, List<Edit> internalEdit, boolean dueToRebase) {
 | 
			
		||||
      logger.atFine().log(
 | 
			
		||||
          "addDiff: endA = %d, endB = %d, numberOfInternalEdits = %d, dueToRebase = %s",
 | 
			
		||||
          endA, endB, internalEdit != null ? internalEdit.size() : 0, dueToRebase);
 | 
			
		||||
 | 
			
		||||
      int lenA = endA - nextA;
 | 
			
		||||
      int lenB = endB - nextB;
 | 
			
		||||
      logger.atFine().log("lenA = %d, lenB = %d", lenA, lenB);
 | 
			
		||||
      checkState(lenA > 0 || lenB > 0);
 | 
			
		||||
 | 
			
		||||
      logger.atFine().log("create non-common entry");
 | 
			
		||||
      ContentEntry e = entry();
 | 
			
		||||
      if (lenA > 0) {
 | 
			
		||||
        logger.atFine().log("lenA > 0: lenA = %d", lenA);
 | 
			
		||||
        e.a = Lists.newArrayListWithCapacity(lenA);
 | 
			
		||||
        for (; nextA < endA; nextA++) {
 | 
			
		||||
          e.a.add(fileA.get(nextA));
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (lenB > 0) {
 | 
			
		||||
        logger.atFine().log("lenB > 0: lenB = %d", lenB);
 | 
			
		||||
        e.b = Lists.newArrayListWithCapacity(lenB);
 | 
			
		||||
        for (; nextB < endB; nextB++) {
 | 
			
		||||
          e.b.add(fileB.get(nextB));
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (internalEdit != null && !internalEdit.isEmpty()) {
 | 
			
		||||
        logger.atFine().log("processing internal edits");
 | 
			
		||||
 | 
			
		||||
        e.editA = Lists.newArrayListWithCapacity(internalEdit.size() * 2);
 | 
			
		||||
        e.editB = Lists.newArrayListWithCapacity(internalEdit.size() * 2);
 | 
			
		||||
        int lastA = 0;
 | 
			
		||||
        int lastB = 0;
 | 
			
		||||
        for (Edit edit : internalEdit) {
 | 
			
		||||
          logger.atFine().log("internal edit = %s", edit);
 | 
			
		||||
 | 
			
		||||
          if (edit.getBeginA() != edit.getEndA()) {
 | 
			
		||||
            logger.atFine().log(
 | 
			
		||||
                "edit.getBeginA() != edit.getEndA(): edit.getBeginA() = %d, edit.getEndA() = %d",
 | 
			
		||||
                edit.getBeginA(), edit.getEndA());
 | 
			
		||||
            e.editA.add(
 | 
			
		||||
                ImmutableList.of(edit.getBeginA() - lastA, edit.getEndA() - edit.getBeginA()));
 | 
			
		||||
            lastA = edit.getEndA();
 | 
			
		||||
            logger.atFine().log("lastA = %d", lastA);
 | 
			
		||||
          }
 | 
			
		||||
          if (edit.getBeginB() != edit.getEndB()) {
 | 
			
		||||
            logger.atFine().log(
 | 
			
		||||
                "edit.getBeginB() != edit.getEndB(): edit.getBeginB() = %d, edit.getEndB() = %d",
 | 
			
		||||
                edit.getBeginB(), edit.getEndB());
 | 
			
		||||
            e.editB.add(
 | 
			
		||||
                ImmutableList.of(edit.getBeginB() - lastB, edit.getEndB() - edit.getBeginB()));
 | 
			
		||||
            lastB = edit.getEndB();
 | 
			
		||||
            logger.atFine().log("lastB = %d", lastB);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      e.dueToRebase = dueToRebase ? true : null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ContentEntry entry() {
 | 
			
		||||
      ContentEntry e = new ContentEntry();
 | 
			
		||||
      lines.add(e);
 | 
			
		||||
      return e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Deprecated
 | 
			
		||||
  enum IgnoreWhitespace {
 | 
			
		||||
    NONE(DiffPreferencesInfo.Whitespace.IGNORE_NONE),
 | 
			
		||||
@@ -462,6 +271,7 @@ public class GetDiff implements RestReadView<FileResource> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static class ContextOptionHandler extends OptionHandler<Short> {
 | 
			
		||||
 | 
			
		||||
    public ContextOptionHandler(CmdLineParser parser, OptionDef option, Setter<Short> setter) {
 | 
			
		||||
      super(parser, option, setter);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								javatests/com/google/gerrit/prettify/BUILD
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								javatests/com/google/gerrit/prettify/BUILD
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
load("//tools/bzl:junit.bzl", "junit_tests")
 | 
			
		||||
 | 
			
		||||
junit_tests(
 | 
			
		||||
    name = "prettify_tests",
 | 
			
		||||
    srcs = glob(["**/*.java"]),
 | 
			
		||||
    deps = [
 | 
			
		||||
        "//java/com/google/gerrit/prettify:server",
 | 
			
		||||
        "//java/com/google/gerrit/prettify/common/testing",
 | 
			
		||||
        "//java/com/google/gerrit/testing:gerrit-test-util",
 | 
			
		||||
        "//lib:guava",
 | 
			
		||||
        "//lib/truth",
 | 
			
		||||
    ],
 | 
			
		||||
)
 | 
			
		||||
@@ -0,0 +1,170 @@
 | 
			
		||||
// 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.prettify.common;
 | 
			
		||||
 | 
			
		||||
import static com.google.gerrit.prettify.common.testing.SparseFileContentSubject.assertThat;
 | 
			
		||||
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.ImmutableMap;
 | 
			
		||||
import org.junit.Ignore;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
 | 
			
		||||
public class SparseFileContentBuilderTest {
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void addLineWithNegativeNumber() {
 | 
			
		||||
    SparseFileContentBuilder builder = new SparseFileContentBuilder(10);
 | 
			
		||||
    assertThrows(IllegalArgumentException.class, () -> builder.addLine(-1, "First line"));
 | 
			
		||||
 | 
			
		||||
    assertThrows(IllegalArgumentException.class, () -> builder.addLine(-5, "First line"));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  @Ignore
 | 
			
		||||
  public void addLineNumberZeroFileSize() {
 | 
			
		||||
    // Temporary ignore - see comments in SparseFileContentBuilder.build() method
 | 
			
		||||
    SparseFileContentBuilder builder = new SparseFileContentBuilder(0);
 | 
			
		||||
    assertThrows(IllegalArgumentException.class, () -> builder.addLine(0, "First line"));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  @Ignore
 | 
			
		||||
  public void addLineNumberNonZeroFileSize() {
 | 
			
		||||
    // Temporary ignore - see comments in SparseFileContentBuilder.build() method
 | 
			
		||||
    SparseFileContentBuilder builder = new SparseFileContentBuilder(5);
 | 
			
		||||
    assertThrows(IllegalArgumentException.class, () -> builder.addLine(5, "First line"));
 | 
			
		||||
    assertThrows(IllegalArgumentException.class, () -> builder.addLine(6, "First line"));
 | 
			
		||||
    assertThrows(IllegalArgumentException.class, () -> builder.addLine(7, "First line"));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void addLineIncorrectOrder() {
 | 
			
		||||
    SparseFileContentBuilder builder = new SparseFileContentBuilder(5);
 | 
			
		||||
 | 
			
		||||
    builder.addLine(0, "First line");
 | 
			
		||||
    builder.addLine(1, "Second line");
 | 
			
		||||
    builder.addLine(3, "Third line");
 | 
			
		||||
    builder.addLine(4, "Fourth line");
 | 
			
		||||
    assertThrows(IllegalArgumentException.class, () -> builder.addLine(4, "Other Line"));
 | 
			
		||||
 | 
			
		||||
    assertThrows(IllegalArgumentException.class, () -> builder.addLine(2, "Other Line"));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void emptyContentZeroSize() {
 | 
			
		||||
    SparseFileContentBuilder builder = new SparseFileContentBuilder(0);
 | 
			
		||||
 | 
			
		||||
    SparseFileContent content = builder.build();
 | 
			
		||||
    assertThat(content).getSize().isEqualTo(0);
 | 
			
		||||
    assertThat(content).getRangesCount().isEqualTo(0);
 | 
			
		||||
    assertThat(content).lines().isEmpty();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void emptyContentNonZeroSize() {
 | 
			
		||||
    SparseFileContentBuilder builder = new SparseFileContentBuilder(4);
 | 
			
		||||
    SparseFileContent content = builder.build();
 | 
			
		||||
    assertThat(content).getSize().isEqualTo(4);
 | 
			
		||||
    assertThat(content).getRangesCount().isEqualTo(0);
 | 
			
		||||
    assertThat(content).lines().isEmpty();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void oneLineContentLineNumberZero() {
 | 
			
		||||
    SparseFileContentBuilder builder = new SparseFileContentBuilder(1);
 | 
			
		||||
 | 
			
		||||
    builder.addLine(0, "First line");
 | 
			
		||||
    SparseFileContent content = builder.build();
 | 
			
		||||
    assertThat(content).getSize().isEqualTo(1);
 | 
			
		||||
    assertThat(content).getRangesCount().isEqualTo(1);
 | 
			
		||||
    assertThat(content).lines().containsExactlyEntriesIn(ImmutableMap.of(0, "First line"));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void oneLineContentLineNumberNotZero() {
 | 
			
		||||
    SparseFileContentBuilder builder = new SparseFileContentBuilder(6);
 | 
			
		||||
 | 
			
		||||
    builder.addLine(5, "First line");
 | 
			
		||||
    SparseFileContent content = builder.build();
 | 
			
		||||
    assertThat(content).getSize().isEqualTo(6);
 | 
			
		||||
    assertThat(content).getRangesCount().isEqualTo(1);
 | 
			
		||||
    assertThat(content).lines().containsExactlyEntriesIn(ImmutableMap.of(5, "First line"));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void multiLineContinuousContentStartingFromZero() {
 | 
			
		||||
    SparseFileContentBuilder builder = new SparseFileContentBuilder(5);
 | 
			
		||||
 | 
			
		||||
    builder.addLine(0, "First line");
 | 
			
		||||
    builder.addLine(1, "Second line");
 | 
			
		||||
    builder.addLine(2, "Third line");
 | 
			
		||||
    SparseFileContent content = builder.build();
 | 
			
		||||
    assertThat(content).getSize().isEqualTo(5);
 | 
			
		||||
    assertThat(content).getRangesCount().isEqualTo(1);
 | 
			
		||||
    assertThat(content)
 | 
			
		||||
        .lines()
 | 
			
		||||
        .containsExactlyEntriesIn(
 | 
			
		||||
            ImmutableMap.of(
 | 
			
		||||
                0, "First line",
 | 
			
		||||
                1, "Second line",
 | 
			
		||||
                2, "Third line"));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void multiLineContentStartingFromNonZeroLine() {
 | 
			
		||||
    SparseFileContentBuilder builder = new SparseFileContentBuilder(8);
 | 
			
		||||
 | 
			
		||||
    builder.addLine(5, "First line");
 | 
			
		||||
    builder.addLine(6, "Second line");
 | 
			
		||||
    builder.addLine(7, "Third line");
 | 
			
		||||
    SparseFileContent content = builder.build();
 | 
			
		||||
    assertThat(content).getSize().isEqualTo(8);
 | 
			
		||||
    assertThat(content).getRangesCount().isEqualTo(1);
 | 
			
		||||
    assertThat(content)
 | 
			
		||||
        .lines()
 | 
			
		||||
        .containsExactlyEntriesIn(
 | 
			
		||||
            ImmutableMap.of(
 | 
			
		||||
                5, "First line",
 | 
			
		||||
                6, "Second line",
 | 
			
		||||
                7, "Third line"));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void multiLineContentWithGaps() {
 | 
			
		||||
    SparseFileContentBuilder builder = new SparseFileContentBuilder(10000);
 | 
			
		||||
    builder.addLine(0, "First line");
 | 
			
		||||
    builder.addLine(1, "Second line");
 | 
			
		||||
    builder.addLine(3, "Third line");
 | 
			
		||||
    builder.addLine(4, "Fourth line");
 | 
			
		||||
    builder.addLine(5, "Fifth line");
 | 
			
		||||
    builder.addLine(6, "Sixth line");
 | 
			
		||||
    builder.addLine(10, "Seventh line");
 | 
			
		||||
    SparseFileContent content = builder.build();
 | 
			
		||||
    assertThat(content).getSize().isEqualTo(10000);
 | 
			
		||||
    assertThat(content).getRangesCount().isEqualTo(3);
 | 
			
		||||
    assertThat(content)
 | 
			
		||||
        .lines()
 | 
			
		||||
        .containsExactlyEntriesIn(
 | 
			
		||||
            ImmutableMap.builder()
 | 
			
		||||
                .put(0, "First line")
 | 
			
		||||
                .put(1, "Second line")
 | 
			
		||||
                .put(3, "Third line")
 | 
			
		||||
                .put(4, "Fourth line")
 | 
			
		||||
                .put(5, "Fifth line")
 | 
			
		||||
                .put(6, "Sixth line")
 | 
			
		||||
                .put(10, "Seventh line")
 | 
			
		||||
                .build());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user