| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 1 | // 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 | |
| 15 | package com.google.gitiles; |
| 16 | |
| 17 | import static com.google.common.base.Preconditions.checkNotNull; |
| David Pletcher | d7bdaf3 | 2014-08-27 14:50:32 -0700 | [diff] [blame] | 18 | import static java.nio.charset.StandardCharsets.UTF_8; |
| Dave Borowitz | a999b16 | 2014-04-21 16:51:47 -0700 | [diff] [blame] | 19 | import static org.eclipse.jgit.util.QuotedString.GIT_PATH; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 20 | |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 21 | import com.google.common.collect.ImmutableMap; |
| Dave Borowitz | a999b16 | 2014-04-21 16:51:47 -0700 | [diff] [blame] | 22 | import com.google.common.collect.Lists; |
| Dave Borowitz | 3b744b1 | 2016-08-19 16:11:10 -0400 | [diff] [blame] | 23 | import java.io.IOException; |
| 24 | import java.io.OutputStream; |
| 25 | import java.util.List; |
| 26 | import java.util.Map; |
| David Pursehouse | a4d3935 | 2018-01-23 08:27:29 +0900 | [diff] [blame] | 27 | import org.apache.commons.text.StringEscapeUtils; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 28 | import org.eclipse.jgit.diff.DiffEntry; |
| Dave Borowitz | a999b16 | 2014-04-21 16:51:47 -0700 | [diff] [blame] | 29 | import org.eclipse.jgit.diff.DiffEntry.ChangeType; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 30 | import org.eclipse.jgit.diff.DiffFormatter; |
| 31 | import org.eclipse.jgit.diff.RawText; |
| 32 | import org.eclipse.jgit.patch.FileHeader; |
| 33 | import org.eclipse.jgit.patch.FileHeader.PatchType; |
| 34 | import org.eclipse.jgit.util.RawParseUtils; |
| 35 | |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 36 | /** Formats a unified format patch as UTF-8 encoded HTML. */ |
| 37 | final class HtmlDiffFormatter extends DiffFormatter { |
| David Pursehouse | ccaa85d | 2017-05-30 10:47:27 +0900 | [diff] [blame] | 38 | private static final byte[] DIFF_BEGIN = |
| 39 | "<pre class=\"u-pre u-monospace Diff-unified\">".getBytes(UTF_8); |
| David Pletcher | d7bdaf3 | 2014-08-27 14:50:32 -0700 | [diff] [blame] | 40 | private static final byte[] DIFF_END = "</pre>".getBytes(UTF_8); |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 41 | |
| Andrew Bonventre | b33426e | 2015-09-09 18:28:28 -0400 | [diff] [blame] | 42 | private static final byte[] HUNK_BEGIN = "<span class=\"Diff-hunk\">".getBytes(UTF_8); |
| David Pletcher | d7bdaf3 | 2014-08-27 14:50:32 -0700 | [diff] [blame] | 43 | private static final byte[] HUNK_END = "</span>".getBytes(UTF_8); |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 44 | |
| Andrew Bonventre | b33426e | 2015-09-09 18:28:28 -0400 | [diff] [blame] | 45 | 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 Pletcher | d7bdaf3 | 2014-08-27 14:50:32 -0700 | [diff] [blame] | 48 | private static final byte[] LINE_END = "</span>\n".getBytes(UTF_8); |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 49 | |
| 50 | private final Renderer renderer; |
| Dave Borowitz | a999b16 | 2014-04-21 16:51:47 -0700 | [diff] [blame] | 51 | private final GitilesView view; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 52 | private int fileIndex; |
| Dave Borowitz | a999b16 | 2014-04-21 16:51:47 -0700 | [diff] [blame] | 53 | private DiffEntry entry; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 54 | |
| Dave Borowitz | a999b16 | 2014-04-21 16:51:47 -0700 | [diff] [blame] | 55 | HtmlDiffFormatter(Renderer renderer, GitilesView view, OutputStream out) { |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 56 | super(out); |
| 57 | this.renderer = checkNotNull(renderer, "renderer"); |
| Dave Borowitz | a999b16 | 2014-04-21 16:51:47 -0700 | [diff] [blame] | 58 | this.view = checkNotNull(view, "view"); |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 59 | } |
| 60 | |
| 61 | @Override |
| 62 | public void format(List<? extends DiffEntry> entries) throws IOException { |
| 63 | for (fileIndex = 0; fileIndex < entries.size(); fileIndex++) { |
| Dave Borowitz | a999b16 | 2014-04-21 16:51:47 -0700 | [diff] [blame] | 64 | entry = entries.get(fileIndex); |
| 65 | format(entry); |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 66 | } |
| 67 | } |
| 68 | |
| 69 | @Override |
| Han-Wen Nienhuys | c0200f6 | 2016-05-02 17:34:51 +0200 | [diff] [blame] | 70 | public void format(FileHeader hdr, RawText a, RawText b) throws IOException { |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 71 | 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 Nienhuys | c0200f6 | 2016-05-02 17:34:51 +0200 | [diff] [blame] | 85 | private void renderHeader(String header) throws IOException { |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 86 | int lf = header.indexOf('\n'); |
| Han-Wen Nienhuys | c0200f6 | 2016-05-02 17:34:51 +0200 | [diff] [blame] | 87 | String rest = 0 <= lf ? header.substring(lf + 1) : ""; |
| Dave Borowitz | a999b16 | 2014-04-21 16:51:47 -0700 | [diff] [blame] | 88 | |
| 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 Nienhuys | c0200f6 | 2016-05-02 17:34:51 +0200 | [diff] [blame] | 93 | parts.add( |
| 94 | ImmutableMap.of( |
| 95 | "text", GIT_PATH.quote(getOldPrefix() + entry.getOldPath()), |
| 96 | "url", revisionUrl(view.getOldRevision(), entry.getOldPath()))); |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 97 | } else { |
| Han-Wen Nienhuys | c0200f6 | 2016-05-02 17:34:51 +0200 | [diff] [blame] | 98 | parts.add(ImmutableMap.of("text", GIT_PATH.quote(getOldPrefix() + entry.getNewPath()))); |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 99 | } |
| Dave Borowitz | a999b16 | 2014-04-21 16:51:47 -0700 | [diff] [blame] | 100 | if (entry.getChangeType() != ChangeType.DELETE) { |
| Han-Wen Nienhuys | c0200f6 | 2016-05-02 17:34:51 +0200 | [diff] [blame] | 101 | parts.add( |
| 102 | ImmutableMap.of( |
| 103 | "text", GIT_PATH.quote(getNewPrefix() + entry.getNewPath()), |
| 104 | "url", revisionUrl(view.getRevision(), entry.getNewPath()))); |
| Dave Borowitz | a999b16 | 2014-04-21 16:51:47 -0700 | [diff] [blame] | 105 | } else { |
| Han-Wen Nienhuys | c0200f6 | 2016-05-02 17:34:51 +0200 | [diff] [blame] | 106 | parts.add(ImmutableMap.of("text", GIT_PATH.quote(getNewPrefix() + entry.getOldPath()))); |
| Dave Borowitz | a999b16 | 2014-04-21 16:51:47 -0700 | [diff] [blame] | 107 | } |
| 108 | |
| Han-Wen Nienhuys | c0200f6 | 2016-05-02 17:34:51 +0200 | [diff] [blame] | 109 | 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 Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 116 | } |
| 117 | |
| Dave Borowitz | a999b16 | 2014-04-21 16:51:47 -0700 | [diff] [blame] | 118 | 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 Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 127 | @Override |
| Han-Wen Nienhuys | c0200f6 | 2016-05-02 17:34:51 +0200 | [diff] [blame] | 128 | protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine, int bEndLine) |
| 129 | throws IOException { |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 130 | 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 Nienhuys | c0200f6 | 2016-05-02 17:34:51 +0200 | [diff] [blame] | 137 | protected void writeLine(char prefix, RawText text, int cur) throws IOException { |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 138 | // 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 Pearce | eea6373 | 2012-12-26 12:40:41 -0800 | [diff] [blame] | 154 | out.write(prefix); |
| David Pletcher | d7bdaf3 | 2014-08-27 14:50:32 -0700 | [diff] [blame] | 155 | out.write(StringEscapeUtils.escapeHtml4(text.getString(cur)).getBytes(UTF_8)); |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 156 | out.write(LINE_END); |
| 157 | } |
| 158 | } |