| 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 | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 23 | |
| 24 | import org.apache.commons.lang3.StringEscapeUtils; |
| 25 | import org.eclipse.jgit.diff.DiffEntry; |
| Dave Borowitz | a999b16 | 2014-04-21 16:51:47 -0700 | [diff] [blame] | 26 | import org.eclipse.jgit.diff.DiffEntry.ChangeType; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 27 | import org.eclipse.jgit.diff.DiffFormatter; |
| 28 | import org.eclipse.jgit.diff.RawText; |
| 29 | import org.eclipse.jgit.patch.FileHeader; |
| 30 | import org.eclipse.jgit.patch.FileHeader.PatchType; |
| 31 | import org.eclipse.jgit.util.RawParseUtils; |
| 32 | |
| 33 | import java.io.IOException; |
| 34 | import java.io.OutputStream; |
| 35 | import java.util.List; |
| Dave Borowitz | a999b16 | 2014-04-21 16:51:47 -0700 | [diff] [blame] | 36 | import java.util.Map; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 37 | |
| 38 | /** Formats a unified format patch as UTF-8 encoded HTML. */ |
| 39 | final class HtmlDiffFormatter extends DiffFormatter { |
| Andrew Bonventre | b33426e | 2015-09-09 18:28:28 -0400 | [diff] [blame] | 40 | private static final byte[] DIFF_BEGIN = "<pre class=\"u-pre Diff-unified\">".getBytes(UTF_8); |
| David Pletcher | d7bdaf3 | 2014-08-27 14:50:32 -0700 | [diff] [blame] | 41 | private static final byte[] DIFF_END = "</pre>".getBytes(UTF_8); |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 42 | |
| Andrew Bonventre | b33426e | 2015-09-09 18:28:28 -0400 | [diff] [blame] | 43 | 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] | 44 | private static final byte[] HUNK_END = "</span>".getBytes(UTF_8); |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 45 | |
| Andrew Bonventre | b33426e | 2015-09-09 18:28:28 -0400 | [diff] [blame] | 46 | 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 Pletcher | d7bdaf3 | 2014-08-27 14:50:32 -0700 | [diff] [blame] | 49 | private static final byte[] LINE_END = "</span>\n".getBytes(UTF_8); |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 50 | |
| 51 | private final Renderer renderer; |
| Dave Borowitz | a999b16 | 2014-04-21 16:51:47 -0700 | [diff] [blame] | 52 | private final GitilesView view; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 53 | private int fileIndex; |
| Dave Borowitz | a999b16 | 2014-04-21 16:51:47 -0700 | [diff] [blame] | 54 | private DiffEntry entry; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 55 | |
| Dave Borowitz | a999b16 | 2014-04-21 16:51:47 -0700 | [diff] [blame] | 56 | HtmlDiffFormatter(Renderer renderer, GitilesView view, OutputStream out) { |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 57 | super(out); |
| 58 | this.renderer = checkNotNull(renderer, "renderer"); |
| Dave Borowitz | a999b16 | 2014-04-21 16:51:47 -0700 | [diff] [blame] | 59 | this.view = checkNotNull(view, "view"); |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 60 | } |
| 61 | |
| 62 | @Override |
| 63 | public void format(List<? extends DiffEntry> entries) throws IOException { |
| 64 | for (fileIndex = 0; fileIndex < entries.size(); fileIndex++) { |
| Dave Borowitz | a999b16 | 2014-04-21 16:51:47 -0700 | [diff] [blame] | 65 | entry = entries.get(fileIndex); |
| 66 | format(entry); |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 67 | } |
| 68 | } |
| 69 | |
| 70 | @Override |
| 71 | public void format(FileHeader hdr, RawText a, RawText b) |
| 72 | throws IOException { |
| 73 | int start = hdr.getStartOffset(); |
| 74 | int end = hdr.getEndOffset(); |
| 75 | if (!hdr.getHunks().isEmpty()) { |
| 76 | end = hdr.getHunks().get(0).getStartOffset(); |
| 77 | } |
| 78 | renderHeader(RawParseUtils.decode(hdr.getBuffer(), start, end)); |
| 79 | |
| 80 | if (hdr.getPatchType() == PatchType.UNIFIED) { |
| 81 | getOutputStream().write(DIFF_BEGIN); |
| 82 | format(hdr.toEditList(), a, b); |
| 83 | getOutputStream().write(DIFF_END); |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | private void renderHeader(String header) |
| 88 | throws IOException { |
| 89 | int lf = header.indexOf('\n'); |
| Dave Borowitz | a999b16 | 2014-04-21 16:51:47 -0700 | [diff] [blame] | 90 | String rest = 0 <= lf ? header.substring(lf + 1) : ""; |
| 91 | |
| 92 | // Based on DiffFormatter.formatGitDiffFirstHeaderLine. |
| 93 | List<Map<String, String>> parts = Lists.newArrayListWithCapacity(3); |
| 94 | parts.add(ImmutableMap.of("text", "diff --git")); |
| 95 | if (entry.getChangeType() != ChangeType.ADD) { |
| 96 | parts.add(ImmutableMap.of( |
| 97 | "text", GIT_PATH.quote(getOldPrefix() + entry.getOldPath()), |
| 98 | "url", revisionUrl(view.getOldRevision(), entry.getOldPath()))); |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 99 | } else { |
| Dave Borowitz | a999b16 | 2014-04-21 16:51:47 -0700 | [diff] [blame] | 100 | parts.add(ImmutableMap.of( |
| 101 | "text", GIT_PATH.quote(getOldPrefix() + entry.getNewPath()))); |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 102 | } |
| Dave Borowitz | a999b16 | 2014-04-21 16:51:47 -0700 | [diff] [blame] | 103 | if (entry.getChangeType() != ChangeType.DELETE) { |
| 104 | parts.add(ImmutableMap.of( |
| 105 | "text", GIT_PATH.quote(getNewPrefix() + entry.getNewPath()), |
| 106 | "url", revisionUrl(view.getRevision(), entry.getNewPath()))); |
| 107 | } else { |
| 108 | parts.add(ImmutableMap.of( |
| 109 | "text", GIT_PATH.quote(getNewPrefix() + entry.getOldPath()))); |
| 110 | } |
| 111 | |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 112 | getOutputStream().write(renderer.newRenderer("gitiles.diffHeader") |
| Dave Borowitz | a999b16 | 2014-04-21 16:51:47 -0700 | [diff] [blame] | 113 | .setData(ImmutableMap.of("firstParts", parts, "rest", rest, "fileIndex", fileIndex)) |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 114 | .render() |
| David Pletcher | d7bdaf3 | 2014-08-27 14:50:32 -0700 | [diff] [blame] | 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 |
| 128 | protected void writeHunkHeader(int aStartLine, int aEndLine, |
| 129 | int bStartLine, int bEndLine) throws IOException { |
| 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 |
| 137 | protected void writeLine(char prefix, RawText text, int cur) |
| 138 | throws IOException { |
| 139 | // 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 Pearce | eea6373 | 2012-12-26 12:40:41 -0800 | [diff] [blame] | 155 | out.write(prefix); |
| David Pletcher | d7bdaf3 | 2014-08-27 14:50:32 -0700 | [diff] [blame] | 156 | out.write(StringEscapeUtils.escapeHtml4(text.getString(cur)).getBytes(UTF_8)); |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 157 | out.write(LINE_END); |
| 158 | } |
| 159 | } |