Line data Source code
1 : // Copyright (C) 2009 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.patch;
16 :
17 : import static com.google.common.base.Preconditions.checkArgument;
18 : import static com.google.common.base.Preconditions.checkState;
19 :
20 : import com.google.common.collect.ImmutableList;
21 : import com.google.common.flogger.FluentLogger;
22 : import com.google.gerrit.common.Nullable;
23 : import com.google.gerrit.common.data.PatchScript;
24 : import com.google.gerrit.entities.Change;
25 : import com.google.gerrit.entities.PatchSet;
26 : import com.google.gerrit.entities.Project;
27 : import com.google.gerrit.exceptions.StorageException;
28 : import com.google.gerrit.extensions.client.DiffPreferencesInfo;
29 : import com.google.gerrit.extensions.restapi.AuthException;
30 : import com.google.gerrit.server.CurrentUser;
31 : import com.google.gerrit.server.PatchSetUtil;
32 : import com.google.gerrit.server.edit.ChangeEdit;
33 : import com.google.gerrit.server.edit.ChangeEditUtil;
34 : import com.google.gerrit.server.git.GitRepositoryManager;
35 : import com.google.gerrit.server.git.LargeObjectException;
36 : import com.google.gerrit.server.notedb.ChangeNotes;
37 : import com.google.gerrit.server.patch.PatchScriptBuilder.IntraLineDiffCalculatorResult;
38 : import com.google.gerrit.server.patch.filediff.FileDiffOutput;
39 : import com.google.gerrit.server.permissions.ChangePermission;
40 : import com.google.gerrit.server.permissions.PermissionBackend;
41 : import com.google.gerrit.server.permissions.PermissionBackendException;
42 : import com.google.gerrit.server.project.InvalidChangeOperationException;
43 : import com.google.gerrit.server.project.NoSuchChangeException;
44 : import com.google.gerrit.server.project.ProjectCache;
45 : import com.google.gerrit.server.project.ProjectState;
46 : import com.google.inject.Provider;
47 : import com.google.inject.assistedinject.Assisted;
48 : import com.google.inject.assistedinject.AssistedInject;
49 : import java.io.IOException;
50 : import java.util.Optional;
51 : import java.util.Set;
52 : import java.util.concurrent.Callable;
53 : import org.eclipse.jgit.diff.Edit;
54 : import org.eclipse.jgit.errors.RepositoryNotFoundException;
55 : import org.eclipse.jgit.lib.ObjectId;
56 : import org.eclipse.jgit.lib.Repository;
57 :
58 : public class PatchScriptFactory implements Callable<PatchScript> {
59 :
60 12 : private static final FluentLogger logger = FluentLogger.forEnclosingClass();
61 :
62 : public interface Factory {
63 :
64 : PatchScriptFactory create(
65 : ChangeNotes notes,
66 : String fileName,
67 : @Assisted("patchSetA") PatchSet.Id patchSetA,
68 : @Assisted("patchSetB") PatchSet.Id patchSetB,
69 : DiffPreferencesInfo diffPrefs,
70 : CurrentUser currentUser);
71 :
72 : PatchScriptFactory create(
73 : ChangeNotes notes,
74 : String fileName,
75 : int parentNum,
76 : PatchSet.Id patchSetB,
77 : DiffPreferencesInfo diffPrefs,
78 : CurrentUser currentUser);
79 : }
80 :
81 : private final GitRepositoryManager repoManager;
82 : private final PatchSetUtil psUtil;
83 : private final Provider<PatchScriptBuilder> builderFactory;
84 : private final PatchListCache patchListCache;
85 :
86 : private final String fileName;
87 : @Nullable private final PatchSet.Id psa;
88 : private final int parentNum;
89 : private final PatchSet.Id psb;
90 : private final DiffPreferencesInfo diffPrefs;
91 : private final CurrentUser currentUser;
92 :
93 : private final ChangeEditUtil editReader;
94 : private final PermissionBackend permissionBackend;
95 : private final ProjectCache projectCache;
96 : private final DiffOperations diffOperations;
97 :
98 : private final Change.Id changeId;
99 :
100 : private ChangeNotes notes;
101 :
102 : @AssistedInject
103 : PatchScriptFactory(
104 : GitRepositoryManager grm,
105 : PatchSetUtil psUtil,
106 : Provider<PatchScriptBuilder> builderFactory,
107 : PatchListCache patchListCache,
108 : ChangeEditUtil editReader,
109 : PermissionBackend permissionBackend,
110 : ProjectCache projectCache,
111 : DiffOperations diffOperations,
112 : @Assisted ChangeNotes notes,
113 : @Assisted String fileName,
114 : @Assisted("patchSetA") @Nullable PatchSet.Id patchSetA,
115 : @Assisted("patchSetB") PatchSet.Id patchSetB,
116 : @Assisted DiffPreferencesInfo diffPrefs,
117 12 : @Assisted CurrentUser currentUser) {
118 12 : this.repoManager = grm;
119 12 : this.psUtil = psUtil;
120 12 : this.builderFactory = builderFactory;
121 12 : this.patchListCache = patchListCache;
122 12 : this.notes = notes;
123 12 : this.editReader = editReader;
124 12 : this.permissionBackend = permissionBackend;
125 12 : this.projectCache = projectCache;
126 12 : this.diffOperations = diffOperations;
127 :
128 12 : this.fileName = fileName;
129 12 : this.psa = patchSetA;
130 12 : this.parentNum = 0;
131 12 : this.psb = patchSetB;
132 12 : this.diffPrefs = diffPrefs;
133 12 : this.currentUser = currentUser;
134 12 : changeId = patchSetB.changeId();
135 12 : }
136 :
137 : @AssistedInject
138 : PatchScriptFactory(
139 : GitRepositoryManager grm,
140 : PatchSetUtil psUtil,
141 : Provider<PatchScriptBuilder> builderFactory,
142 : PatchListCache patchListCache,
143 : ChangeEditUtil editReader,
144 : PermissionBackend permissionBackend,
145 : ProjectCache projectCache,
146 : DiffOperations diffOperations,
147 : @Assisted ChangeNotes notes,
148 : @Assisted String fileName,
149 : @Assisted int parentNum,
150 : @Assisted PatchSet.Id patchSetB,
151 : @Assisted DiffPreferencesInfo diffPrefs,
152 3 : @Assisted CurrentUser currentUser) {
153 3 : this.repoManager = grm;
154 3 : this.psUtil = psUtil;
155 3 : this.builderFactory = builderFactory;
156 3 : this.patchListCache = patchListCache;
157 3 : this.notes = notes;
158 3 : this.editReader = editReader;
159 3 : this.permissionBackend = permissionBackend;
160 3 : this.projectCache = projectCache;
161 3 : this.diffOperations = diffOperations;
162 :
163 3 : this.fileName = fileName;
164 3 : this.psa = null;
165 3 : this.parentNum = parentNum;
166 3 : this.psb = patchSetB;
167 3 : this.diffPrefs = diffPrefs;
168 3 : this.currentUser = currentUser;
169 3 : changeId = patchSetB.changeId();
170 3 : checkArgument(parentNum > 0, "parentNum must be > 0");
171 3 : }
172 :
173 : @Override
174 : public PatchScript call()
175 : throws LargeObjectException, AuthException, InvalidChangeOperationException, IOException,
176 : PermissionBackendException {
177 :
178 12 : if (!permissionBackend.user(currentUser).change(notes).test(ChangePermission.READ)) {
179 0 : throw new NoSuchChangeException(changeId);
180 : }
181 :
182 12 : if (!projectCache
183 12 : .get(notes.getProjectName())
184 12 : .map(ProjectState::statePermitsRead)
185 12 : .orElse(false)) {
186 0 : throw new NoSuchChangeException(changeId);
187 : }
188 :
189 12 : try (Repository git = repoManager.openRepository(notes.getProjectName())) {
190 : try {
191 12 : validatePatchSetId(psa);
192 12 : validatePatchSetId(psb);
193 :
194 12 : ObjectId aId = getAId().orElse(null);
195 12 : ObjectId bId = getBId().orElse(null);
196 12 : if (bId == null) {
197 : // Change edit: create synthetic PatchSet corresponding to the edit.
198 1 : Optional<ChangeEdit> edit = editReader.byChange(notes);
199 1 : if (!edit.isPresent()) {
200 0 : throw new NoSuchChangeException(notes.getChangeId());
201 : }
202 1 : bId = edit.get().getEditCommit();
203 : }
204 12 : return getPatchScript(git, aId, bId);
205 0 : } catch (DiffNotAvailableException e) {
206 0 : throw new StorageException(e);
207 0 : } catch (IOException e) {
208 0 : logger.atSevere().withCause(e).log("File content unavailable");
209 0 : throw new NoSuchChangeException(changeId, e);
210 0 : } catch (org.eclipse.jgit.errors.LargeObjectException err) {
211 0 : throw new LargeObjectException("File content is too large", err);
212 : }
213 0 : } catch (RepositoryNotFoundException e) {
214 0 : logger.atSevere().withCause(e).log("Repository %s not found", notes.getProjectName());
215 0 : throw new NoSuchChangeException(changeId, e);
216 0 : } catch (IOException e) {
217 0 : logger.atSevere().withCause(e).log("Cannot open repository %s", notes.getProjectName());
218 0 : throw new NoSuchChangeException(changeId, e);
219 : }
220 : }
221 :
222 : private PatchScript getPatchScript(Repository git, ObjectId aId, ObjectId bId)
223 : throws IOException, DiffNotAvailableException {
224 : FileDiffOutput fileDiffOutput =
225 12 : aId == null
226 7 : ? diffOperations.getModifiedFileAgainstParent(
227 7 : notes.getProjectName(), bId, parentNum, fileName, diffPrefs.ignoreWhitespace)
228 12 : : diffOperations.getModifiedFile(
229 7 : notes.getProjectName(), aId, bId, fileName, diffPrefs.ignoreWhitespace);
230 12 : return newBuilder().toPatchScript(git, fileDiffOutput);
231 : }
232 :
233 : private Optional<ObjectId> getAId() {
234 12 : if (psa == null) {
235 7 : return Optional.empty();
236 : }
237 7 : checkState(parentNum == 0, "expected no parentNum when psa is present");
238 7 : checkArgument(psa.get() != 0, "edit not supported for left side");
239 7 : return Optional.of(getCommitId(psa));
240 : }
241 :
242 : private Optional<ObjectId> getBId() {
243 12 : if (psb.get() == 0) {
244 : // Change edit
245 1 : return Optional.empty();
246 : }
247 11 : return Optional.of(getCommitId(psb));
248 : }
249 :
250 : private PatchScriptBuilder newBuilder() {
251 12 : final PatchScriptBuilder b = builderFactory.get();
252 12 : b.setDiffPrefs(diffPrefs);
253 12 : if (diffPrefs.intralineDifference) {
254 6 : b.setIntraLineDiffCalculator(
255 6 : new IntraLineDiffCalculator(patchListCache, notes.getProjectName(), diffPrefs));
256 : }
257 12 : return b;
258 : }
259 :
260 : private ObjectId getCommitId(PatchSet.Id psId) {
261 11 : PatchSet ps = psUtil.get(notes, psId);
262 11 : if (ps == null) {
263 0 : throw new NoSuchChangeException(psId.changeId());
264 : }
265 11 : return ps.commitId();
266 : }
267 :
268 : private void validatePatchSetId(PatchSet.Id psId) throws NoSuchChangeException {
269 12 : if (psId == null) { // OK, means use base;
270 12 : } else if (changeId.equals(psId.changeId())) { // OK, same change;
271 : } else {
272 0 : throw new NoSuchChangeException(changeId);
273 : }
274 12 : }
275 :
276 : private static class IntraLineDiffCalculator
277 : implements PatchScriptBuilder.IntraLineDiffCalculator {
278 :
279 : private final PatchListCache patchListCache;
280 : private final Project.NameKey projectKey;
281 : private final DiffPreferencesInfo diffPrefs;
282 :
283 : IntraLineDiffCalculator(
284 6 : PatchListCache patchListCache, Project.NameKey projectKey, DiffPreferencesInfo diffPrefs) {
285 6 : this.patchListCache = patchListCache;
286 6 : this.projectKey = projectKey;
287 6 : this.diffPrefs = diffPrefs;
288 6 : }
289 :
290 : @Override
291 : public IntraLineDiffCalculatorResult calculateIntraLineDiff(
292 : ImmutableList<Edit> edits,
293 : Set<Edit> editsDueToRebase,
294 : ObjectId aId,
295 : ObjectId bId,
296 : Text aSrc,
297 : Text bSrc,
298 : ObjectId bTreeId,
299 : String bPath) {
300 5 : IntraLineDiff d =
301 5 : patchListCache.getIntraLineDiff(
302 5 : IntraLineDiffKey.create(aId, bId, diffPrefs.ignoreWhitespace),
303 5 : IntraLineDiffArgs.create(
304 : aSrc, bSrc, edits, editsDueToRebase, projectKey, bTreeId, bPath));
305 5 : if (d == null) {
306 0 : return IntraLineDiffCalculatorResult.FAILURE;
307 : }
308 5 : switch (d.getStatus()) {
309 : case EDIT_LIST:
310 5 : return IntraLineDiffCalculatorResult.success(d.getEdits());
311 :
312 : case ERROR:
313 0 : return IntraLineDiffCalculatorResult.FAILURE;
314 :
315 : case TIMEOUT:
316 0 : return IntraLineDiffCalculatorResult.TIMEOUT;
317 :
318 : case DISABLED:
319 : default:
320 0 : return IntraLineDiffCalculatorResult.NO_RESULT;
321 : }
322 : }
323 : }
324 : }
|