blob: 04e8391a2ba4822dd00ec22150aac1d17ee172fa [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;
Dave Borowitza03760a2014-01-29 16:17:28 -080034import org.eclipse.jgit.util.GitDateFormatter;
35import org.eclipse.jgit.util.GitDateFormatter.Format;
Dave Borowitz9de65952012-08-13 16:09:45 -070036
37import java.io.IOException;
38import java.io.OutputStream;
39import java.util.Arrays;
40import java.util.Map;
41
42import javax.servlet.http.HttpServletRequest;
43import javax.servlet.http.HttpServletResponse;
44
45/** Serves an HTML page with all the diffs for a commit. */
46public class DiffServlet extends BaseServlet {
Chad Horohoead23f142012-11-12 09:45:39 -080047 private static final long serialVersionUID = 1L;
Dave Borowitz9de65952012-08-13 16:09:45 -070048 private static final String PLACEHOLDER = "id=\"DIFF_OUTPUT_BLOCK\"";
49
Dave Borowitzded109a2014-03-03 15:25:39 -050050 private final GitilesAccess.Factory accessFactory;
Dave Borowitz9de65952012-08-13 16:09:45 -070051 private final Linkifier linkifier;
52
Dave Borowitzded109a2014-03-03 15:25:39 -050053 public DiffServlet(GitilesAccess.Factory accessFactory, Renderer renderer,
54 Linkifier linkifier) {
55 super(renderer);
56 this.accessFactory = checkNotNull(accessFactory, "accessFactory");
Dave Borowitz9de65952012-08-13 16:09:45 -070057 this.linkifier = checkNotNull(linkifier, "linkifier");
58 }
59
60 @Override
61 protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
62 GitilesView view = ViewFilter.getView(req);
63 Repository repo = ServletUtils.getRepository(req);
64
65 RevWalk walk = new RevWalk(repo);
66 try {
67 boolean showCommit;
68 AbstractTreeIterator oldTree;
69 AbstractTreeIterator newTree;
70 try {
71 // If we are viewing the diff between a commit and one of its parents,
72 // include the commit detail in the rendered page.
73 showCommit = isParentOf(walk, view.getOldRevision(), view.getRevision());
74 oldTree = getTreeIterator(walk, view.getOldRevision().getId());
75 newTree = getTreeIterator(walk, view.getRevision().getId());
76 } catch (MissingObjectException e) {
77 res.setStatus(SC_NOT_FOUND);
78 return;
79 } catch (IncorrectObjectTypeException e) {
80 res.setStatus(SC_NOT_FOUND);
81 return;
82 }
83
84 Map<String, Object> data = getData(req);
85 data.put("title", "Diff - " + view.getRevisionRange());
86 if (showCommit) {
Dave Borowitza03760a2014-01-29 16:17:28 -080087 GitDateFormatter df = new GitDateFormatter(Format.DEFAULT);
Dave Borowitz3b086a72013-07-02 15:03:03 -070088 data.put("commit", new CommitSoyData()
89 .setLinkifier(linkifier)
Dave Borowitzded109a2014-03-03 15:25:39 -050090 .setArchiveFormat(getArchiveFormat(accessFactory.forRequest(req)))
Dave Borowitza03760a2014-01-29 16:17:28 -080091 .toSoyData(req, walk.parseCommit(view.getRevision().getId()), df));
Dave Borowitz9de65952012-08-13 16:09:45 -070092 }
93 if (!data.containsKey("repositoryName") && (view.getRepositoryName() != null)) {
94 data.put("repositoryName", view.getRepositoryName());
95 }
96 if (!data.containsKey("breadcrumbs")) {
97 data.put("breadcrumbs", view.getBreadcrumbs());
98 }
99
100 String[] html = renderAndSplit(data);
101 res.setStatus(HttpServletResponse.SC_OK);
102 res.setContentType(FormatType.HTML.getMimeType());
103 res.setCharacterEncoding(Charsets.UTF_8.name());
Dave Borowitz33d4fda2013-10-22 16:40:20 -0700104 setCacheHeaders(res);
Dave Borowitz9de65952012-08-13 16:09:45 -0700105
106 OutputStream out = res.getOutputStream();
107 try {
108 out.write(html[0].getBytes(Charsets.UTF_8));
Dave Borowitz33d4fda2013-10-22 16:40:20 -0700109 formatHtmlDiff(out, repo, oldTree, newTree, view.getPathPart());
Dave Borowitz9de65952012-08-13 16:09:45 -0700110 out.write(html[1].getBytes(Charsets.UTF_8));
111 } finally {
112 out.close();
113 }
114 } finally {
115 walk.release();
116 }
117 }
118
119 private static boolean isParentOf(RevWalk walk, Revision oldRevision, Revision newRevision)
120 throws MissingObjectException, IncorrectObjectTypeException, IOException {
121 RevCommit newCommit = walk.parseCommit(newRevision.getId());
122 if (newCommit.getParentCount() > 0) {
123 return Arrays.asList(newCommit.getParents()).contains(oldRevision.getId());
124 } else {
125 return oldRevision == Revision.NULL;
126 }
127 }
128
129 private String[] renderAndSplit(Map<String, Object> data) {
130 String html = renderer.newRenderer("gitiles.diffDetail")
131 .setData(data)
132 .render();
133 int id = html.indexOf(PLACEHOLDER);
134 if (id < 0) {
135 throw new IllegalStateException("Template must contain " + PLACEHOLDER);
136 }
137
138 int lt = html.lastIndexOf('<', id);
139 int gt = html.indexOf('>', id + PLACEHOLDER.length());
140 return new String[] {html.substring(0, lt), html.substring(gt + 1)};
141 }
142
143 private void formatHtmlDiff(OutputStream out,
Dave Borowitz33d4fda2013-10-22 16:40:20 -0700144 Repository repo, AbstractTreeIterator oldTree,
145 AbstractTreeIterator newTree, String path)
Dave Borowitz9de65952012-08-13 16:09:45 -0700146 throws IOException {
147 DiffFormatter diff = new HtmlDiffFormatter(renderer, out);
148 try {
149 if (!path.equals("")) {
150 diff.setPathFilter(PathFilter.create(path));
151 }
152 diff.setRepository(repo);
153 diff.setDetectRenames(true);
154 diff.format(oldTree, newTree);
155 } finally {
156 diff.release();
157 }
158 }
159
160 private static AbstractTreeIterator getTreeIterator(RevWalk walk, ObjectId id)
161 throws IOException {
162 if (!id.equals(ObjectId.zeroId())) {
163 CanonicalTreeParser p = new CanonicalTreeParser();
164 p.reset(walk.getObjectReader(), walk.parseTree(id));
165 return p;
166 } else {
167 return new EmptyTreeIterator();
168 }
169 }
170}