You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

BlamePage.java 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. /*
  2. * Copyright 2011 gitblit.com.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.gitblit.wicket.pages;
  17. import java.awt.Color;
  18. import java.text.DateFormat;
  19. import java.text.MessageFormat;
  20. import java.text.SimpleDateFormat;
  21. import java.util.Comparator;
  22. import java.util.Date;
  23. import java.util.HashSet;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.Set;
  27. import java.util.TreeSet;
  28. import org.apache.wicket.Component;
  29. import org.apache.wicket.PageParameters;
  30. import org.apache.wicket.behavior.SimpleAttributeModifier;
  31. import org.apache.wicket.markup.html.basic.Label;
  32. import org.apache.wicket.markup.html.link.BookmarkablePageLink;
  33. import org.apache.wicket.markup.repeater.Item;
  34. import org.apache.wicket.markup.repeater.data.DataView;
  35. import org.apache.wicket.markup.repeater.data.ListDataProvider;
  36. import org.eclipse.jgit.lib.ObjectId;
  37. import org.eclipse.jgit.revwalk.RevCommit;
  38. import com.gitblit.Keys;
  39. import com.gitblit.models.AnnotatedLine;
  40. import com.gitblit.models.PathModel;
  41. import com.gitblit.utils.ColorFactory;
  42. import com.gitblit.utils.DiffUtils;
  43. import com.gitblit.utils.JGitUtils;
  44. import com.gitblit.utils.StringUtils;
  45. import com.gitblit.wicket.CacheControl;
  46. import com.gitblit.wicket.CacheControl.LastModified;
  47. import com.gitblit.wicket.WicketUtils;
  48. import com.gitblit.wicket.panels.CommitHeaderPanel;
  49. import com.gitblit.wicket.panels.LinkPanel;
  50. import com.gitblit.wicket.panels.PathBreadcrumbsPanel;
  51. @CacheControl(LastModified.BOOT)
  52. public class BlamePage extends RepositoryPage {
  53. /**
  54. * The different types of Blame visualizations.
  55. */
  56. private enum BlameType {
  57. COMMIT,
  58. AUTHOR,
  59. AGE;
  60. private BlameType() {
  61. }
  62. public static BlameType get(String name) {
  63. for (BlameType blameType : BlameType.values()) {
  64. if (blameType.name().equalsIgnoreCase(name)) {
  65. return blameType;
  66. }
  67. }
  68. throw new IllegalArgumentException("Unknown Blame Type [" + name
  69. + "]");
  70. }
  71. @Override
  72. public String toString() {
  73. return name().toLowerCase();
  74. }
  75. }
  76. public BlamePage(PageParameters params) {
  77. super(params);
  78. final String blobPath = WicketUtils.getPath(params);
  79. final String blameTypeParam = params.getString("blametype", BlameType.COMMIT.toString());
  80. final BlameType activeBlameType = BlameType.get(blameTypeParam);
  81. RevCommit commit = getCommit();
  82. add(new BookmarkablePageLink<Void>("blobLink", BlobPage.class,
  83. WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
  84. add(new BookmarkablePageLink<Void>("commitLink", CommitPage.class,
  85. WicketUtils.newObjectParameter(repositoryName, objectId)));
  86. add(new BookmarkablePageLink<Void>("commitDiffLink", CommitDiffPage.class,
  87. WicketUtils.newObjectParameter(repositoryName, objectId)));
  88. // blame page links
  89. add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
  90. WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
  91. // "Blame by" links
  92. for (BlameType type : BlameType.values()) {
  93. String typeString = type.toString();
  94. PageParameters blameTypePageParam =
  95. WicketUtils.newBlameTypeParameter(repositoryName, commit.getName(),
  96. WicketUtils.getPath(params), typeString);
  97. String blameByLinkText = "blameBy"
  98. + Character.toUpperCase(typeString.charAt(0)) + typeString.substring(1)
  99. + "Link";
  100. BookmarkablePageLink<Void> blameByPageLink =
  101. new BookmarkablePageLink<Void>(blameByLinkText, BlamePage.class, blameTypePageParam);
  102. if (activeBlameType == type) {
  103. blameByPageLink.add(new SimpleAttributeModifier("style", "font-weight:bold;"));
  104. }
  105. add(blameByPageLink);
  106. }
  107. add(new CommitHeaderPanel("commitHeader", repositoryName, commit));
  108. add(new PathBreadcrumbsPanel("breadcrumbs", repositoryName, blobPath, objectId));
  109. String format = app().settings().getString(Keys.web.datetimestampLongFormat,
  110. "EEEE, MMMM d, yyyy HH:mm Z");
  111. final DateFormat df = new SimpleDateFormat(format);
  112. df.setTimeZone(getTimeZone());
  113. PathModel pathModel = null;
  114. List<PathModel> paths = JGitUtils.getFilesInPath(getRepository(), StringUtils.getRootPath(blobPath), commit);
  115. for (PathModel path : paths) {
  116. if (path.path.equals(blobPath)) {
  117. pathModel = path;
  118. break;
  119. }
  120. }
  121. if (pathModel == null) {
  122. final String notFound = MessageFormat.format("Blame page failed to find {0} in {1} @ {2}",
  123. blobPath, repositoryName, objectId);
  124. logger.error(notFound);
  125. add(new Label("annotation").setVisible(false));
  126. add(new Label("missingBlob", missingBlob(blobPath, commit)).setEscapeModelStrings(false));
  127. return;
  128. }
  129. add(new Label("missingBlob").setVisible(false));
  130. List<AnnotatedLine> lines = DiffUtils.blame(getRepository(), blobPath, objectId);
  131. final Map<?, String> colorMap = initializeColors(activeBlameType, lines);
  132. ListDataProvider<AnnotatedLine> blameDp = new ListDataProvider<AnnotatedLine>(lines);
  133. DataView<AnnotatedLine> blameView = new DataView<AnnotatedLine>("annotation", blameDp) {
  134. private static final long serialVersionUID = 1L;
  135. private String lastCommitId = "";
  136. private boolean showInitials = true;
  137. private String zeroId = ObjectId.zeroId().getName();
  138. @Override
  139. public void populateItem(final Item<AnnotatedLine> item) {
  140. final AnnotatedLine entry = item.getModelObject();
  141. // commit id and author
  142. if (!lastCommitId.equals(entry.commitId)) {
  143. lastCommitId = entry.commitId;
  144. if (zeroId.equals(entry.commitId)) {
  145. // unknown commit
  146. item.add(new Label("commit", "<?>"));
  147. showInitials = false;
  148. } else {
  149. // show the link for first line
  150. LinkPanel commitLink = new LinkPanel("commit", null,
  151. getShortObjectId(entry.commitId), CommitPage.class,
  152. newCommitParameter(entry.commitId));
  153. WicketUtils.setHtmlTooltip(commitLink,
  154. MessageFormat.format("{0}, {1}", entry.author, df.format(entry.when)));
  155. item.add(commitLink);
  156. WicketUtils.setCssStyle(item, "border-top: 1px solid #ddd;");
  157. showInitials = true;
  158. }
  159. } else {
  160. if (showInitials) {
  161. showInitials = false;
  162. // show author initials
  163. item.add(new Label("commit", getInitials(entry.author)));
  164. } else {
  165. // hide the commit link until the next block
  166. item.add(new Label("commit").setVisible(false));
  167. }
  168. }
  169. // line number
  170. item.add(new Label("line", "" + entry.lineNumber));
  171. // line content
  172. String color;
  173. switch (activeBlameType) {
  174. case AGE:
  175. color = colorMap.get(entry.when);
  176. break;
  177. case AUTHOR:
  178. color = colorMap.get(entry.author);
  179. break;
  180. default:
  181. color = colorMap.get(entry.commitId);
  182. break;
  183. }
  184. Component data = new Label("data", StringUtils.escapeForHtml(entry.data, true)).setEscapeModelStrings(false);
  185. data.add(new SimpleAttributeModifier("style", "background-color: " + color + ";"));
  186. item.add(data);
  187. }
  188. };
  189. add(blameView);
  190. }
  191. private String getInitials(String author) {
  192. StringBuilder sb = new StringBuilder();
  193. String[] chunks = author.split(" ");
  194. for (String chunk : chunks) {
  195. sb.append(chunk.charAt(0));
  196. }
  197. return sb.toString().toUpperCase();
  198. }
  199. @Override
  200. protected String getPageName() {
  201. return getString("gb.blame");
  202. }
  203. @Override
  204. protected Class<? extends BasePage> getRepoNavPageClass() {
  205. return TreePage.class;
  206. }
  207. protected String missingBlob(String blobPath, RevCommit commit) {
  208. StringBuilder sb = new StringBuilder();
  209. sb.append("<div class=\"alert alert-error\">");
  210. String pattern = getString("gb.doesNotExistInTree").replace("{0}", "<b>{0}</b>").replace("{1}", "<b>{1}</b>");
  211. sb.append(MessageFormat.format(pattern, blobPath, commit.getTree().getId().getName()));
  212. sb.append("</div>");
  213. return sb.toString();
  214. }
  215. private Map<?, String> initializeColors(BlameType blameType, List<AnnotatedLine> lines) {
  216. ColorFactory colorFactory = new ColorFactory();
  217. Map<?, String> colorMap;
  218. if (BlameType.AGE == blameType) {
  219. Set<Date> keys = new TreeSet<Date>(new Comparator<Date>() {
  220. @Override
  221. public int compare(Date o1, Date o2) {
  222. // younger code has a brighter, older code lightens to white
  223. return o1.compareTo(o2);
  224. }
  225. });
  226. for (AnnotatedLine line : lines) {
  227. keys.add(line.when);
  228. }
  229. // TODO consider making this a setting
  230. colorMap = colorFactory.getGraduatedColorMap(keys, Color.decode("#FFA63A"));
  231. } else {
  232. Set<String> keys = new HashSet<String>();
  233. for (AnnotatedLine line : lines) {
  234. if (blameType == BlameType.AUTHOR) {
  235. keys.add(line.author);
  236. } else {
  237. keys.add(line.commitId);
  238. }
  239. }
  240. colorMap = colorFactory.getRandomColorMap(keys);
  241. }
  242. return colorMap;
  243. }
  244. }