blob: 4998f9403f029b4fba4167f68ff5e97a26c7f19b [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;
David Pletcherd7bdaf32014-08-27 14:50:32 -070018import static java.nio.charset.StandardCharsets.UTF_8;
Dave Borowitza999b162014-04-21 16:51:47 -070019import static org.eclipse.jgit.util.QuotedString.GIT_PATH;
Dave Borowitz9de65952012-08-13 16:09:45 -070020
Dave Borowitz9de65952012-08-13 16:09:45 -070021import com.google.common.collect.ImmutableMap;
Dave Borowitza999b162014-04-21 16:51:47 -070022import com.google.common.collect.Lists;
Dave Borowitz9de65952012-08-13 16:09:45 -070023
24import org.apache.commons.lang3.StringEscapeUtils;
25import org.eclipse.jgit.diff.DiffEntry;
Dave Borowitza999b162014-04-21 16:51:47 -070026import org.eclipse.jgit.diff.DiffEntry.ChangeType;
Dave Borowitz9de65952012-08-13 16:09:45 -070027import org.eclipse.jgit.diff.DiffFormatter;
28import org.eclipse.jgit.diff.RawText;
29import org.eclipse.jgit.patch.FileHeader;
30import org.eclipse.jgit.patch.FileHeader.PatchType;
31import org.eclipse.jgit.util.RawParseUtils;
32
33import java.io.IOException;
34import java.io.OutputStream;
35import java.util.List;
Dave Borowitza999b162014-04-21 16:51:47 -070036import java.util.Map;
Dave Borowitz9de65952012-08-13 16:09:45 -070037
38/** Formats a unified format patch as UTF-8 encoded HTML. */
39final class HtmlDiffFormatter extends DiffFormatter {
Andrew Bonventreb33426e2015-09-09 18:28:28 -040040 private static final byte[] DIFF_BEGIN = "<pre class=\"u-pre Diff-unified\">".getBytes(UTF_8);
David Pletcherd7bdaf32014-08-27 14:50:32 -070041 private static final byte[] DIFF_END = "</pre>".getBytes(UTF_8);
Dave Borowitz9de65952012-08-13 16:09:45 -070042
Andrew Bonventreb33426e2015-09-09 18:28:28 -040043 private static final byte[] HUNK_BEGIN = "<span class=\"Diff-hunk\">".getBytes(UTF_8);
David Pletcherd7bdaf32014-08-27 14:50:32 -070044 private static final byte[] HUNK_END = "</span>".getBytes(UTF_8);
Dave Borowitz9de65952012-08-13 16:09:45 -070045
Andrew Bonventreb33426e2015-09-09 18:28:28 -040046 private static final byte[] LINE_INSERT_BEGIN = "<span class=\"Diff-insert\">".getBytes(UTF_8);
47 private static final byte[] LINE_DELETE_BEGIN = "<span class=\"Diff-delete\">".getBytes(UTF_8);
48 private static final byte[] LINE_CHANGE_BEGIN = "<span class=\"Diff-change\">".getBytes(UTF_8);
David Pletcherd7bdaf32014-08-27 14:50:32 -070049 private static final byte[] LINE_END = "</span>\n".getBytes(UTF_8);
Dave Borowitz9de65952012-08-13 16:09:45 -070050
51 private final Renderer renderer;
Dave Borowitza999b162014-04-21 16:51:47 -070052 private final GitilesView view;
Dave Borowitz9de65952012-08-13 16:09:45 -070053 private int fileIndex;
Dave Borowitza999b162014-04-21 16:51:47 -070054 private DiffEntry entry;
Dave Borowitz9de65952012-08-13 16:09:45 -070055
Dave Borowitza999b162014-04-21 16:51:47 -070056 HtmlDiffFormatter(Renderer renderer, GitilesView view, OutputStream out) {
Dave Borowitz9de65952012-08-13 16:09:45 -070057 super(out);
58 this.renderer = checkNotNull(renderer, "renderer");
Dave Borowitza999b162014-04-21 16:51:47 -070059 this.view = checkNotNull(view, "view");
Dave Borowitz9de65952012-08-13 16:09:45 -070060 }
61
62 @Override
63 public void format(List<? extends DiffEntry> entries) throws IOException {
64 for (fileIndex = 0; fileIndex < entries.size(); fileIndex++) {
Dave Borowitza999b162014-04-21 16:51:47 -070065 entry = entries.get(fileIndex);
66 format(entry);
Dave Borowitz9de65952012-08-13 16:09:45 -070067 }
68 }
69
70 @Override
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020071 public void format(FileHeader hdr, RawText a, RawText b) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -070072 int start = hdr.getStartOffset();
73 int end = hdr.getEndOffset();
74 if (!hdr.getHunks().isEmpty()) {
75 end = hdr.getHunks().get(0).getStartOffset();
76 }
77 renderHeader(RawParseUtils.decode(hdr.getBuffer(), start, end));
78
79 if (hdr.getPatchType() == PatchType.UNIFIED) {
80 getOutputStream().write(DIFF_BEGIN);
81 format(hdr.toEditList(), a, b);
82 getOutputStream().write(DIFF_END);
83 }
84 }
85
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020086 private void renderHeader(String header) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -070087 int lf = header.indexOf('\n');
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020088 String rest = 0 <= lf ? header.substring(lf + 1) : "";
Dave Borowitza999b162014-04-21 16:51:47 -070089
90 // Based on DiffFormatter.formatGitDiffFirstHeaderLine.
91 List<Map<String, String>> parts = Lists.newArrayListWithCapacity(3);
92 parts.add(ImmutableMap.of("text", "diff --git"));
93 if (entry.getChangeType() != ChangeType.ADD) {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020094 parts.add(
95 ImmutableMap.of(
96 "text", GIT_PATH.quote(getOldPrefix() + entry.getOldPath()),
97 "url", revisionUrl(view.getOldRevision(), entry.getOldPath())));
Dave Borowitz9de65952012-08-13 16:09:45 -070098 } else {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020099 parts.add(ImmutableMap.of("text", GIT_PATH.quote(getOldPrefix() + entry.getNewPath())));
Dave Borowitz9de65952012-08-13 16:09:45 -0700100 }
Dave Borowitza999b162014-04-21 16:51:47 -0700101 if (entry.getChangeType() != ChangeType.DELETE) {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200102 parts.add(
103 ImmutableMap.of(
104 "text", GIT_PATH.quote(getNewPrefix() + entry.getNewPath()),
105 "url", revisionUrl(view.getRevision(), entry.getNewPath())));
Dave Borowitza999b162014-04-21 16:51:47 -0700106 } else {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200107 parts.add(ImmutableMap.of("text", GIT_PATH.quote(getNewPrefix() + entry.getOldPath())));
Dave Borowitza999b162014-04-21 16:51:47 -0700108 }
109
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200110 getOutputStream()
111 .write(
112 renderer
113 .newRenderer("gitiles.diffHeader")
114 .setData(ImmutableMap.of("firstParts", parts, "rest", rest, "fileIndex", fileIndex))
115 .render()
116 .getBytes(UTF_8));
Dave Borowitz9de65952012-08-13 16:09:45 -0700117 }
118
Dave Borowitza999b162014-04-21 16:51:47 -0700119 private String revisionUrl(Revision rev, String path) {
120 return GitilesView.path()
121 .copyFrom(view)
122 .setOldRevision(Revision.NULL)
123 .setRevision(Revision.named(rev.getId().name()))
124 .setPathPart(path)
125 .toUrl();
126 }
127
Dave Borowitz9de65952012-08-13 16:09:45 -0700128 @Override
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200129 protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine, int bEndLine)
130 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700131 getOutputStream().write(HUNK_BEGIN);
132 // TODO(sop): If hunk header starts including method names, escape it.
133 super.writeHunkHeader(aStartLine, aEndLine, bStartLine, bEndLine);
134 getOutputStream().write(HUNK_END);
135 }
136
137 @Override
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200138 protected void writeLine(char prefix, RawText text, int cur) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700139 // Manually render each line, rather than invoke a Soy template. This method
140 // can be called thousands of times in a single request. Avoid unnecessary
141 // overheads by formatting as-is.
142 OutputStream out = getOutputStream();
143 switch (prefix) {
144 case '+':
145 out.write(LINE_INSERT_BEGIN);
146 break;
147 case '-':
148 out.write(LINE_DELETE_BEGIN);
149 break;
150 case ' ':
151 default:
152 out.write(LINE_CHANGE_BEGIN);
153 break;
154 }
Shawn Pearceeea63732012-12-26 12:40:41 -0800155 out.write(prefix);
David Pletcherd7bdaf32014-08-27 14:50:32 -0700156 out.write(StringEscapeUtils.escapeHtml4(text.getString(cur)).getBytes(UTF_8));
Dave Borowitz9de65952012-08-13 16:09:45 -0700157 out.write(LINE_END);
158 }
159}