blob: 44d2c18a728e140df558def72095ff28ede762ac [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 Borowitz3b744b12016-08-19 16:11:10 -040023import java.io.IOException;
24import java.io.OutputStream;
25import java.util.List;
26import java.util.Map;
David Pursehousea4d39352018-01-23 08:27:29 +090027import org.apache.commons.text.StringEscapeUtils;
Dave Borowitz9de65952012-08-13 16:09:45 -070028import org.eclipse.jgit.diff.DiffEntry;
Dave Borowitza999b162014-04-21 16:51:47 -070029import org.eclipse.jgit.diff.DiffEntry.ChangeType;
Dave Borowitz9de65952012-08-13 16:09:45 -070030import org.eclipse.jgit.diff.DiffFormatter;
31import org.eclipse.jgit.diff.RawText;
32import org.eclipse.jgit.patch.FileHeader;
33import org.eclipse.jgit.patch.FileHeader.PatchType;
34import org.eclipse.jgit.util.RawParseUtils;
35
Dave Borowitz9de65952012-08-13 16:09:45 -070036/** Formats a unified format patch as UTF-8 encoded HTML. */
37final class HtmlDiffFormatter extends DiffFormatter {
David Pursehouseccaa85d2017-05-30 10:47:27 +090038 private static final byte[] DIFF_BEGIN =
39 "<pre class=\"u-pre u-monospace Diff-unified\">".getBytes(UTF_8);
David Pletcherd7bdaf32014-08-27 14:50:32 -070040 private static final byte[] DIFF_END = "</pre>".getBytes(UTF_8);
Dave Borowitz9de65952012-08-13 16:09:45 -070041
Andrew Bonventreb33426e2015-09-09 18:28:28 -040042 private static final byte[] HUNK_BEGIN = "<span class=\"Diff-hunk\">".getBytes(UTF_8);
David Pletcherd7bdaf32014-08-27 14:50:32 -070043 private static final byte[] HUNK_END = "</span>".getBytes(UTF_8);
Dave Borowitz9de65952012-08-13 16:09:45 -070044
Andrew Bonventreb33426e2015-09-09 18:28:28 -040045 private static final byte[] LINE_INSERT_BEGIN = "<span class=\"Diff-insert\">".getBytes(UTF_8);
46 private static final byte[] LINE_DELETE_BEGIN = "<span class=\"Diff-delete\">".getBytes(UTF_8);
47 private static final byte[] LINE_CHANGE_BEGIN = "<span class=\"Diff-change\">".getBytes(UTF_8);
David Pletcherd7bdaf32014-08-27 14:50:32 -070048 private static final byte[] LINE_END = "</span>\n".getBytes(UTF_8);
Dave Borowitz9de65952012-08-13 16:09:45 -070049
50 private final Renderer renderer;
Dave Borowitza999b162014-04-21 16:51:47 -070051 private final GitilesView view;
Dave Borowitz9de65952012-08-13 16:09:45 -070052 private int fileIndex;
Dave Borowitza999b162014-04-21 16:51:47 -070053 private DiffEntry entry;
Dave Borowitz9de65952012-08-13 16:09:45 -070054
Dave Borowitza999b162014-04-21 16:51:47 -070055 HtmlDiffFormatter(Renderer renderer, GitilesView view, OutputStream out) {
Dave Borowitz9de65952012-08-13 16:09:45 -070056 super(out);
57 this.renderer = checkNotNull(renderer, "renderer");
Dave Borowitza999b162014-04-21 16:51:47 -070058 this.view = checkNotNull(view, "view");
Dave Borowitz9de65952012-08-13 16:09:45 -070059 }
60
61 @Override
62 public void format(List<? extends DiffEntry> entries) throws IOException {
63 for (fileIndex = 0; fileIndex < entries.size(); fileIndex++) {
Dave Borowitza999b162014-04-21 16:51:47 -070064 entry = entries.get(fileIndex);
65 format(entry);
Dave Borowitz9de65952012-08-13 16:09:45 -070066 }
67 }
68
69 @Override
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020070 public void format(FileHeader hdr, RawText a, RawText b) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -070071 int start = hdr.getStartOffset();
72 int end = hdr.getEndOffset();
73 if (!hdr.getHunks().isEmpty()) {
74 end = hdr.getHunks().get(0).getStartOffset();
75 }
76 renderHeader(RawParseUtils.decode(hdr.getBuffer(), start, end));
77
78 if (hdr.getPatchType() == PatchType.UNIFIED) {
79 getOutputStream().write(DIFF_BEGIN);
80 format(hdr.toEditList(), a, b);
81 getOutputStream().write(DIFF_END);
82 }
83 }
84
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020085 private void renderHeader(String header) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -070086 int lf = header.indexOf('\n');
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020087 String rest = 0 <= lf ? header.substring(lf + 1) : "";
Dave Borowitza999b162014-04-21 16:51:47 -070088
89 // Based on DiffFormatter.formatGitDiffFirstHeaderLine.
90 List<Map<String, String>> parts = Lists.newArrayListWithCapacity(3);
91 parts.add(ImmutableMap.of("text", "diff --git"));
92 if (entry.getChangeType() != ChangeType.ADD) {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020093 parts.add(
94 ImmutableMap.of(
95 "text", GIT_PATH.quote(getOldPrefix() + entry.getOldPath()),
96 "url", revisionUrl(view.getOldRevision(), entry.getOldPath())));
Dave Borowitz9de65952012-08-13 16:09:45 -070097 } else {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020098 parts.add(ImmutableMap.of("text", GIT_PATH.quote(getOldPrefix() + entry.getNewPath())));
Dave Borowitz9de65952012-08-13 16:09:45 -070099 }
Dave Borowitza999b162014-04-21 16:51:47 -0700100 if (entry.getChangeType() != ChangeType.DELETE) {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200101 parts.add(
102 ImmutableMap.of(
103 "text", GIT_PATH.quote(getNewPrefix() + entry.getNewPath()),
104 "url", revisionUrl(view.getRevision(), entry.getNewPath())));
Dave Borowitza999b162014-04-21 16:51:47 -0700105 } else {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200106 parts.add(ImmutableMap.of("text", GIT_PATH.quote(getNewPrefix() + entry.getOldPath())));
Dave Borowitza999b162014-04-21 16:51:47 -0700107 }
108
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200109 getOutputStream()
110 .write(
111 renderer
112 .newRenderer("gitiles.diffHeader")
113 .setData(ImmutableMap.of("firstParts", parts, "rest", rest, "fileIndex", fileIndex))
114 .render()
115 .getBytes(UTF_8));
Dave Borowitz9de65952012-08-13 16:09:45 -0700116 }
117
Dave Borowitza999b162014-04-21 16:51:47 -0700118 private String revisionUrl(Revision rev, String path) {
119 return GitilesView.path()
120 .copyFrom(view)
121 .setOldRevision(Revision.NULL)
122 .setRevision(Revision.named(rev.getId().name()))
123 .setPathPart(path)
124 .toUrl();
125 }
126
Dave Borowitz9de65952012-08-13 16:09:45 -0700127 @Override
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200128 protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine, int bEndLine)
129 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700130 getOutputStream().write(HUNK_BEGIN);
131 // TODO(sop): If hunk header starts including method names, escape it.
132 super.writeHunkHeader(aStartLine, aEndLine, bStartLine, bEndLine);
133 getOutputStream().write(HUNK_END);
134 }
135
136 @Override
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200137 protected void writeLine(char prefix, RawText text, int cur) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700138 // Manually render each line, rather than invoke a Soy template. This method
139 // can be called thousands of times in a single request. Avoid unnecessary
140 // overheads by formatting as-is.
141 OutputStream out = getOutputStream();
142 switch (prefix) {
143 case '+':
144 out.write(LINE_INSERT_BEGIN);
145 break;
146 case '-':
147 out.write(LINE_DELETE_BEGIN);
148 break;
149 case ' ':
150 default:
151 out.write(LINE_CHANGE_BEGIN);
152 break;
153 }
Shawn Pearceeea63732012-12-26 12:40:41 -0800154 out.write(prefix);
David Pletcherd7bdaf32014-08-27 14:50:32 -0700155 out.write(StringEscapeUtils.escapeHtml4(text.getString(cur)).getBytes(UTF_8));
Dave Borowitz9de65952012-08-13 16:09:45 -0700156 out.write(LINE_END);
157 }
158}