Line data Source code
1 : // Copyright (C) 2019 The Android Open Source Project
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : package com.google.gerrit.server.diff;
16 :
17 : import static com.google.common.base.Preconditions.checkState;
18 :
19 : import com.google.common.collect.ImmutableList;
20 : import com.google.common.collect.ImmutableMap;
21 : import com.google.common.collect.Lists;
22 : import com.google.common.collect.Maps;
23 : import com.google.common.flogger.FluentLogger;
24 : import com.google.gerrit.common.data.PatchScript;
25 : import com.google.gerrit.common.data.PatchScript.DisplayMethod;
26 : import com.google.gerrit.common.data.PatchScript.PatchScriptFileInfo;
27 : import com.google.gerrit.entities.Patch;
28 : import com.google.gerrit.extensions.common.ChangeType;
29 : import com.google.gerrit.extensions.common.DiffInfo;
30 : import com.google.gerrit.extensions.common.DiffInfo.ContentEntry;
31 : import com.google.gerrit.extensions.common.DiffInfo.FileMeta;
32 : import com.google.gerrit.extensions.common.DiffInfo.IntraLineStatus;
33 : import com.google.gerrit.extensions.common.DiffWebLinkInfo;
34 : import com.google.gerrit.extensions.common.WebLinkInfo;
35 : import com.google.gerrit.jgit.diff.ReplaceEdit;
36 : import com.google.gerrit.prettify.common.SparseFileContent;
37 : import com.google.gerrit.server.change.FileContentUtil;
38 : import com.google.gerrit.server.project.ProjectState;
39 : import java.util.List;
40 : import java.util.Optional;
41 : import java.util.Set;
42 : import org.eclipse.jgit.diff.Edit;
43 :
44 : /** Creates and fills a new {@link DiffInfo} object based on diff between files. */
45 : public class DiffInfoCreator {
46 10 : private static final FluentLogger logger = FluentLogger.forEnclosingClass();
47 :
48 10 : private static final ImmutableMap<Patch.ChangeType, ChangeType> CHANGE_TYPE =
49 10 : Maps.immutableEnumMap(
50 : new ImmutableMap.Builder<Patch.ChangeType, ChangeType>()
51 10 : .put(Patch.ChangeType.ADDED, ChangeType.ADDED)
52 10 : .put(Patch.ChangeType.MODIFIED, ChangeType.MODIFIED)
53 10 : .put(Patch.ChangeType.DELETED, ChangeType.DELETED)
54 10 : .put(Patch.ChangeType.RENAMED, ChangeType.RENAMED)
55 10 : .put(Patch.ChangeType.COPIED, ChangeType.COPIED)
56 10 : .put(Patch.ChangeType.REWRITE, ChangeType.REWRITE)
57 10 : .build());
58 :
59 : private final DiffWebLinksProvider webLinksProvider;
60 : private final boolean intraline;
61 : private final ProjectState state;
62 :
63 : public DiffInfoCreator(
64 10 : ProjectState state, DiffWebLinksProvider webLinksProvider, boolean intraline) {
65 10 : this.webLinksProvider = webLinksProvider;
66 10 : this.state = state;
67 10 : this.intraline = intraline;
68 10 : }
69 :
70 : /* Returns the {@link DiffInfo} to display for end-users */
71 : public DiffInfo create(PatchScript ps, DiffSide sideA, DiffSide sideB) {
72 10 : DiffInfo result = new DiffInfo();
73 :
74 10 : ImmutableList<DiffWebLinkInfo> links = webLinksProvider.getDiffLinks();
75 10 : result.webLinks = links.isEmpty() ? null : links;
76 10 : ImmutableList<WebLinkInfo> editLinks = webLinksProvider.getEditWebLinks();
77 10 : result.editWebLinks = editLinks.isEmpty() ? null : editLinks;
78 :
79 10 : if (ps.isBinary()) {
80 0 : result.binary = true;
81 : }
82 10 : result.metaA = createFileMeta(sideA).orElse(null);
83 10 : result.metaB = createFileMeta(sideB).orElse(null);
84 :
85 10 : if (intraline) {
86 4 : if (ps.hasIntralineTimeout()) {
87 0 : result.intralineStatus = IntraLineStatus.TIMEOUT;
88 4 : } else if (ps.hasIntralineFailure()) {
89 0 : result.intralineStatus = IntraLineStatus.FAILURE;
90 : } else {
91 4 : result.intralineStatus = IntraLineStatus.OK;
92 : }
93 4 : logger.atFine().log("intralineStatus = %s", result.intralineStatus);
94 : }
95 :
96 10 : result.changeType = CHANGE_TYPE.get(ps.getChangeType());
97 10 : logger.atFine().log("changeType = %s", result.changeType);
98 10 : if (result.changeType == null) {
99 0 : throw new IllegalStateException("unknown change type: " + ps.getChangeType());
100 : }
101 :
102 10 : if (ps.getPatchHeader().size() > 0) {
103 8 : result.diffHeader = ps.getPatchHeader();
104 : }
105 10 : result.content = calculateDiffContentEntries(ps);
106 10 : return result;
107 : }
108 :
109 : private static List<ContentEntry> calculateDiffContentEntries(PatchScript ps) {
110 10 : ContentCollector contentCollector = new ContentCollector(ps);
111 10 : Set<Edit> editsDueToRebase = ps.getEditsDueToRebase();
112 10 : for (Edit edit : ps.getEdits()) {
113 10 : logger.atFine().log("next edit = %s", edit);
114 :
115 10 : if (edit.getType() == Edit.Type.EMPTY) {
116 3 : logger.atFine().log("skip empty edit");
117 3 : continue;
118 : }
119 10 : contentCollector.addCommon(edit.getBeginA());
120 :
121 10 : checkState(
122 10 : contentCollector.nextA == edit.getBeginA(),
123 : "nextA = %s; want %s",
124 : contentCollector.nextA,
125 10 : edit.getBeginA());
126 10 : checkState(
127 10 : contentCollector.nextB == edit.getBeginB(),
128 : "nextB = %s; want %s",
129 : contentCollector.nextB,
130 10 : edit.getBeginB());
131 10 : switch (edit.getType()) {
132 : case DELETE:
133 : case INSERT:
134 : case REPLACE:
135 : List<Edit> internalEdit =
136 10 : edit instanceof ReplaceEdit ? ((ReplaceEdit) edit).getInternalEdits() : null;
137 10 : boolean dueToRebase = editsDueToRebase.contains(edit);
138 10 : contentCollector.addDiff(edit.getEndA(), edit.getEndB(), internalEdit, dueToRebase);
139 10 : break;
140 : case EMPTY:
141 : default:
142 0 : throw new IllegalStateException();
143 : }
144 10 : }
145 10 : contentCollector.addCommon(ps.getA().getSize());
146 :
147 10 : return contentCollector.lines;
148 : }
149 :
150 : private Optional<FileMeta> createFileMeta(DiffSide side) {
151 10 : PatchScriptFileInfo fileInfo = side.fileInfo();
152 10 : if (fileInfo.displayMethod == DisplayMethod.NONE) {
153 7 : return Optional.empty();
154 : }
155 10 : FileMeta result = new FileMeta();
156 10 : result.name = side.fileName();
157 10 : result.contentType =
158 10 : FileContentUtil.resolveContentType(
159 10 : state, side.fileName(), fileInfo.mode, fileInfo.mimeType);
160 10 : result.lines = fileInfo.content.getSize();
161 10 : ImmutableList<WebLinkInfo> fileLinks = webLinksProvider.getFileWebLinks(side.type());
162 10 : result.webLinks = fileLinks.isEmpty() ? null : fileLinks;
163 10 : result.commitId = fileInfo.commitId;
164 10 : return Optional.of(result);
165 : }
166 :
167 : private static class ContentCollector {
168 :
169 : private final List<ContentEntry> lines;
170 : private final SparseFileContent.Accessor fileA;
171 : private final SparseFileContent.Accessor fileB;
172 : private final boolean ignoreWS;
173 :
174 : private int nextA;
175 : private int nextB;
176 :
177 10 : ContentCollector(PatchScript ps) {
178 10 : lines = Lists.newArrayListWithExpectedSize(ps.getEdits().size() + 2);
179 10 : fileA = ps.getA().createAccessor();
180 10 : fileB = ps.getB().createAccessor();
181 10 : ignoreWS = ps.isIgnoreWhitespace();
182 10 : }
183 :
184 : void addCommon(int end) {
185 10 : logger.atFine().log("addCommon: end = %d", end);
186 :
187 10 : end = Math.min(end, fileA.getSize());
188 10 : logger.atFine().log("end = %d", end);
189 :
190 10 : if (nextA >= end) {
191 9 : logger.atFine().log("nextA >= end: nextA = %d, end = %d", nextA, end);
192 9 : return;
193 : }
194 :
195 5 : while (nextA < end) {
196 5 : logger.atFine().log("nextA < end: nextA = %d, end = %d", nextA, end);
197 :
198 5 : if (!fileA.contains(nextA)) {
199 2 : logger.atFine().log("fileA does not contain nextA: nextA = %d", nextA);
200 :
201 2 : int endRegion = Math.min(end, nextA == 0 ? fileA.first() : fileA.next(nextA - 1));
202 2 : int len = endRegion - nextA;
203 2 : entry().skip = len;
204 2 : nextA = endRegion;
205 2 : nextB += len;
206 :
207 2 : logger.atFine().log("setting: nextA = %d, nextB = %d", nextA, nextB);
208 2 : continue;
209 : }
210 :
211 5 : ContentEntry e = null;
212 5 : for (int i = nextA; i == nextA && i < end; i = fileA.next(i), nextA++, nextB++) {
213 5 : if (ignoreWS && fileB.contains(nextB)) {
214 0 : if (e == null || e.common == null) {
215 0 : logger.atFine().log("create new common entry: nextA = %d, nextB = %d", nextA, nextB);
216 0 : e = entry();
217 0 : e.a = Lists.newArrayListWithCapacity(end - nextA);
218 0 : e.b = Lists.newArrayListWithCapacity(end - nextA);
219 0 : e.common = true;
220 : }
221 0 : e.a.add(fileA.get(nextA));
222 0 : e.b.add(fileB.get(nextB));
223 : } else {
224 5 : if (e == null || e.common != null) {
225 5 : logger.atFine().log(
226 : "create new non-common entry: nextA = %d, nextB = %d", nextA, nextB);
227 5 : e = entry();
228 5 : e.ab = Lists.newArrayListWithCapacity(end - nextA);
229 : }
230 5 : e.ab.add(fileA.get(nextA));
231 : }
232 : }
233 5 : }
234 5 : }
235 :
236 : void addDiff(int endA, int endB, List<Edit> internalEdit, boolean dueToRebase) {
237 10 : logger.atFine().log(
238 : "addDiff: endA = %d, endB = %d, numberOfInternalEdits = %d, dueToRebase = %s",
239 10 : endA, endB, internalEdit != null ? internalEdit.size() : 0, dueToRebase);
240 :
241 10 : int lenA = endA - nextA;
242 10 : int lenB = endB - nextB;
243 10 : logger.atFine().log("lenA = %d, lenB = %d", lenA, lenB);
244 10 : checkState(lenA > 0 || lenB > 0);
245 :
246 10 : logger.atFine().log("create non-common entry");
247 10 : ContentEntry e = entry();
248 10 : if (lenA > 0) {
249 6 : logger.atFine().log("lenA > 0: lenA = %d", lenA);
250 6 : e.a = Lists.newArrayListWithCapacity(lenA);
251 6 : for (; nextA < endA; nextA++) {
252 6 : e.a.add(fileA.get(nextA));
253 : }
254 : }
255 10 : if (lenB > 0) {
256 10 : logger.atFine().log("lenB > 0: lenB = %d", lenB);
257 10 : e.b = Lists.newArrayListWithCapacity(lenB);
258 10 : for (; nextB < endB; nextB++) {
259 10 : e.b.add(fileB.get(nextB));
260 : }
261 : }
262 10 : if (internalEdit != null && !internalEdit.isEmpty()) {
263 3 : logger.atFine().log("processing internal edits");
264 :
265 3 : e.editA = Lists.newArrayListWithCapacity(internalEdit.size() * 2);
266 3 : e.editB = Lists.newArrayListWithCapacity(internalEdit.size() * 2);
267 3 : int lastA = 0;
268 3 : int lastB = 0;
269 3 : for (Edit edit : internalEdit) {
270 3 : logger.atFine().log("internal edit = %s", edit);
271 :
272 3 : if (edit.getBeginA() != edit.getEndA()) {
273 3 : logger.atFine().log(
274 : "edit.getBeginA() != edit.getEndA(): edit.getBeginA() = %d, edit.getEndA() = %d",
275 3 : edit.getBeginA(), edit.getEndA());
276 3 : e.editA.add(
277 3 : ImmutableList.of(edit.getBeginA() - lastA, edit.getEndA() - edit.getBeginA()));
278 3 : lastA = edit.getEndA();
279 3 : logger.atFine().log("lastA = %d", lastA);
280 : }
281 3 : if (edit.getBeginB() != edit.getEndB()) {
282 3 : logger.atFine().log(
283 : "edit.getBeginB() != edit.getEndB(): edit.getBeginB() = %d, edit.getEndB() = %d",
284 3 : edit.getBeginB(), edit.getEndB());
285 3 : e.editB.add(
286 3 : ImmutableList.of(edit.getBeginB() - lastB, edit.getEndB() - edit.getBeginB()));
287 3 : lastB = edit.getEndB();
288 3 : logger.atFine().log("lastB = %d", lastB);
289 : }
290 3 : }
291 : }
292 10 : e.dueToRebase = dueToRebase ? true : null;
293 10 : }
294 :
295 : private ContentEntry entry() {
296 10 : ContentEntry e = new ContentEntry();
297 10 : lines.add(e);
298 10 : return e;
299 : }
300 : }
301 : }
|