From a502d96a860456ec5e8c96761db70f7cabb74751 Mon Sep 17 00:00:00 2001 From: Paul Martin <paul@paulsputer.com> Date: Sat, 30 Apr 2016 04:19:14 -0400 Subject: [PATCH] Merge pull request #1073 from gitblit/1062-DocEditorUpdates --- src/main/java/com/gitblit/wicket/pages/BlamePage.java | 227 ++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 files changed, 201 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/gitblit/wicket/pages/BlamePage.java b/src/main/java/com/gitblit/wicket/pages/BlamePage.java index d76181d..2fcca0a 100644 --- a/src/main/java/com/gitblit/wicket/pages/BlamePage.java +++ b/src/main/java/com/gitblit/wicket/pages/BlamePage.java @@ -15,84 +15,189 @@ */ package com.gitblit.wicket.pages; +import java.awt.Color; import java.text.DateFormat; import java.text.MessageFormat; import java.text.SimpleDateFormat; +import java.util.Comparator; +import java.util.Date; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import org.apache.wicket.Component; import org.apache.wicket.PageParameters; +import org.apache.wicket.behavior.SimpleAttributeModifier; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.link.BookmarkablePageLink; +import org.apache.wicket.markup.html.link.ExternalLink; import org.apache.wicket.markup.repeater.Item; import org.apache.wicket.markup.repeater.data.DataView; import org.apache.wicket.markup.repeater.data.ListDataProvider; -import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.revwalk.RevCommit; -import com.gitblit.GitBlit; import com.gitblit.Keys; import com.gitblit.models.AnnotatedLine; +import com.gitblit.models.PathModel; +import com.gitblit.utils.ColorFactory; import com.gitblit.utils.DiffUtils; +import com.gitblit.utils.JGitUtils; import com.gitblit.utils.StringUtils; +import com.gitblit.wicket.CacheControl; +import com.gitblit.wicket.CacheControl.LastModified; import com.gitblit.wicket.WicketUtils; import com.gitblit.wicket.panels.CommitHeaderPanel; import com.gitblit.wicket.panels.LinkPanel; import com.gitblit.wicket.panels.PathBreadcrumbsPanel; +@CacheControl(LastModified.BOOT) public class BlamePage extends RepositoryPage { + + /** + * The different types of Blame visualizations. + */ + private enum BlameType { + COMMIT, + + AUTHOR, + + AGE; + + private BlameType() { + } + + public static BlameType get(String name) { + for (BlameType blameType : BlameType.values()) { + if (blameType.name().equalsIgnoreCase(name)) { + return blameType; + } + } + throw new IllegalArgumentException("Unknown Blame Type [" + name + + "]"); + } + + @Override + public String toString() { + return name().toLowerCase(); + } + } public BlamePage(PageParameters params) { super(params); final String blobPath = WicketUtils.getPath(params); - RevCommit commit = getCommit(); + final String blameTypeParam = params.getString("blametype", BlameType.COMMIT.toString()); + final BlameType activeBlameType = BlameType.get(blameTypeParam); - add(new BookmarkablePageLink<Void>("blobLink", BlobPage.class, - WicketUtils.newPathParameter(repositoryName, objectId, blobPath))); + RevCommit commit = getCommit(); + + PathModel pathModel = null; + + List<PathModel> paths = JGitUtils.getFilesInPath(getRepository(), StringUtils.getRootPath(blobPath), commit); + for (PathModel path : paths) { + if (path.path.equals(blobPath)) { + pathModel = path; + break; + } + } + + if (pathModel == null) { + final String notFound = MessageFormat.format("Blame page failed to find {0} in {1} @ {2}", + blobPath, repositoryName, objectId); + logger.error(notFound); + add(new Label("annotation").setVisible(false)); + add(new Label("missingBlob", missingBlob(blobPath, commit)).setEscapeModelStrings(false)); + return; + } + + if (pathModel.isFilestoreItem()) { + String rawUrl = JGitUtils.getLfsRepositoryUrl(getContextUrl(), repositoryName, pathModel.getFilestoreOid()); + add(new ExternalLink("blobLink", rawUrl)); + } else { + add(new BookmarkablePageLink<Void>("blobLink", BlobPage.class, + WicketUtils.newPathParameter(repositoryName, objectId, blobPath))); + } + add(new BookmarkablePageLink<Void>("commitLink", CommitPage.class, WicketUtils.newObjectParameter(repositoryName, objectId))); add(new BookmarkablePageLink<Void>("commitDiffLink", CommitDiffPage.class, WicketUtils.newObjectParameter(repositoryName, objectId))); // blame page links - add(new BookmarkablePageLink<Void>("headLink", BlamePage.class, - WicketUtils.newPathParameter(repositoryName, Constants.HEAD, blobPath))); add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class, WicketUtils.newPathParameter(repositoryName, objectId, blobPath))); + + // "Blame by" links + for (BlameType type : BlameType.values()) { + String typeString = type.toString(); + PageParameters blameTypePageParam = + WicketUtils.newBlameTypeParameter(repositoryName, commit.getName(), + WicketUtils.getPath(params), typeString); + + String blameByLinkText = "blameBy" + + Character.toUpperCase(typeString.charAt(0)) + typeString.substring(1) + + "Link"; + BookmarkablePageLink<Void> blameByPageLink = + new BookmarkablePageLink<Void>(blameByLinkText, BlamePage.class, blameTypePageParam); + + if (activeBlameType == type) { + blameByPageLink.add(new SimpleAttributeModifier("style", "font-weight:bold;")); + } + + add(blameByPageLink); + } add(new CommitHeaderPanel("commitHeader", repositoryName, commit)); add(new PathBreadcrumbsPanel("breadcrumbs", repositoryName, blobPath, objectId)); - String format = GitBlit.getString(Keys.web.datetimestampLongFormat, + String format = app().settings().getString(Keys.web.datetimestampLongFormat, "EEEE, MMMM d, yyyy HH:mm Z"); final DateFormat df = new SimpleDateFormat(format); df.setTimeZone(getTimeZone()); + + + + + + add(new Label("missingBlob").setVisible(false)); + + final int tabLength = app().settings().getInteger(Keys.web.tabLength, 4); List<AnnotatedLine> lines = DiffUtils.blame(getRepository(), blobPath, objectId); + final Map<?, String> colorMap = initializeColors(activeBlameType, lines); ListDataProvider<AnnotatedLine> blameDp = new ListDataProvider<AnnotatedLine>(lines); DataView<AnnotatedLine> blameView = new DataView<AnnotatedLine>("annotation", blameDp) { private static final long serialVersionUID = 1L; - private int count; private String lastCommitId = ""; private boolean showInitials = true; + private String zeroId = ObjectId.zeroId().getName(); + @Override public void populateItem(final Item<AnnotatedLine> item) { - AnnotatedLine entry = item.getModelObject(); - item.add(new Label("line", "" + entry.lineNumber)); - item.add(new Label("data", StringUtils.escapeForHtml(entry.data, true)) - .setEscapeModelStrings(false)); + final AnnotatedLine entry = item.getModelObject(); + + // commit id and author if (!lastCommitId.equals(entry.commitId)) { lastCommitId = entry.commitId; - count++; - // show the link for first line - LinkPanel commitLink = new LinkPanel("commit", null, - getShortObjectId(entry.commitId), CommitPage.class, - newCommitParameter(entry.commitId)); - WicketUtils.setHtmlTooltip(commitLink, - MessageFormat.format("{0}, {1}", entry.author, df.format(entry.when))); - item.add(commitLink); - showInitials = true; + if (zeroId.equals(entry.commitId)) { + // unknown commit + item.add(new Label("commit", "<?>")); + showInitials = false; + } else { + // show the link for first line + LinkPanel commitLink = new LinkPanel("commit", null, + getShortObjectId(entry.commitId), CommitPage.class, + newCommitParameter(entry.commitId)); + WicketUtils.setHtmlTooltip(commitLink, + MessageFormat.format("{0}, {1}", entry.author, df.format(entry.when))); + item.add(commitLink); + WicketUtils.setCssStyle(item, "border-top: 1px solid #ddd;"); + showInitials = true; + } } else { if (showInitials) { showInitials = false; @@ -103,11 +208,26 @@ item.add(new Label("commit").setVisible(false)); } } - if (count % 2 == 0) { - WicketUtils.setCssClass(item, "even"); - } else { - WicketUtils.setCssClass(item, "odd"); + + // line number + item.add(new Label("line", "" + entry.lineNumber)); + + // line content + String color; + switch (activeBlameType) { + case AGE: + color = colorMap.get(entry.when); + break; + case AUTHOR: + color = colorMap.get(entry.author); + break; + default: + color = colorMap.get(entry.commitId); + break; } + Component data = new Label("data", StringUtils.escapeForHtml(entry.data, true, tabLength)).setEscapeModelStrings(false); + data.add(new SimpleAttributeModifier("style", "background-color: " + color + ";")); + item.add(data); } }; add(blameView); @@ -126,4 +246,59 @@ protected String getPageName() { return getString("gb.blame"); } + + @Override + protected boolean isCommitPage() { + return true; + } + + @Override + protected Class<? extends BasePage> getRepoNavPageClass() { + return TreePage.class; + } + + protected String missingBlob(String blobPath, RevCommit commit) { + StringBuilder sb = new StringBuilder(); + sb.append("<div class=\"alert alert-error\">"); + String pattern = getString("gb.doesNotExistInTree").replace("{0}", "<b>{0}</b>").replace("{1}", "<b>{1}</b>"); + sb.append(MessageFormat.format(pattern, blobPath, commit.getTree().getId().getName())); + sb.append("</div>"); + return sb.toString(); + } + + private Map<?, String> initializeColors(BlameType blameType, List<AnnotatedLine> lines) { + ColorFactory colorFactory = new ColorFactory(); + Map<?, String> colorMap; + + if (BlameType.AGE == blameType) { + Set<Date> keys = new TreeSet<Date>(new Comparator<Date>() { + @Override + public int compare(Date o1, Date o2) { + // younger code has a brighter, older code lightens to white + return o1.compareTo(o2); + } + }); + + for (AnnotatedLine line : lines) { + keys.add(line.when); + } + + // TODO consider making this a setting + colorMap = colorFactory.getGraduatedColorMap(keys, Color.decode("#FFA63A")); + } else { + Set<String> keys = new HashSet<String>(); + + for (AnnotatedLine line : lines) { + if (blameType == BlameType.AUTHOR) { + keys.add(line.author); + } else { + keys.add(line.commitId); + } + } + + colorMap = colorFactory.getRandomColorMap(keys); + } + + return colorMap; + } } -- Gitblit v1.9.1