Extract DiffInfoCreator from GetDiff class
Extract DiffInfoCreator for reuse in robot's preview fix. Change-Id: I32e6f41007a38e0d758b209046cbd45c8cf6b49d
This commit is contained in:
parent
bd41629222
commit
0a0ff68a03
@ -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());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user