blob: a8fdeeccdb4f2990fbaf544c6a7e2f7cf92037d1 [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;
21
22import org.eclipse.jgit.diff.DiffFormatter;
23import org.eclipse.jgit.errors.IncorrectObjectTypeException;
24import org.eclipse.jgit.errors.MissingObjectException;
25import org.eclipse.jgit.http.server.ServletUtils;
26import org.eclipse.jgit.lib.ObjectId;
27import org.eclipse.jgit.lib.Repository;
28import org.eclipse.jgit.revwalk.RevCommit;
29import org.eclipse.jgit.revwalk.RevWalk;
30import org.eclipse.jgit.treewalk.AbstractTreeIterator;
31import org.eclipse.jgit.treewalk.CanonicalTreeParser;
32import org.eclipse.jgit.treewalk.EmptyTreeIterator;
33import org.eclipse.jgit.treewalk.filter.PathFilter;
34
35import java.io.IOException;
36import java.io.OutputStream;
37import java.util.Arrays;
38import java.util.Map;
39
40import javax.servlet.http.HttpServletRequest;
41import javax.servlet.http.HttpServletResponse;
42
43/** Serves an HTML page with all the diffs for a commit. */
44public class DiffServlet extends BaseServlet {
Chad Horohoead23f142012-11-12 09:45:39 -080045 private static final long serialVersionUID = 1L;
Dave Borowitz9de65952012-08-13 16:09:45 -070046 private static final String PLACEHOLDER = "id=\"DIFF_OUTPUT_BLOCK\"";
47
48 private final Linkifier linkifier;
49
50 public DiffServlet(Renderer renderer, Linkifier linkifier) {
51 super(renderer);
52 this.linkifier = checkNotNull(linkifier, "linkifier");
53 }
54
55 @Override
56 protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
57 GitilesView view = ViewFilter.getView(req);
58 Repository repo = ServletUtils.getRepository(req);
59
60 RevWalk walk = new RevWalk(repo);
61 try {
62 boolean showCommit;
63 AbstractTreeIterator oldTree;
64 AbstractTreeIterator newTree;
65 try {
66 // If we are viewing the diff between a commit and one of its parents,
67 // include the commit detail in the rendered page.
68 showCommit = isParentOf(walk, view.getOldRevision(), view.getRevision());
69 oldTree = getTreeIterator(walk, view.getOldRevision().getId());
70 newTree = getTreeIterator(walk, view.getRevision().getId());
71 } catch (MissingObjectException e) {
72 res.setStatus(SC_NOT_FOUND);
73 return;
74 } catch (IncorrectObjectTypeException e) {
75 res.setStatus(SC_NOT_FOUND);
76 return;
77 }
78
79 Map<String, Object> data = getData(req);
80 data.put("title", "Diff - " + view.getRevisionRange());
81 if (showCommit) {
82 data.put("commit", new CommitSoyData(linkifier, req, repo, walk, view)
83 .toSoyData(walk.parseCommit(view.getRevision().getId())));
84 }
85 if (!data.containsKey("repositoryName") && (view.getRepositoryName() != null)) {
86 data.put("repositoryName", view.getRepositoryName());
87 }
88 if (!data.containsKey("breadcrumbs")) {
89 data.put("breadcrumbs", view.getBreadcrumbs());
90 }
91
92 String[] html = renderAndSplit(data);
93 res.setStatus(HttpServletResponse.SC_OK);
94 res.setContentType(FormatType.HTML.getMimeType());
95 res.setCharacterEncoding(Charsets.UTF_8.name());
96 setCacheHeaders(req, res);
97
98 OutputStream out = res.getOutputStream();
99 try {
100 out.write(html[0].getBytes(Charsets.UTF_8));
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700101 formatHtmlDiff(out, repo, walk, oldTree, newTree, view.getPathPart());
Dave Borowitz9de65952012-08-13 16:09:45 -0700102 out.write(html[1].getBytes(Charsets.UTF_8));
103 } finally {
104 out.close();
105 }
106 } finally {
107 walk.release();
108 }
109 }
110
111 private static boolean isParentOf(RevWalk walk, Revision oldRevision, Revision newRevision)
112 throws MissingObjectException, IncorrectObjectTypeException, IOException {
113 RevCommit newCommit = walk.parseCommit(newRevision.getId());
114 if (newCommit.getParentCount() > 0) {
115 return Arrays.asList(newCommit.getParents()).contains(oldRevision.getId());
116 } else {
117 return oldRevision == Revision.NULL;
118 }
119 }
120
121 private String[] renderAndSplit(Map<String, Object> data) {
122 String html = renderer.newRenderer("gitiles.diffDetail")
123 .setData(data)
124 .render();
125 int id = html.indexOf(PLACEHOLDER);
126 if (id < 0) {
127 throw new IllegalStateException("Template must contain " + PLACEHOLDER);
128 }
129
130 int lt = html.lastIndexOf('<', id);
131 int gt = html.indexOf('>', id + PLACEHOLDER.length());
132 return new String[] {html.substring(0, lt), html.substring(gt + 1)};
133 }
134
135 private void formatHtmlDiff(OutputStream out,
136 Repository repo, RevWalk walk,
137 AbstractTreeIterator oldTree, AbstractTreeIterator newTree,
138 String path)
139 throws IOException {
140 DiffFormatter diff = new HtmlDiffFormatter(renderer, out);
141 try {
142 if (!path.equals("")) {
143 diff.setPathFilter(PathFilter.create(path));
144 }
145 diff.setRepository(repo);
146 diff.setDetectRenames(true);
147 diff.format(oldTree, newTree);
148 } finally {
149 diff.release();
150 }
151 }
152
153 private static AbstractTreeIterator getTreeIterator(RevWalk walk, ObjectId id)
154 throws IOException {
155 if (!id.equals(ObjectId.zeroId())) {
156 CanonicalTreeParser p = new CanonicalTreeParser();
157 p.reset(walk.getObjectReader(), walk.parseTree(id));
158 return p;
159 } else {
160 return new EmptyTreeIterator();
161 }
162 }
163}