Implement HTML diff based on daisydiff To compare two versions of a document it is now possible to specify 2 revisions in the URL. In the returned HTML the differences between the 2 versions of the document are highlighted, e.g. additions with green background and deletions with red background. To generate the HTML diff daisydiff is used [1]. Which differences are highlighted depends on the specified diff mode which is represented in the URL by a char sequence between the 2 revisions: - revisionA<->revisionB: Generates the unified diff between the 2 revisions, additions are highlighted by green background, deletions are highlighted by red background - revisionA<-revisionB: Generates the side-by-side diff between the 2 revisions for side A, deletions are highlighted by red background - revisionA->revisionB: Generates the side-by-side diff between the 2 revisions for side B, additions are highlighted by green background For now the HTML diff is only accessible by manually manipulating the URL, making it available from the UI will be done in follow-up commits. The htmlheader template that comes with daisydiff was stripped down to only highlight the differences in the HTML. Other features, like navigation buttons to skip through the diffs, popups on the deleted/added elements and image diff highligting have been removed. The idea is to start with something simple and to enable other features later if needed. [1] http://code.google.com/p/daisydiff/ Change-Id: Ie0a7bddac415ac41bfe445ee48eaffc6b6ac8d68 Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
diff --git a/BUCK b/BUCK index cccde03..25b0467 100644 --- a/BUCK +++ b/BUCK
@@ -5,6 +5,9 @@ ASCIIDOCTOR = '//lib/asciidoctor:asciidoc_lib' if STANDALONE_MODE \ else '//plugins/x-docs/lib/asciidoctor:asciidoc_lib' +DAISYDIFF = '//lib/daisydiff:daisydiff_lib' if STANDALONE_MODE \ + else '//plugins/x-docs/lib/daisydiff:daisydiff_lib' + gerrit_plugin( name = 'x-docs', srcs = glob(['src/main/java/**/*.java']), @@ -18,7 +21,10 @@ 'Gerrit-Module: com.googlesource.gerrit.plugins.xdocs.Module', 'Gerrit-InitStep: com.googlesource.gerrit.plugins.xdocs.XDocInit', ], - deps = [ASCIIDOCTOR], + deps = [ + ASCIIDOCTOR, + DAISYDIFF, + ], ) # this is required for bucklets/tools/eclipse/project.py to work
diff --git a/lib/daisydiff/BUCK b/lib/daisydiff/BUCK new file mode 100644 index 0000000..dd4a9a9 --- /dev/null +++ b/lib/daisydiff/BUCK
@@ -0,0 +1,18 @@ +include_defs('//bucklets/maven_jar.bucklet') + +java_library( + name = 'daisydiff_lib', + exported_deps = [ + ':daisydiff', + ], + visibility = ['PUBLIC'], +) + +maven_jar( + name = 'daisydiff', + id = 'org.outerj.daisy:daisydiff:1.1', + sha1 = 'a67ed5147dd164e0614f1efc0145d06851c90d4f', + repository = 'https://maven.atlassian.com/content/repositories/atlassian-3rdparty', + license = 'Apache2.0', + attach_source = False, +)
diff --git a/pom.xml b/pom.xml index 492a79b..7a08bdf 100644 --- a/pom.xml +++ b/pom.xml
@@ -130,6 +130,11 @@ <artifactId>asciidoctorj</artifactId> <version>1.5.1</version> </dependency> + <dependency> + <groupId>org.outerj.daisy</groupId> + <artifactId>daisydiff</artifactId> + <version>1.1</version> + </dependency> </dependencies> <repositories> @@ -137,5 +142,9 @@ <id>snapshot-repository</id> <url>https://oss.sonatype.org/content/repositories/snapshots/</url> </repository> + <repository> + <id>atlassian</id> + <url>https://maven.atlassian.com/content/repositories/atlassian-3rdparty</url> + </repository> </repositories> </project>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/DiffMode.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/DiffMode.java new file mode 100644 index 0000000..5f37e5e --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/DiffMode.java
@@ -0,0 +1,19 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.xdocs; + +public enum DiffMode { + NO_DIFF, UNIFIED, SIDEBYSIDE_A, SIDEBYSIDE_B; +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocCache.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocCache.java index 3d1226f..91917c4 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocCache.java +++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocCache.java
@@ -43,15 +43,15 @@ } public Resource get(FormatterProvider formatter, Project.NameKey project, - String file, ObjectId revId) { + String file, ObjectId revId, ObjectId revIdB, DiffMode diffMode) { ProjectState p = projectCache.get(project); ObjectId metaConfigRevId = p != null && p.getConfig().getRevision() != null ? p.getConfig().getRevision() : ObjectId.zeroId(); return cache.getUnchecked((new XDocResourceKey(formatter.getName(), - project, file, revId, metaConfigRevId, getParentsHash(project))) - .asString()); + project, file, revId, metaConfigRevId, getParentsHash(project), + revIdB, diffMode)).asString()); } private String getParentsHash(Project.NameKey project) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocInit.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocInit.java index 25cd2a2..0a3323b 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocInit.java +++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocInit.java
@@ -14,6 +14,8 @@ package com.googlesource.gerrit.plugins.xdocs; +import static com.google.gerrit.pgm.init.api.InitUtil.extract; + import com.google.gerrit.extensions.annotations.PluginName; import com.google.gerrit.pgm.init.api.ConsoleUI; import com.google.gerrit.pgm.init.api.InitStep; @@ -54,6 +56,13 @@ ui.message("Initialized %s plugin: %s", pluginName, pluginConfig.getAbsolutePath()); } + + extract(new File(sitePaths.static_dir, "xdocs/css/unified.css"), + XDocInit.class, "diff/unified.css"); + extract(new File(sitePaths.static_dir, "xdocs/css/sidebyside-a.css"), + XDocInit.class, "diff/sidebyside-a.css"); + extract(new File(sitePaths.static_dir, "xdocs/css/sidebyside-b.css"), + XDocInit.class, "diff/sidebyside-b.css"); } @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocLoader.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocLoader.java index 7971096..15c1ce5 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocLoader.java +++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocLoader.java
@@ -53,15 +53,34 @@ import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.PathFilter; +import org.outerj.daisy.diff.HtmlCleaner; +import org.outerj.daisy.diff.XslFilter; +import org.outerj.daisy.diff.html.HTMLDiffer; +import org.outerj.daisy.diff.html.HtmlSaxDiffOutput; +import org.outerj.daisy.diff.html.TextNodeComparator; +import org.outerj.daisy.diff.html.dom.DomTreeBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.xml.sax.ContentHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.sax.TransformerHandler; +import javax.xml.transform.stream.StreamResult; + @Singleton public class XDocLoader extends CacheLoader<String, Resource> { private static final Logger log = LoggerFactory.getLogger(XDocLoader.class); @@ -97,24 +116,23 @@ RevWalk rw = new RevWalk(repo); try { ObjectId revId = checkRevId(key.getRevId()); - RevCommit commit = rw.parseCommit(revId); - RevTree tree = commit.getTree(); - TreeWalk tw = new TreeWalk(repo); - try { - tw.addTree(tree); - tw.setRecursive(true); - tw.setFilter(PathFilter.create(key.getResource())); - if (!tw.next()) { + String html = loadHtml(formatter, repo, rw, key, revId); + + if (key.getDiffMode() != DiffMode.NO_DIFF) { + ObjectId revIdB = checkRevId(key.getRevIdB()); + String htmlB = loadHtml(formatter, repo, rw, key, revIdB); + if (html == null && htmlB == null) { throw new ResourceNotFoundException(); } - ObjectId objectId = tw.getObjectId(0); - ObjectLoader loader = repo.open(objectId); - String html = - getHtml(formatter, repo, loader, key.getProject(), revId); - return getAsHtmlResource(html, commit.getCommitTime()); - } finally { - tw.release(); + html = diffHtml(html, htmlB, key.getDiffMode()); + } else { + if (html == null) { + throw new ResourceNotFoundException(); + } } + + RevCommit commit = rw.parseCommit(revId); + return getAsHtmlResource(html, commit.getCommitTime()); } finally { rw.release(); } @@ -145,6 +163,27 @@ return revId; } + private String loadHtml(FormatterProvider formatter, Repository repo, + RevWalk rw, XDocResourceKey key, ObjectId revId) throws IOException, + ResourceNotFoundException, MethodNotAllowedException, GitAPIException { + RevCommit commit = rw.parseCommit(revId); + RevTree tree = commit.getTree(); + TreeWalk tw = new TreeWalk(repo); + try { + tw.addTree(tree); + tw.setRecursive(true); + tw.setFilter(PathFilter.create(key.getResource())); + if (!tw.next()) { + return null; + } + ObjectId objectId = tw.getObjectId(0); + ObjectLoader loader = repo.open(objectId); + return getHtml(formatter, repo, loader, key.getProject(), revId); + } finally { + tw.release(); + } + } + private String getHtml(FormatterProvider formatter, Repository repo, ObjectLoader loader, Project.NameKey project, ObjectId revId) throws MethodNotAllowedException, IOException, GitAPIException, @@ -189,6 +228,60 @@ } } + private String diffHtml(String htmlA, String htmlB, DiffMode diffMode) + throws IOException, TransformerConfigurationException, SAXException, + ResourceNotFoundException { + ByteArrayOutputStream htmlDiff = new ByteArrayOutputStream(); + + SAXTransformerFactory tf = + (SAXTransformerFactory) TransformerFactory.newInstance(); + TransformerHandler result = tf.newTransformerHandler(); + result.setResult(new StreamResult(htmlDiff)); + + String htmlHeader = "com/googlesource/gerrit/plugins/xdocs/diff/htmlheader-"; + switch (diffMode) { + case SIDEBYSIDE_A: + htmlHeader += "sidebyside-a.xsl"; + break; + case SIDEBYSIDE_B: + htmlHeader += "sidebyside-b.xsl"; + break; + case UNIFIED: + htmlHeader += "unified.xsl"; + break; + default: + log.error(String.format("Unsupported diff mode: %s", diffMode.name())); + throw new ResourceNotFoundException(); + } + + ContentHandler postProcess = new XslFilter().xsl(result, htmlHeader); + postProcess.startDocument(); + postProcess.startElement("", "diffreport", "diffreport", + new AttributesImpl()); + postProcess.startElement("", "diff", "diff", + new AttributesImpl()); + + HtmlSaxDiffOutput output = new HtmlSaxDiffOutput(postProcess, "diff"); + HTMLDiffer differ = new HTMLDiffer(output); + differ.diff(getComparator(htmlA), getComparator(htmlB)); + + postProcess.endElement("", "diff", "diff"); + postProcess.endElement("", "diffreport", "diffreport"); + postProcess.endDocument(); + + return htmlDiff.toString(UTF_8.name()); + } + + private TextNodeComparator getComparator(String html) throws IOException, + SAXException { + InputSource source = + new InputSource(new ByteArrayInputStream( + Strings.nullToEmpty(html).getBytes(UTF_8))); + DomTreeBuilder handler = new DomTreeBuilder(); + new HtmlCleaner().cleanAndParse(source, handler); + return new TextNodeComparator(handler, Locale.US); + } + private ConfigSection getFormatterConfig(String formatterName) { XDocGlobalConfig cfg = new XDocGlobalConfig(cfgFactory.getGlobalPluginConfig(pluginName));
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocResourceKey.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocResourceKey.java index 178eea2..3bb94c7 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocResourceKey.java +++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocResourceKey.java
@@ -29,15 +29,20 @@ private final ObjectId revId; private final ObjectId metaConfigRevId; private final String parentsHash; + private final ObjectId revIdB; + private final DiffMode diffMode; XDocResourceKey(String formatter, Project.NameKey project, String r, - ObjectId revId, ObjectId metaConfigRevId, String parentsHash) { + ObjectId revId, ObjectId metaConfigRevId, String parentsHash, + ObjectId revIdB, DiffMode diffMode) { this.formatter = formatter; this.project = project; this.resource = r; this.revId = revId; this.metaConfigRevId = metaConfigRevId; this.parentsHash = parentsHash; + this.revIdB = revIdB; + this.diffMode = diffMode != null ? diffMode : DiffMode.NO_DIFF; } public String getFormatter() { @@ -56,10 +61,18 @@ return revId; } + public ObjectId getRevIdB() { + return revIdB; + } + + public DiffMode getDiffMode() { + return diffMode; + } + @Override public int hashCode() { return Objects.hash(formatter, project, resource, revId, metaConfigRevId, - parentsHash); + parentsHash, revIdB, diffMode); } @Override @@ -71,7 +84,9 @@ && Objects.equals(resource, rk.resource) && Objects.equals(revId, rk.revId) && Objects.equals(metaConfigRevId, rk.metaConfigRevId) - && Objects.equals(parentsHash, rk.parentsHash); + && Objects.equals(parentsHash, rk.parentsHash) + && Objects.equals(revIdB, rk.revIdB) + && Objects.equals(diffMode, rk.diffMode); } return false; } @@ -89,6 +104,10 @@ b.append(metaConfigRevId != null ? metaConfigRevId.name() : ""); b.append("/"); b.append(Strings.nullToEmpty(parentsHash)); + b.append("/"); + b.append(revIdB != null ? revIdB.name() : ""); + b.append("/"); + b.append(diffMode.name()); return b.toString(); } @@ -100,6 +119,8 @@ String revision = null; String metaConfigRevision = null; String parentsHash = null; + String revisionB = null; + String diffMode = null; if (s.length > 0) { formatter = IdString.fromUrl(s[0]).get(); } @@ -118,11 +139,18 @@ if (s.length > 5) { parentsHash = s[5]; } + if (s.length > 6) { + revisionB = s[6]; + } + if (s.length > 7) { + diffMode = s[7]; + } return new XDocResourceKey(formatter, new Project.NameKey(project), file, - toObjectId(revision), toObjectId(metaConfigRevision), parentsHash); + toObjectId(revision), toObjectId(metaConfigRevision), parentsHash, + toObjectId(revisionB), DiffMode.valueOf(diffMode)); } private static ObjectId toObjectId(String id) { - return id != null ? ObjectId.fromString(id) : null; + return !Strings.isNullOrEmpty(id) ? ObjectId.fromString(id) : null; } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocServlet.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocServlet.java index 78520f7..9d34b0c 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocServlet.java +++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocServlet.java
@@ -19,6 +19,7 @@ import com.google.common.base.CharMatcher; import com.google.common.base.MoreObjects; +import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; import com.google.common.net.HttpHeaders; import com.google.gerrit.extensions.restapi.AuthException; @@ -125,25 +126,36 @@ throw new ResourceNotFoundException(); } + validateDiffMode(key, mimeType); + ProjectControl projectControl = projectControlFactory.validateFor(key.project); - String rev = getRevision(cfg, key.revision, projectControl); + String rev = getRevision(cfg, + MoreObjects.firstNonNull(key.revision, cfg.getIndexRef()), + projectControl); + String revB = getRevision(cfg, key.revisionB, projectControl); Repository repo = repoManager.openRepository(key.project); try { - ObjectId revId = resolveRevision(repo, rev); - + ObjectId revId = + resolveRevision(repo, MoreObjects.firstNonNull(rev, Constants.HEAD)); if (ObjectId.isId(rev)) { validateCanReadCommit(repo, projectControl, revId); } - if (isResourceNotModified(req, key, revId)) { + ObjectId revIdB = resolveRevision(repo, revB); + if (revIdB != null && ObjectId.isId(revB)) { + validateCanReadCommit(repo, projectControl, revIdB); + } + + if (isResourceNotModified(req, key, revId, revIdB)) { res.sendError(SC_NOT_MODIFIED); return; } Resource rsc; if (formatter != null) { - rsc = docCache.get(formatter, key.project, key.file, revId); + rsc = docCache.get(formatter, key.project, key.file, revId, + revIdB, key.diffMode); } else if (isImage(mimeType)) { rsc = getImageResource(repo, revId, key.file); } else { @@ -152,7 +164,7 @@ if (rsc != Resource.NOT_FOUND) { res.setHeader(HttpHeaders.ETAG, - computeETag(key.project, revId, key.file)); + computeETag(key.project, revId, key.file, revIdB, key.diffMode)); } CacheHeaders.setCacheablePrivate(res, 7, TimeUnit.DAYS, false); rsc.send(req, res); @@ -211,6 +223,14 @@ } } + private static void validateDiffMode(ResourceKey key, MimeType mimeType) + throws ResourceNotFoundException { + if (key.diffMode != DiffMode.NO_DIFF + && (key.revisionB == null || isImage(mimeType))) { + throw new ResourceNotFoundException(); + } + } + private ProjectState getProject(ResourceKey key) throws ResourceNotFoundException { ProjectState state = projectCache.get(key.project); @@ -240,29 +260,35 @@ private String getRevision(XDocProjectConfig cfg, String revision, ProjectControl projectControl) throws ResourceNotFoundException, AuthException, IOException { - String rev = revision; - if (rev == null) { - rev = cfg.getIndexRef(); + if (revision == null) { + return null; } - if (Constants.HEAD.equals(rev)) { - rev = getHead.get().apply(new ProjectResource(projectControl)); + + if (ObjectId.isId(revision)) { + return revision; + } + + if (Constants.HEAD.equals(revision)) { + return getHead.get().apply(new ProjectResource(projectControl)); } else { - if (!ObjectId.isId(rev)) { - if (!rev.startsWith(Constants.R_REFS)) { - rev = Constants.R_HEADS + rev; - } - if (!projectControl.controlForRef(rev).isVisible()) { - throw new ResourceNotFoundException(); - } + String rev = revision; + if (!rev.startsWith(Constants.R_REFS)) { + rev = Constants.R_HEADS + rev; } + if (!projectControl.controlForRef(rev).isVisible()) { + throw new ResourceNotFoundException(); + } + return rev; } - return rev; } private static ObjectId resolveRevision(Repository repo, String revision) throws ResourceNotFoundException, IOException { - ObjectId revId = - repo.resolve(MoreObjects.firstNonNull(revision, Constants.HEAD)); + if (revision == null) { + return null; + } + + ObjectId revId = repo.resolve(revision); if (revId == null) { throw new ResourceNotFoundException(); } @@ -284,21 +310,26 @@ } private static boolean isResourceNotModified(HttpServletRequest req, - ResourceKey key, ObjectId revId) { + ResourceKey key, ObjectId revId, ObjectId revIdB) { String receivedETag = req.getHeader(HttpHeaders.IF_NONE_MATCH); if (receivedETag != null) { - return receivedETag.equals(computeETag(key.project, revId, key.file)); + return receivedETag.equals(computeETag(key.project, revId, key.file, + revIdB, key.diffMode)); } return false; } private static String computeETag(Project.NameKey project, ObjectId revId, - String file) { - return Hashing.md5().newHasher() - .putUnencodedChars(project.get()) + String file, ObjectId revIdB, DiffMode diffMode) { + Hasher hasher = Hashing.md5().newHasher(); + hasher.putUnencodedChars(project.get()) .putUnencodedChars(revId.getName()) - .putUnencodedChars(file) - .hash().toString(); + .putUnencodedChars(file); + if (diffMode != DiffMode.NO_DIFF) { + hasher.putUnencodedChars(revIdB.getName()).putUnencodedChars( + diffMode.name()); + } + return hasher.hash().toString(); } private String getRedirectUrl(HttpServletRequest req, ResourceKey key, @@ -323,11 +354,15 @@ final Project.NameKey project; final String file; final String revision; + final String revisionB; + final DiffMode diffMode; static ResourceKey fromPath(String path) { String project; String file = null; String revision = null; + String revisionB = null; + DiffMode diffMode = DiffMode.NO_DIFF; if (!path.startsWith(PATH_PREFIX)) { // should not happen since this servlet is only registered to handle @@ -360,13 +395,34 @@ project = IdString.fromUrl(CharMatcher.is('/').trimTrailingFrom(path)).get(); } - return new ResourceKey(project, file, revision); + if (revision != null) { + if (revision.contains("<->")) { + diffMode = DiffMode.UNIFIED; + int p = revision.indexOf("<->"); + revisionB = revision.substring(p + 3); + revision = revision.substring(0, p); + } else if (revision.contains("<-")) { + diffMode = DiffMode.SIDEBYSIDE_A; + int p = revision.indexOf("<-"); + revisionB = revision.substring(p + 2); + revision = revision.substring(0, p); + } else if (revision.contains("->")) { + diffMode = DiffMode.SIDEBYSIDE_B; + int p = revision.indexOf("->"); + revisionB = revision.substring(p + 2); + revision = revision.substring(0, p); + } + } + + return new ResourceKey(project, file, revision, revisionB, diffMode); } - private ResourceKey(String p, String f, String r) { + private ResourceKey(String p, String f, String r, String r2, DiffMode dm) { project = new Project.NameKey(p); file = f; revision = r; + revisionB = r2; + diffMode = dm; } } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocWebLink.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocWebLink.java index 1454776..05a0e7d 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocWebLink.java +++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocWebLink.java
@@ -120,7 +120,7 @@ if (revId == null) { return null; } - Resource rsc = docCache.get(formatter, p, fileName, revId); + Resource rsc = docCache.get(formatter, p, fileName, revId, null, null); if (rsc != Resource.NOT_FOUND) { StringBuilder url = new StringBuilder(); if (framed) {
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md index 5e99a7f..f7b2632 100644 --- a/src/main/resources/Documentation/about.md +++ b/src/main/resources/Documentation/about.md
@@ -83,3 +83,11 @@ <td><a href="http://commons.apache.org">http://commons.apache.org</a></td> </tr> </table> + +<a id="htmlDiff"> +HTML Diff +--------- + +To generate the HTML diff [daisydiff](http://code.google.com/p/daisydiff/) +is used which is licensed under the +[Apache2.0](../../../Documentation/licenses.html#Apache2_0) license.
diff --git a/src/main/resources/com/googlesource/gerrit/plugins/xdocs/diff/htmlheader-sidebyside-a.xsl b/src/main/resources/com/googlesource/gerrit/plugins/xdocs/diff/htmlheader-sidebyside-a.xsl new file mode 100644 index 0000000..09e1d73 --- /dev/null +++ b/src/main/resources/com/googlesource/gerrit/plugins/xdocs/diff/htmlheader-sidebyside-a.xsl
@@ -0,0 +1,72 @@ +<?xml version="1.0"?> +<!-- + Copyright 2004 Guy Van den Broeck + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> +<xsl:output method="html" indent="yes"/> + +<xsl:template match="/"> + <html> + <head> + <xsl:apply-templates select="diffreport/css/node()"/> + <link href="/static/xdocs/css/sidebyside-a.css" type="text/css" rel="stylesheet"/> + </head> + <body> + <xsl:apply-templates select="diffreport/diff/node()"/> + </body> + </html> +</xsl:template> + +<xsl:template match="@*|node()"> +<xsl:copy> + <xsl:apply-templates select="@*|node()"/> +</xsl:copy> +</xsl:template> + +<xsl:template match="img"> +<img> + <xsl:copy-of select="@*"/> +</img> +</xsl:template> + +<xsl:template match="span[@class='diff-html-changed']"> +<span> + <xsl:copy-of select="@*"/> + <xsl:apply-templates select="node()"/> +</span> +</xsl:template> + +<xsl:template match="span[@class='diff-html-added']"> +<span> + <xsl:copy-of select="@*"/> + <xsl:apply-templates select="node()"/> +</span> +</xsl:template> + +<xsl:template match="span[@class='diff-html-removed']"> +<span> + <xsl:copy-of select="@*"/> + <xsl:apply-templates select="node()"/> +</span> +</xsl:template> + +<xsl:template match="span[@class='diff-html-conflict']"> +<span> + <xsl:copy-of select="@*"/> + <xsl:apply-templates select="node()"/> +</span> +</xsl:template> + +</xsl:stylesheet>
diff --git a/src/main/resources/com/googlesource/gerrit/plugins/xdocs/diff/htmlheader-sidebyside-b.xsl b/src/main/resources/com/googlesource/gerrit/plugins/xdocs/diff/htmlheader-sidebyside-b.xsl new file mode 100644 index 0000000..7c94aa0 --- /dev/null +++ b/src/main/resources/com/googlesource/gerrit/plugins/xdocs/diff/htmlheader-sidebyside-b.xsl
@@ -0,0 +1,72 @@ +<?xml version="1.0"?> +<!-- + Copyright 2004 Guy Van den Broeck + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> +<xsl:output method="html" indent="yes"/> + +<xsl:template match="/"> + <html> + <head> + <xsl:apply-templates select="diffreport/css/node()"/> + <link href="/static/xdocs/css/sidebyside-b.css" type="text/css" rel="stylesheet"/> + </head> + <body> + <xsl:apply-templates select="diffreport/diff/node()"/> + </body> + </html> +</xsl:template> + +<xsl:template match="@*|node()"> +<xsl:copy> + <xsl:apply-templates select="@*|node()"/> +</xsl:copy> +</xsl:template> + +<xsl:template match="img"> +<img> + <xsl:copy-of select="@*"/> +</img> +</xsl:template> + +<xsl:template match="span[@class='diff-html-changed']"> +<span> + <xsl:copy-of select="@*"/> + <xsl:apply-templates select="node()"/> +</span> +</xsl:template> + +<xsl:template match="span[@class='diff-html-added']"> +<span> + <xsl:copy-of select="@*"/> + <xsl:apply-templates select="node()"/> +</span> +</xsl:template> + +<xsl:template match="span[@class='diff-html-removed']"> +<span> + <xsl:copy-of select="@*"/> + <xsl:apply-templates select="node()"/> +</span> +</xsl:template> + +<xsl:template match="span[@class='diff-html-conflict']"> +<span> + <xsl:copy-of select="@*"/> + <xsl:apply-templates select="node()"/> +</span> +</xsl:template> + +</xsl:stylesheet>
diff --git a/src/main/resources/com/googlesource/gerrit/plugins/xdocs/diff/htmlheader-unified.xsl b/src/main/resources/com/googlesource/gerrit/plugins/xdocs/diff/htmlheader-unified.xsl new file mode 100644 index 0000000..4932a53 --- /dev/null +++ b/src/main/resources/com/googlesource/gerrit/plugins/xdocs/diff/htmlheader-unified.xsl
@@ -0,0 +1,72 @@ +<?xml version="1.0"?> +<!-- + Copyright 2004 Guy Van den Broeck + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> +<xsl:output method="html" indent="yes"/> + +<xsl:template match="/"> + <html> + <head> + <xsl:apply-templates select="diffreport/css/node()"/> + <link href="/static/xdocs/css/unified.css" type="text/css" rel="stylesheet"/> + </head> + <body> + <xsl:apply-templates select="diffreport/diff/node()"/> + </body> + </html> +</xsl:template> + +<xsl:template match="@*|node()"> +<xsl:copy> + <xsl:apply-templates select="@*|node()"/> +</xsl:copy> +</xsl:template> + +<xsl:template match="img"> +<img> + <xsl:copy-of select="@*"/> +</img> +</xsl:template> + +<xsl:template match="span[@class='diff-html-changed']"> +<span> + <xsl:copy-of select="@*"/> + <xsl:apply-templates select="node()"/> +</span> +</xsl:template> + +<xsl:template match="span[@class='diff-html-added']"> +<span> + <xsl:copy-of select="@*"/> + <xsl:apply-templates select="node()"/> +</span> +</xsl:template> + +<xsl:template match="span[@class='diff-html-removed']"> +<span> + <xsl:copy-of select="@*"/> + <xsl:apply-templates select="node()"/> +</span> +</xsl:template> + +<xsl:template match="span[@class='diff-html-conflict']"> +<span> + <xsl:copy-of select="@*"/> + <xsl:apply-templates select="node()"/> +</span> +</xsl:template> + +</xsl:stylesheet>
diff --git a/src/main/resources/com/googlesource/gerrit/plugins/xdocs/diff/sidebyside-a.css b/src/main/resources/com/googlesource/gerrit/plugins/xdocs/diff/sidebyside-a.css new file mode 100644 index 0000000..e8a4824 --- /dev/null +++ b/src/main/resources/com/googlesource/gerrit/plugins/xdocs/diff/sidebyside-a.css
@@ -0,0 +1,153 @@ +/* + * Styles for the Tag Diff + */ +span.diff-tag-html { + font-family: "Andale Mono" monospace; + font-size: 80%; +} + +span.diff-tag-removed { + font-size: 100%; + text-decoration: line-through; + background-color: #fdc6c6; /* light red */ +} + +span.diff-tag-added { + display: none; +} + +span.diff-tag-conflict { + font-size: 100%; + background-color: #f781be; /* light rose */ +} + +/* + * Styles for the HTML Diff + */ +span.diff-html-added { + display: none; +} + +span.diff-html-removed { + font-size: 100%; + text-decoration: line-through; + background-color: #fdc6c6; /* light red */ +} + +span.diff-html-changed { + background: url(../images/diffunderline.gif) bottom repeat-x; + *background-color: #c6c6fd; /* light blue */ +} + +span.diff-html-conflict { +/* background: url(../images/diffunderline.gif) bottom repeat-x; */ + background-color: #f781be; /* light rose */ +} + +span.diff-html-selected { + background-color: #FF8800; /* light orange */ +} + +span.diff-html-selected img{ + border: 2px solid #FF8800; /* light orange */ +} + +span.diff-html-added img{ + border: 2px solid #ccffcc; +} + +span.diff-html-removed img{ + border: 2px solid #fdc6c6; +} + +span.diff-html-changed img{ + border: 2px dotted #000099; +} + +div.diff-removed-image, div.diff-added-image, div.diff-conflict-image { + height: 300px; + width: 200px; + position: absolute; + opacity : 0.55; + filter: alpha(opacity=55); + -moz-opacity: 0.55; +} + +div.diff-removed-image, div.diff-added-image, div.diff-conflict-image { + margin-top: 2px; + margin-bottom: 2px; + margin-right: 2px; + margin-left: 2px; +} + +div.diff-removed-image { + background-color: #fdc6c6; + background-image: url(../images/diffmin.gif); +} +div.diff-added-image { + background-color: #ccffcc; + background-image: url(../images/diffplus.gif); + background-repeat: no-repeat; +} + +div.diff-conflict-image { + background-color: #f781be; + background-image: url(../images/diffconflict.gif); + background-repeat: no-repeat; +} + +img.diff-icon { + background-color: #FF8800; + background-image: url(../images/bg_rounded.gif); + width: 16px; + height: 16px; + border: 0px none; +} + +table.diff-tooltip-link, table.diff-tooltip-link-changed { + width: 100%; + text-align: center; + Vertical-align: middle; +} + +table.diff-tooltip-link-changed { + border-top: thin dashed #000000; + margin-top: 3px; + padding-top: 3px +} +td.diff-tooltip-prev { + text-align: left; +} + +td.diff-tooltip-next { + text-align: right; +} + +table.diffpage-html-firstlast { + width: 100%; + Vertical-align: middle; +} + +div.diff-topbar{ + border-bottom: 2px solid #FF8800; + border-left: 1px solid #FF8800; + border-right: 1px solid #FF8800; + background-color: #FFF5F5; +} + +a.diffpage-html-a, a.diffpage-html-a:hover, a.diffpage-html-a:link, a.diffpage-html-a:visited, a.diffpage-html-a:active { + text-decoration: none; + color: #FF8800; +} + +.diffpage-html-firstlast a img, .dsydiff-prevnextnav a img { + vertical-align: middle; +} + +ul.changelist { + padding-left: 15px; +} + +body{ + margin-top: 0px; +}
diff --git a/src/main/resources/com/googlesource/gerrit/plugins/xdocs/diff/sidebyside-b.css b/src/main/resources/com/googlesource/gerrit/plugins/xdocs/diff/sidebyside-b.css new file mode 100644 index 0000000..3e2317a --- /dev/null +++ b/src/main/resources/com/googlesource/gerrit/plugins/xdocs/diff/sidebyside-b.css
@@ -0,0 +1,151 @@ +/* + * Styles for the Tag Diff + */ +span.diff-tag-html { + font-family: "Andale Mono" monospace; + font-size: 80%; +} + +span.diff-tag-removed { + display: none; +} + +span.diff-tag-added { + font-size: 100%; + background-color: #ccffcc; /* light green */ +} + +span.diff-tag-conflict { + font-size: 100%; + background-color: #f781be; /* light rose */ +} + +/* + * Styles for the HTML Diff + */ +span.diff-html-added { + font-size: 100%; + background-color: #ccffcc; /* light green */ +} + +span.diff-html-removed { + display: none; +} + +span.diff-html-changed { + background: url(../images/diffunderline.gif) bottom repeat-x; + *background-color: #c6c6fd; /* light blue */ +} + +span.diff-html-conflict { +/* background: url(../images/diffunderline.gif) bottom repeat-x; */ + background-color: #f781be; /* light rose */ +} + +span.diff-html-selected { + background-color: #FF8800; /* light orange */ +} + +span.diff-html-selected img{ + border: 2px solid #FF8800; /* light orange */ +} + +span.diff-html-added img{ + border: 2px solid #ccffcc; +} + +span.diff-html-removed img{ + border: 2px solid #fdc6c6; +} + +span.diff-html-changed img{ + border: 2px dotted #000099; +} + +div.diff-removed-image, div.diff-added-image, div.diff-conflict-image { + height: 300px; + width: 200px; + position: absolute; + opacity : 0.55; + filter: alpha(opacity=55); + -moz-opacity: 0.55; +} + +div.diff-removed-image, div.diff-added-image, div.diff-conflict-image { + margin-top: 2px; + margin-bottom: 2px; + margin-right: 2px; + margin-left: 2px; +} + +div.diff-removed-image { + background-color: #fdc6c6; + background-image: url(../images/diffmin.gif); +} +div.diff-added-image { + background-color: #ccffcc; + background-image: url(../images/diffplus.gif); + background-repeat: no-repeat; +} + +div.diff-conflict-image { + background-color: #f781be; + background-image: url(../images/diffconflict.gif); + background-repeat: no-repeat; +} + +img.diff-icon { + background-color: #FF8800; + background-image: url(../images/bg_rounded.gif); + width: 16px; + height: 16px; + border: 0px none; +} + +table.diff-tooltip-link, table.diff-tooltip-link-changed { + width: 100%; + text-align: center; + Vertical-align: middle; +} + +table.diff-tooltip-link-changed { + border-top: thin dashed #000000; + margin-top: 3px; + padding-top: 3px +} +td.diff-tooltip-prev { + text-align: left; +} + +td.diff-tooltip-next { + text-align: right; +} + +table.diffpage-html-firstlast { + width: 100%; + Vertical-align: middle; +} + +div.diff-topbar{ + border-bottom: 2px solid #FF8800; + border-left: 1px solid #FF8800; + border-right: 1px solid #FF8800; + background-color: #FFF5F5; +} + +a.diffpage-html-a, a.diffpage-html-a:hover, a.diffpage-html-a:link, a.diffpage-html-a:visited, a.diffpage-html-a:active { + text-decoration: none; + color: #FF8800; +} + +.diffpage-html-firstlast a img, .dsydiff-prevnextnav a img { + vertical-align: middle; +} + +ul.changelist { + padding-left: 15px; +} + +body{ + margin-top: 0px; +}
diff --git a/src/main/resources/com/googlesource/gerrit/plugins/xdocs/diff/unified.css b/src/main/resources/com/googlesource/gerrit/plugins/xdocs/diff/unified.css new file mode 100644 index 0000000..2a1e62b --- /dev/null +++ b/src/main/resources/com/googlesource/gerrit/plugins/xdocs/diff/unified.css
@@ -0,0 +1,155 @@ +/* + * Styles for the Tag Diff + */ +span.diff-tag-html { + font-family: "Andale Mono" monospace; + font-size: 80%; +} + +span.diff-tag-removed { + font-size: 100%; + text-decoration: line-through; + background-color: #fdc6c6; /* light red */ +} + +span.diff-tag-added { + font-size: 100%; + background-color: #ccffcc; /* light green */ +} + +span.diff-tag-conflict { + font-size: 100%; + background-color: #f781be; /* light rose */ +} + +/* + * Styles for the HTML Diff + */ +span.diff-html-added { + font-size: 100%; + background-color: #ccffcc; /* light green */ +} + +span.diff-html-removed { + font-size: 100%; + text-decoration: line-through; + background-color: #fdc6c6; /* light red */ +} + +span.diff-html-changed { + background: url(../images/diffunderline.gif) bottom repeat-x; + *background-color: #c6c6fd; /* light blue */ +} + +span.diff-html-conflict { +/* background: url(../images/diffunderline.gif) bottom repeat-x; */ + background-color: #f781be; /* light rose */ +} + +span.diff-html-selected { + background-color: #FF8800; /* light orange */ +} + +span.diff-html-selected img{ + border: 2px solid #FF8800; /* light orange */ +} + +span.diff-html-added img{ + border: 2px solid #ccffcc; +} + +span.diff-html-removed img{ + border: 2px solid #fdc6c6; +} + +span.diff-html-changed img{ + border: 2px dotted #000099; +} + +div.diff-removed-image, div.diff-added-image, div.diff-conflict-image { + height: 300px; + width: 200px; + position: absolute; + opacity : 0.55; + filter: alpha(opacity=55); + -moz-opacity: 0.55; +} + +div.diff-removed-image, div.diff-added-image, div.diff-conflict-image { + margin-top: 2px; + margin-bottom: 2px; + margin-right: 2px; + margin-left: 2px; +} + +div.diff-removed-image { + background-color: #fdc6c6; + background-image: url(../images/diffmin.gif); +} +div.diff-added-image { + background-color: #ccffcc; + background-image: url(../images/diffplus.gif); + background-repeat: no-repeat; +} + +div.diff-conflict-image { + background-color: #f781be; + background-image: url(../images/diffconflict.gif); + background-repeat: no-repeat; +} + +img.diff-icon { + background-color: #FF8800; + background-image: url(../images/bg_rounded.gif); + width: 16px; + height: 16px; + border: 0px none; +} + +table.diff-tooltip-link, table.diff-tooltip-link-changed { + width: 100%; + text-align: center; + Vertical-align: middle; +} + +table.diff-tooltip-link-changed { + border-top: thin dashed #000000; + margin-top: 3px; + padding-top: 3px +} +td.diff-tooltip-prev { + text-align: left; +} + +td.diff-tooltip-next { + text-align: right; +} + +table.diffpage-html-firstlast { + width: 100%; + Vertical-align: middle; +} + +div.diff-topbar{ + border-bottom: 2px solid #FF8800; + border-left: 1px solid #FF8800; + border-right: 1px solid #FF8800; + background-color: #FFF5F5; +} + +a.diffpage-html-a, a.diffpage-html-a:hover, a.diffpage-html-a:link, a.diffpage-html-a:visited, a.diffpage-html-a:active { + text-decoration: none; + color: #FF8800; +} + +.diffpage-html-firstlast a img, .dsydiff-prevnextnav a img { + vertical-align: middle; +} + +ul.changelist { + padding-left: 15px; +} + +body{ + margin-top: 0px; +}