blob: 40eeb834bc142abdf17ff08900a39bf63a5534c0 [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;
Dave Borowitz9de65952012-08-13 16:09:45 -070027import org.apache.commons.lang3.StringEscapeUtils;
28import 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 {
Mark Mentovai1bc4a082016-11-07 20:49:22 -050038 private static final byte[] DIFF_BEGIN = "<pre class=\"u-pre u-monospace Diff-unified\">".getBytes(UTF_8);
David Pletcherd7bdaf32014-08-27 14:50:32 -070039 private static final byte[] DIFF_END = "</pre>".getBytes(UTF_8);
Dave Borowitz9de65952012-08-13 16:09:45 -070040
Andrew Bonventreb33426e2015-09-09 18:28:28 -040041 private static final byte[] HUNK_BEGIN = "<span class=\"Diff-hunk\">".getBytes(UTF_8);
David Pletcherd7bdaf32014-08-27 14:50:32 -070042 private static final byte[] HUNK_END = "</span>".getBytes(UTF_8);
Dave Borowitz9de65952012-08-13 16:09:45 -070043
Andrew Bonventreb33426e2015-09-09 18:28:28 -040044 private static final byte[] LINE_INSERT_BEGIN = "<span class=\"Diff-insert\">".getBytes(UTF_8);
45 private static final byte[] LINE_DELETE_BEGIN = "<span class=\"Diff-delete\">".getBytes(UTF_8);
46 private static final byte[] LINE_CHANGE_BEGIN = "<span class=\"Diff-change\">".getBytes(UTF_8);
David Pletcherd7bdaf32014-08-27 14:50:32 -070047 private static final byte[] LINE_END = "</span>\n".getBytes(UTF_8);
Dave Borowitz9de65952012-08-13 16:09:45 -070048
49 private final Renderer renderer;
Dave Borowitza999b162014-04-21 16:51:47 -070050 private final GitilesView view;
Dave Borowitz9de65952012-08-13 16:09:45 -070051 private int fileIndex;
Dave Borowitza999b162014-04-21 16:51:47 -070052 private DiffEntry entry;
Dave Borowitz9de65952012-08-13 16:09:45 -070053
Dave Borowitza999b162014-04-21 16:51:47 -070054 HtmlDiffFormatter(Renderer renderer, GitilesView view, OutputStream out) {
Dave Borowitz9de65952012-08-13 16:09:45 -070055 super(out);
56 this.renderer = checkNotNull(renderer, "renderer");
Dave Borowitza999b162014-04-21 16:51:47 -070057 this.view = checkNotNull(view, "view");
Dave Borowitz9de65952012-08-13 16:09:45 -070058 }
59
60 @Override
61 public void format(List<? extends DiffEntry> entries) throws IOException {
62 for (fileIndex = 0; fileIndex < entries.size(); fileIndex++) {
Dave Borowitza999b162014-04-21 16:51:47 -070063 entry = entries.get(fileIndex);
64 format(entry);
Dave Borowitz9de65952012-08-13 16:09:45 -070065 }
66 }
67
68 @Override
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020069 public void format(FileHeader hdr, RawText a, RawText b) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -070070 int start = hdr.getStartOffset();
71 int end = hdr.getEndOffset();
72 if (!hdr.getHunks().isEmpty()) {
73 end = hdr.getHunks().get(0).getStartOffset();
74 }
75 renderHeader(RawParseUtils.decode(hdr.getBuffer(), start, end));
76
77 if (hdr.getPatchType() == PatchType.UNIFIED) {
78 getOutputStream().write(DIFF_BEGIN);
79 format(hdr.toEditList(), a, b);
80 getOutputStream().write(DIFF_END);
81 }
82 }
83
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020084 private void renderHeader(String header) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -070085 int lf = header.indexOf('\n');
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020086 String rest = 0 <= lf ? header.substring(lf + 1) : "";
Dave Borowitza999b162014-04-21 16:51:47 -070087
88 // Based on DiffFormatter.formatGitDiffFirstHeaderLine.
89 List<Map<String, String>> parts = Lists.newArrayListWithCapacity(3);
90 parts.add(ImmutableMap.of("text", "diff --git"));
91 if (entry.getChangeType() != ChangeType.ADD) {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020092 parts.add(
93 ImmutableMap.of(
94 "text", GIT_PATH.quote(getOldPrefix() + entry.getOldPath()),
95 "url", revisionUrl(view.getOldRevision(), entry.getOldPath())));
Dave Borowitz9de65952012-08-13 16:09:45 -070096 } else {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020097 parts.add(ImmutableMap.of("text", GIT_PATH.quote(getOldPrefix() + entry.getNewPath())));
Dave Borowitz9de65952012-08-13 16:09:45 -070098 }
Dave Borowitza999b162014-04-21 16:51:47 -070099 if (entry.getChangeType() != ChangeType.DELETE) {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200100 parts.add(
101 ImmutableMap.of(
102 "text", GIT_PATH.quote(getNewPrefix() + entry.getNewPath()),
103 "url", revisionUrl(view.getRevision(), entry.getNewPath())));
Dave Borowitza999b162014-04-21 16:51:47 -0700104 } else {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200105 parts.add(ImmutableMap.of("text", GIT_PATH.quote(getNewPrefix() + entry.getOldPath())));
Dave Borowitza999b162014-04-21 16:51:47 -0700106 }
107
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200108 getOutputStream()
109 .write(
110 renderer
111 .newRenderer("gitiles.diffHeader")
112 .setData(ImmutableMap.of("firstParts", parts, "rest", rest, "fileIndex", fileIndex))
113 .render()
114 .getBytes(UTF_8));
Dave Borowitz9de65952012-08-13 16:09:45 -0700115 }
116
Dave Borowitza999b162014-04-21 16:51:47 -0700117 private String revisionUrl(Revision rev, String path) {
118 return GitilesView.path()
119 .copyFrom(view)
120 .setOldRevision(Revision.NULL)
121 .setRevision(Revision.named(rev.getId().name()))
122 .setPathPart(path)
123 .toUrl();
124 }
125
Dave Borowitz9de65952012-08-13 16:09:45 -0700126 @Override
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200127 protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine, int bEndLine)
128 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700129 getOutputStream().write(HUNK_BEGIN);
130 // TODO(sop): If hunk header starts including method names, escape it.
131 super.writeHunkHeader(aStartLine, aEndLine, bStartLine, bEndLine);
132 getOutputStream().write(HUNK_END);
133 }
134
135 @Override
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200136 protected void writeLine(char prefix, RawText text, int cur) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700137 // Manually render each line, rather than invoke a Soy template. This method
138 // can be called thousands of times in a single request. Avoid unnecessary
139 // overheads by formatting as-is.
140 OutputStream out = getOutputStream();
141 switch (prefix) {
142 case '+':
143 out.write(LINE_INSERT_BEGIN);
144 break;
145 case '-':
146 out.write(LINE_DELETE_BEGIN);
147 break;
148 case ' ':
149 default:
150 out.write(LINE_CHANGE_BEGIN);
151 break;
152 }
Shawn Pearceeea63732012-12-26 12:40:41 -0800153 out.write(prefix);
David Pletcherd7bdaf32014-08-27 14:50:32 -0700154 out.write(StringEscapeUtils.escapeHtml4(text.getString(cur)).getBytes(UTF_8));
Dave Borowitz9de65952012-08-13 16:09:45 -0700155 out.write(LINE_END);
156 }
157}