blob: 5b091f21809d3d96e30dfcd81ff4b26fa111c26f [file] [log] [blame]
Dave Borowitz9de65952012-08-13 16:09:45 -07001// Copyright 2012 Google Inc. All Rights Reserved.
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
15package com.google.gitiles;
16
17import static com.google.common.base.Preconditions.checkNotNull;
18import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
19
20import com.google.common.base.Charsets;
Dave Borowitzea9bba12014-07-09 16:45:40 -070021import com.google.common.io.BaseEncoding;
Dave Borowitzf03fcea2014-04-21 17:20:33 -070022import com.google.gitiles.CommitData.Field;
Dave Borowitz2b2f34b2014-04-29 16:47:20 -070023import com.google.gitiles.DateFormatter.Format;
Dave Borowitz9de65952012-08-13 16:09:45 -070024
25import org.eclipse.jgit.diff.DiffFormatter;
26import org.eclipse.jgit.errors.IncorrectObjectTypeException;
27import org.eclipse.jgit.errors.MissingObjectException;
28import org.eclipse.jgit.http.server.ServletUtils;
Dave Borowitzf03fcea2014-04-21 17:20:33 -070029import org.eclipse.jgit.lib.FileMode;
Dave Borowitz9de65952012-08-13 16:09:45 -070030import org.eclipse.jgit.lib.ObjectId;
31import org.eclipse.jgit.lib.Repository;
32import org.eclipse.jgit.revwalk.RevCommit;
33import org.eclipse.jgit.revwalk.RevWalk;
34import org.eclipse.jgit.treewalk.AbstractTreeIterator;
35import org.eclipse.jgit.treewalk.CanonicalTreeParser;
36import org.eclipse.jgit.treewalk.EmptyTreeIterator;
Dave Borowitzf03fcea2014-04-21 17:20:33 -070037import org.eclipse.jgit.treewalk.TreeWalk;
Dave Borowitz9de65952012-08-13 16:09:45 -070038import org.eclipse.jgit.treewalk.filter.PathFilter;
39
40import java.io.IOException;
41import java.io.OutputStream;
Dave Borowitzea9bba12014-07-09 16:45:40 -070042import java.io.OutputStreamWriter;
Dave Borowitz9de65952012-08-13 16:09:45 -070043import java.util.Arrays;
44import java.util.Map;
Dave Borowitzf03fcea2014-04-21 17:20:33 -070045import java.util.Set;
Dave Borowitz9de65952012-08-13 16:09:45 -070046
47import javax.servlet.http.HttpServletRequest;
48import javax.servlet.http.HttpServletResponse;
49
50/** Serves an HTML page with all the diffs for a commit. */
51public class DiffServlet extends BaseServlet {
Chad Horohoead23f142012-11-12 09:45:39 -080052 private static final long serialVersionUID = 1L;
Dave Borowitz9de65952012-08-13 16:09:45 -070053 private static final String PLACEHOLDER = "id=\"DIFF_OUTPUT_BLOCK\"";
54
55 private final Linkifier linkifier;
56
Dave Borowitz2b2f34b2014-04-29 16:47:20 -070057 public DiffServlet(GitilesAccess.Factory accessFactory, Renderer renderer, Linkifier linkifier) {
Dave Borowitz8d6d6872014-03-16 15:18:14 -070058 super(renderer, accessFactory);
Dave Borowitz9de65952012-08-13 16:09:45 -070059 this.linkifier = checkNotNull(linkifier, "linkifier");
60 }
61
62 @Override
Dave Borowitzea9bba12014-07-09 16:45:40 -070063 protected void doGetHtml(HttpServletRequest req, HttpServletResponse res) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -070064 GitilesView view = ViewFilter.getView(req);
65 Repository repo = ServletUtils.getRepository(req);
66
67 RevWalk walk = new RevWalk(repo);
Dave Borowitz0d418f62014-04-29 11:32:25 -070068 TreeWalk tw = null;
Dave Borowitz9de65952012-08-13 16:09:45 -070069 try {
Dave Borowitzf03fcea2014-04-21 17:20:33 -070070 boolean showCommit, isFile;
Dave Borowitz9de65952012-08-13 16:09:45 -070071 AbstractTreeIterator oldTree;
72 AbstractTreeIterator newTree;
73 try {
Dave Borowitz0d418f62014-04-29 11:32:25 -070074 tw = newTreeWalk(walk, view);
75 if (tw == null && !view.getPathPart().isEmpty()) {
76 res.setStatus(SC_NOT_FOUND);
77 return;
78 }
79 isFile = tw != null && isFile(tw);
80
Dave Borowitz9de65952012-08-13 16:09:45 -070081 // If we are viewing the diff between a commit and one of its parents,
82 // include the commit detail in the rendered page.
83 showCommit = isParentOf(walk, view.getOldRevision(), view.getRevision());
84 oldTree = getTreeIterator(walk, view.getOldRevision().getId());
85 newTree = getTreeIterator(walk, view.getRevision().getId());
Dave Borowitzf03fcea2014-04-21 17:20:33 -070086 } catch (MissingObjectException | IncorrectObjectTypeException e) {
Dave Borowitz9de65952012-08-13 16:09:45 -070087 res.setStatus(SC_NOT_FOUND);
88 return;
89 }
90
91 Map<String, Object> data = getData(req);
92 data.put("title", "Diff - " + view.getRevisionRange());
93 if (showCommit) {
Dave Borowitzf03fcea2014-04-21 17:20:33 -070094 Set<Field> fs = CommitSoyData.DEFAULT_FIELDS;
95 if (isFile) {
96 fs = Field.setOf(fs, Field.PARENT_BLAME_URL);
97 }
Dave Borowitz2b2f34b2014-04-29 16:47:20 -070098 GitilesAccess access = getAccess(req);
99 DateFormatter df = new DateFormatter(access, Format.DEFAULT);
Dave Borowitz3b086a72013-07-02 15:03:03 -0700100 data.put("commit", new CommitSoyData()
101 .setLinkifier(linkifier)
Dave Borowitz2b2f34b2014-04-29 16:47:20 -0700102 .setArchiveFormat(getArchiveFormat(access))
Dave Borowitzf03fcea2014-04-21 17:20:33 -0700103 .toSoyData(req, walk.parseCommit(view.getRevision().getId()), fs, df));
Dave Borowitz9de65952012-08-13 16:09:45 -0700104 }
105 if (!data.containsKey("repositoryName") && (view.getRepositoryName() != null)) {
106 data.put("repositoryName", view.getRepositoryName());
107 }
108 if (!data.containsKey("breadcrumbs")) {
109 data.put("breadcrumbs", view.getBreadcrumbs());
110 }
111
112 String[] html = renderAndSplit(data);
113 res.setStatus(HttpServletResponse.SC_OK);
114 res.setContentType(FormatType.HTML.getMimeType());
115 res.setCharacterEncoding(Charsets.UTF_8.name());
Dave Borowitz33d4fda2013-10-22 16:40:20 -0700116 setCacheHeaders(res);
Dave Borowitz9de65952012-08-13 16:09:45 -0700117
Dave Borowitza3b75442014-07-09 16:58:01 -0700118 try (OutputStream out = res.getOutputStream()) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700119 out.write(html[0].getBytes(Charsets.UTF_8));
Dave Borowitzea9bba12014-07-09 16:45:40 -0700120 DiffFormatter diff = new HtmlDiffFormatter(renderer, view, out);
121 formatDiff(repo, oldTree, newTree, view.getPathPart(), diff);
Dave Borowitz9de65952012-08-13 16:09:45 -0700122 out.write(html[1].getBytes(Charsets.UTF_8));
Dave Borowitz9de65952012-08-13 16:09:45 -0700123 }
124 } finally {
Dave Borowitz0d418f62014-04-29 11:32:25 -0700125 if (tw != null) {
126 tw.release();
127 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700128 walk.release();
129 }
130 }
131
Dave Borowitzea9bba12014-07-09 16:45:40 -0700132 @Override
133 protected void doGetText(HttpServletRequest req, HttpServletResponse res)
134 throws IOException {
135 GitilesView view = ViewFilter.getView(req);
136 Repository repo = ServletUtils.getRepository(req);
137
138 RevWalk walk = new RevWalk(repo);
139 try {
140 AbstractTreeIterator oldTree;
141 AbstractTreeIterator newTree;
142 try {
143 oldTree = getTreeIterator(walk, view.getOldRevision().getId());
144 newTree = getTreeIterator(walk, view.getRevision().getId());
145 } catch (MissingObjectException | IncorrectObjectTypeException e) {
146 res.setStatus(SC_NOT_FOUND);
147 return;
148 }
149
150 try (OutputStream out = BaseEncoding.base64()
151 .encodingStream(new OutputStreamWriter(res.getOutputStream()))) {
152 formatDiff(repo, oldTree, newTree, view.getPathPart(), new DiffFormatter(out));
153 }
154 } finally {
155 walk.release();
156 }
157 }
158
Dave Borowitz0d418f62014-04-29 11:32:25 -0700159 private static TreeWalk newTreeWalk(RevWalk walk, GitilesView view) throws IOException {
160 if (view.getPathPart().isEmpty()) {
161 return null;
162 }
163 return TreeWalk.forPath(
164 walk.getObjectReader(),
165 view.getPathPart(),
166 walk.parseTree(view.getRevision().getId()));
167 }
168
Dave Borowitz9de65952012-08-13 16:09:45 -0700169 private static boolean isParentOf(RevWalk walk, Revision oldRevision, Revision newRevision)
170 throws MissingObjectException, IncorrectObjectTypeException, IOException {
171 RevCommit newCommit = walk.parseCommit(newRevision.getId());
172 if (newCommit.getParentCount() > 0) {
173 return Arrays.asList(newCommit.getParents()).contains(oldRevision.getId());
174 } else {
175 return oldRevision == Revision.NULL;
176 }
177 }
178
Dave Borowitz0d418f62014-04-29 11:32:25 -0700179 private static boolean isFile(TreeWalk tw) {
180 return (tw.getRawMode(0) & FileMode.TYPE_FILE) > 0;
Dave Borowitzf03fcea2014-04-21 17:20:33 -0700181 }
182
Dave Borowitz9de65952012-08-13 16:09:45 -0700183 private String[] renderAndSplit(Map<String, Object> data) {
184 String html = renderer.newRenderer("gitiles.diffDetail")
185 .setData(data)
186 .render();
187 int id = html.indexOf(PLACEHOLDER);
188 if (id < 0) {
189 throw new IllegalStateException("Template must contain " + PLACEHOLDER);
190 }
191
192 int lt = html.lastIndexOf('<', id);
193 int gt = html.indexOf('>', id + PLACEHOLDER.length());
194 return new String[] {html.substring(0, lt), html.substring(gt + 1)};
195 }
196
Dave Borowitzea9bba12014-07-09 16:45:40 -0700197 private static void formatDiff(Repository repo, AbstractTreeIterator oldTree,
198 AbstractTreeIterator newTree, String path, DiffFormatter diff) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700199 try {
200 if (!path.equals("")) {
201 diff.setPathFilter(PathFilter.create(path));
202 }
203 diff.setRepository(repo);
204 diff.setDetectRenames(true);
205 diff.format(oldTree, newTree);
206 } finally {
207 diff.release();
208 }
209 }
210
211 private static AbstractTreeIterator getTreeIterator(RevWalk walk, ObjectId id)
212 throws IOException {
213 if (!id.equals(ObjectId.zeroId())) {
214 CanonicalTreeParser p = new CanonicalTreeParser();
215 p.reset(walk.getObjectReader(), walk.parseTree(id));
216 return p;
217 } else {
218 return new EmptyTreeIterator();
219 }
220 }
221}