Line data Source code
1 : // Copyright (C) 2013 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.restapi.change;
16 :
17 : import static com.google.gerrit.server.project.ProjectCache.illegalState;
18 :
19 : import com.google.common.base.MoreObjects;
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.PatchSet;
25 : import com.google.gerrit.entities.Project;
26 : import com.google.gerrit.extensions.client.DiffPreferencesInfo;
27 : import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
28 : import com.google.gerrit.extensions.common.DiffInfo;
29 : import com.google.gerrit.extensions.common.DiffWebLinkInfo;
30 : import com.google.gerrit.extensions.common.WebLinkInfo;
31 : import com.google.gerrit.extensions.restapi.AuthException;
32 : import com.google.gerrit.extensions.restapi.BadRequestException;
33 : import com.google.gerrit.extensions.restapi.CacheControl;
34 : import com.google.gerrit.extensions.restapi.IdString;
35 : import com.google.gerrit.extensions.restapi.ResourceConflictException;
36 : import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
37 : import com.google.gerrit.extensions.restapi.Response;
38 : import com.google.gerrit.extensions.restapi.RestReadView;
39 : import com.google.gerrit.server.CurrentUser;
40 : import com.google.gerrit.server.WebLinks;
41 : import com.google.gerrit.server.change.FileResource;
42 : import com.google.gerrit.server.change.RevisionResource;
43 : import com.google.gerrit.server.diff.DiffInfoCreator;
44 : import com.google.gerrit.server.diff.DiffSide;
45 : import com.google.gerrit.server.diff.DiffWebLinksProvider;
46 : import com.google.gerrit.server.git.LargeObjectException;
47 : import com.google.gerrit.server.notedb.ChangeNotes;
48 : import com.google.gerrit.server.patch.PatchScriptFactory;
49 : import com.google.gerrit.server.permissions.PermissionBackendException;
50 : import com.google.gerrit.server.project.InvalidChangeOperationException;
51 : import com.google.gerrit.server.project.NoSuchChangeException;
52 : import com.google.gerrit.server.project.ProjectCache;
53 : import com.google.gerrit.server.project.ProjectState;
54 : import com.google.inject.Inject;
55 : import com.google.inject.Provider;
56 : import java.io.IOException;
57 : import java.util.concurrent.TimeUnit;
58 : import org.kohsuke.args4j.CmdLineParser;
59 : import org.kohsuke.args4j.Option;
60 : import org.kohsuke.args4j.OptionDef;
61 : import org.kohsuke.args4j.spi.OptionHandler;
62 : import org.kohsuke.args4j.spi.Parameters;
63 : import org.kohsuke.args4j.spi.Setter;
64 :
65 : public class GetDiff implements RestReadView<FileResource> {
66 16 : private static final FluentLogger logger = FluentLogger.forEnclosingClass();
67 :
68 : private final ProjectCache projectCache;
69 : private final PatchScriptFactory.Factory patchScriptFactoryFactory;
70 : private final Revisions revisions;
71 : private final WebLinks webLinks;
72 : private final Provider<CurrentUser> currentUser;
73 :
74 : @Option(name = "--base", metaVar = "REVISION")
75 : String base;
76 :
77 : /** 1-based index of the parent's position in the commit object. */
78 : @Option(name = "--parent", metaVar = "parent-number")
79 : int parentNum;
80 :
81 : @Deprecated
82 : @Option(name = "--ignore-whitespace")
83 : IgnoreWhitespace ignoreWhitespace;
84 :
85 : @Option(name = "--whitespace")
86 : Whitespace whitespace;
87 :
88 : // TODO(hiesel): Remove parameter when not used by callers (e.g. frontend) anymore.
89 : @Option(name = "--context", handler = ContextOptionHandler.class)
90 : int context;
91 :
92 : @Option(name = "--intraline")
93 : boolean intraline;
94 :
95 : @Inject
96 : GetDiff(
97 : ProjectCache projectCache,
98 : PatchScriptFactory.Factory patchScriptFactoryFactory,
99 : Revisions revisions,
100 : WebLinks webLinks,
101 16 : Provider<CurrentUser> currentUser) {
102 16 : this.projectCache = projectCache;
103 16 : this.patchScriptFactoryFactory = patchScriptFactoryFactory;
104 16 : this.revisions = revisions;
105 16 : this.webLinks = webLinks;
106 16 : this.currentUser = currentUser;
107 16 : }
108 :
109 : @Override
110 : public Response<DiffInfo> apply(FileResource resource)
111 : throws BadRequestException, ResourceConflictException, ResourceNotFoundException,
112 : AuthException, InvalidChangeOperationException, IOException, PermissionBackendException {
113 8 : DiffPreferencesInfo prefs = new DiffPreferencesInfo();
114 8 : if (whitespace != null) {
115 2 : prefs.ignoreWhitespace = whitespace;
116 8 : } else if (ignoreWhitespace != null) {
117 0 : prefs.ignoreWhitespace = ignoreWhitespace.whitespace;
118 : } else {
119 8 : prefs.ignoreWhitespace = Whitespace.IGNORE_LEADING_AND_TRAILING;
120 : }
121 8 : prefs.intralineDifference = intraline;
122 8 : logger.atFine().log(
123 : "diff preferences: ignoreWhitespace = %s, intralineDifference = %s",
124 : prefs.ignoreWhitespace, prefs.intralineDifference);
125 :
126 : PatchScriptFactory psf;
127 8 : PatchSet basePatchSet = null;
128 8 : PatchSet.Id pId = resource.getPatchKey().patchSetId();
129 8 : String fileName = resource.getPatchKey().fileName();
130 8 : logger.atFine().log(
131 : "patchSetId = %d, fileName = %s, base = %s, parentNum = %d",
132 8 : pId.get(), fileName, base, parentNum);
133 8 : ChangeNotes notes = resource.getRevision().getNotes();
134 8 : if (base != null) {
135 3 : RevisionResource baseResource =
136 3 : revisions.parse(resource.getRevision().getChangeResource(), IdString.fromDecoded(base));
137 3 : basePatchSet = baseResource.getPatchSet();
138 3 : if (basePatchSet.id().get() == 0) {
139 2 : throw new BadRequestException("edit not allowed as base");
140 : }
141 3 : psf =
142 3 : patchScriptFactoryFactory.create(
143 3 : notes, fileName, basePatchSet.id(), pId, prefs, currentUser.get());
144 8 : } else if (parentNum > 0) {
145 3 : psf =
146 3 : patchScriptFactoryFactory.create(
147 3 : notes, fileName, parentNum, pId, prefs, currentUser.get());
148 : } else {
149 7 : psf = patchScriptFactoryFactory.create(notes, fileName, null, pId, prefs, currentUser.get());
150 : }
151 :
152 : try {
153 8 : PatchScript ps = psf.call();
154 8 : Project.NameKey projectName = resource.getRevision().getChange().getProject();
155 8 : ProjectState state = projectCache.get(projectName).orElseThrow(illegalState(projectName));
156 8 : DiffSide sideA =
157 8 : DiffSide.create(
158 8 : ps.getFileInfoA(),
159 8 : MoreObjects.firstNonNull(ps.getOldName(), ps.getNewName()),
160 : DiffSide.Type.SIDE_A);
161 8 : DiffSide sideB = DiffSide.create(ps.getFileInfoB(), ps.getNewName(), DiffSide.Type.SIDE_B);
162 8 : DiffWebLinksProvider webLinksProvider =
163 : new DiffWebLinksProviderImpl(sideA, sideB, projectName, basePatchSet, webLinks, resource);
164 8 : DiffInfoCreator diffInfoCreator = new DiffInfoCreator(state, webLinksProvider, intraline);
165 8 : DiffInfo result = diffInfoCreator.create(ps, sideA, sideB);
166 :
167 8 : Response<DiffInfo> r = Response.ok(result);
168 8 : if (resource.isCacheable()) {
169 4 : r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));
170 : }
171 8 : return r;
172 0 : } catch (NoSuchChangeException e) {
173 0 : throw new ResourceNotFoundException(e.getMessage(), e);
174 0 : } catch (LargeObjectException e) {
175 0 : throw new ResourceConflictException(e.getMessage(), e);
176 : }
177 : }
178 :
179 : private static class DiffWebLinksProviderImpl implements DiffWebLinksProvider {
180 :
181 : private final WebLinks webLinks;
182 : private final Project.NameKey projectName;
183 : private final DiffSide sideA;
184 : private final DiffSide sideB;
185 : private final String revA;
186 : private final String revB;
187 : private final String hashA;
188 : private final String hashB;
189 : private final FileResource resource;
190 : @Nullable private final PatchSet basePatchSet;
191 :
192 : DiffWebLinksProviderImpl(
193 : DiffSide sideA,
194 : DiffSide sideB,
195 : Project.NameKey projectName,
196 : @Nullable PatchSet basePatchSet,
197 : WebLinks webLinks,
198 8 : FileResource resource) {
199 8 : this.projectName = projectName;
200 8 : this.webLinks = webLinks;
201 8 : this.basePatchSet = basePatchSet;
202 8 : this.resource = resource;
203 8 : this.sideA = sideA;
204 8 : this.sideB = sideB;
205 :
206 8 : revA = basePatchSet != null ? basePatchSet.refName() : sideA.fileInfo().commitId;
207 8 : hashA = sideA.fileInfo().commitId;
208 :
209 8 : RevisionResource revision = resource.getRevision();
210 8 : revB =
211 : revision
212 8 : .getEdit()
213 8 : .map(edit -> edit.getRefName())
214 8 : .orElseGet(() -> revision.getPatchSet().refName());
215 8 : hashB = sideB.fileInfo().commitId;
216 :
217 8 : logger.atFine().log("revA = %s, hashA = %s, revB = %s, hashB = %s", revA, hashA, revB, hashB);
218 8 : }
219 :
220 : @Override
221 : public ImmutableList<DiffWebLinkInfo> getDiffLinks() {
222 8 : return webLinks.getDiffLinks(
223 8 : projectName.get(),
224 8 : resource.getPatchKey().patchSetId().changeId().get(),
225 8 : basePatchSet != null ? basePatchSet.id().get() : null,
226 : revA,
227 8 : sideA.fileName(),
228 8 : resource.getPatchKey().patchSetId().get(),
229 : revB,
230 8 : sideB.fileName());
231 : }
232 :
233 : @Override
234 : public ImmutableList<WebLinkInfo> getEditWebLinks() {
235 8 : return webLinks.getEditLinks(projectName.get(), revB, sideB.fileName());
236 : }
237 :
238 : @Override
239 : public ImmutableList<WebLinkInfo> getFileWebLinks(DiffSide.Type type) {
240 8 : String rev = getSideRev(type);
241 8 : String hash = getSideHash(type);
242 8 : DiffSide side = getDiffSide(type);
243 8 : return webLinks.getFileLinks(projectName.get(), rev, hash, side.fileName());
244 : }
245 :
246 : private String getSideRev(DiffSide.Type sideType) {
247 8 : return DiffSide.Type.SIDE_A == sideType ? revA : revB;
248 : }
249 :
250 : private String getSideHash(DiffSide.Type sideType) {
251 8 : return DiffSide.Type.SIDE_A == sideType ? hashA : hashB;
252 : }
253 :
254 : private DiffSide getDiffSide(DiffSide.Type sideType) {
255 8 : return DiffSide.Type.SIDE_A == sideType ? sideA : sideB;
256 : }
257 : }
258 :
259 : public GetDiff setBase(String base) {
260 3 : this.base = base;
261 3 : return this;
262 : }
263 :
264 : public GetDiff setParent(int parentNum) {
265 3 : this.parentNum = parentNum;
266 3 : return this;
267 : }
268 :
269 : public GetDiff setIntraline(boolean intraline) {
270 2 : this.intraline = intraline;
271 2 : return this;
272 : }
273 :
274 : public GetDiff setWhitespace(Whitespace whitespace) {
275 2 : this.whitespace = whitespace;
276 2 : return this;
277 : }
278 :
279 0 : @Deprecated
280 : enum IgnoreWhitespace {
281 0 : NONE(DiffPreferencesInfo.Whitespace.IGNORE_NONE),
282 0 : TRAILING(DiffPreferencesInfo.Whitespace.IGNORE_TRAILING),
283 0 : CHANGED(DiffPreferencesInfo.Whitespace.IGNORE_LEADING_AND_TRAILING),
284 0 : ALL(DiffPreferencesInfo.Whitespace.IGNORE_ALL);
285 :
286 : private final DiffPreferencesInfo.Whitespace whitespace;
287 :
288 0 : IgnoreWhitespace(DiffPreferencesInfo.Whitespace whitespace) {
289 0 : this.whitespace = whitespace;
290 0 : }
291 : }
292 :
293 : // TODO(hiesel): Remove this class once clients don't send the context parameter anymore.
294 : public static class ContextOptionHandler extends OptionHandler<Short> {
295 :
296 : public ContextOptionHandler(CmdLineParser parser, OptionDef option, Setter<Short> setter) {
297 2 : super(parser, option, setter);
298 2 : }
299 :
300 : @Override
301 : public final int parseArguments(Parameters params) {
302 : // Return 1 to consume the context parameter.
303 0 : return 1;
304 : }
305 :
306 : @Override
307 : public final String getDefaultMetaVariable() {
308 0 : return "ignored";
309 : }
310 : }
311 : }
|