James Moger
2015-11-22 ed552ba47c02779c270ffd62841d6d1048dade70
commit | author | age
716745 1 /*
JM 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
e3733c 18 import java.awt.Color;
008322 19 import java.text.DateFormat;
JM 20 import java.text.MessageFormat;
21 import java.text.SimpleDateFormat;
e3733c 22 import java.util.Comparator;
AL 23 import java.util.Date;
24 import java.util.HashSet;
716745 25 import java.util.List;
e3733c 26 import java.util.Map;
AL 27 import java.util.Set;
28 import java.util.TreeSet;
716745 29
e3733c 30 import org.apache.wicket.Component;
716745 31 import org.apache.wicket.PageParameters;
e3733c 32 import org.apache.wicket.behavior.SimpleAttributeModifier;
716745 33 import org.apache.wicket.markup.html.basic.Label;
JM 34 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
35 import org.apache.wicket.markup.repeater.Item;
36 import org.apache.wicket.markup.repeater.data.DataView;
37 import org.apache.wicket.markup.repeater.data.ListDataProvider;
0078c2 38 import org.eclipse.jgit.lib.ObjectId;
716745 39 import org.eclipse.jgit.revwalk.RevCommit;
JM 40
008322 41 import com.gitblit.Keys;
JM 42 import com.gitblit.models.AnnotatedLine;
ed9d67 43 import com.gitblit.models.PathModel;
e3733c 44 import com.gitblit.utils.ColorFactory;
008322 45 import com.gitblit.utils.DiffUtils;
ed9d67 46 import com.gitblit.utils.JGitUtils;
008322 47 import com.gitblit.utils.StringUtils;
a7db57 48 import com.gitblit.wicket.CacheControl;
JM 49 import com.gitblit.wicket.CacheControl.LastModified;
716745 50 import com.gitblit.wicket.WicketUtils;
JM 51 import com.gitblit.wicket.panels.CommitHeaderPanel;
52 import com.gitblit.wicket.panels.LinkPanel;
53 import com.gitblit.wicket.panels.PathBreadcrumbsPanel;
54
a7db57 55 @CacheControl(LastModified.BOOT)
716745 56 public class BlamePage extends RepositoryPage {
JM 57
e3733c 58     /**
AL 59      * The different types of Blame visualizations.
60      */
61     private enum BlameType {
62         COMMIT,
63
64         AUTHOR,
65
66         AGE;
67
68         private BlameType() {
69         }
70
71         public static BlameType get(String name) {
72             for (BlameType blameType : BlameType.values()) {
73                 if (blameType.name().equalsIgnoreCase(name)) {
74                     return blameType;
75                 }
76             }
77             throw new IllegalArgumentException("Unknown Blame Type [" + name
78                     + "]");
79         }
80
81         @Override
82         public String toString() {
83             return name().toLowerCase();
84         }
85     }
86
716745 87     public BlamePage(PageParameters params) {
JM 88         super(params);
89
90         final String blobPath = WicketUtils.getPath(params);
e3733c 91
AL 92         final String blameTypeParam = params.getString("blametype", BlameType.COMMIT.toString());
93         final BlameType activeBlameType = BlameType.get(blameTypeParam);
716745 94
JM 95         RevCommit commit = getCommit();
96
97         add(new BookmarkablePageLink<Void>("blobLink", BlobPage.class,
008322 98                 WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
716745 99         add(new BookmarkablePageLink<Void>("commitLink", CommitPage.class,
JM 100                 WicketUtils.newObjectParameter(repositoryName, objectId)));
101         add(new BookmarkablePageLink<Void>("commitDiffLink", CommitDiffPage.class,
102                 WicketUtils.newObjectParameter(repositoryName, objectId)));
103
104         // blame page links
105         add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
106                 WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
e3733c 107
AL 108         // "Blame by" links
109         for (BlameType type : BlameType.values()) {
110             String typeString = type.toString();
111             PageParameters blameTypePageParam =
112                     WicketUtils.newBlameTypeParameter(repositoryName, commit.getName(),
113                             WicketUtils.getPath(params), typeString);
114
115             String blameByLinkText = "blameBy"
116                     + Character.toUpperCase(typeString.charAt(0)) + typeString.substring(1)
117                     + "Link";
118             BookmarkablePageLink<Void> blameByPageLink =
119                     new BookmarkablePageLink<Void>(blameByLinkText, BlamePage.class, blameTypePageParam);
120
121             if (activeBlameType == type) {
122                 blameByPageLink.add(new SimpleAttributeModifier("style", "font-weight:bold;"));
123             }
124
125             add(blameByPageLink);
126         }
716745 127
JM 128         add(new CommitHeaderPanel("commitHeader", repositoryName, commit));
129
130         add(new PathBreadcrumbsPanel("breadcrumbs", repositoryName, blobPath, objectId));
131
99d0d4 132         String format = app().settings().getString(Keys.web.datetimestampLongFormat,
a4ed6d 133                 "EEEE, MMMM d, yyyy HH:mm Z");
008322 134         final DateFormat df = new SimpleDateFormat(format);
JM 135         df.setTimeZone(getTimeZone());
699e71 136
ed9d67 137         PathModel pathModel = null;
JM 138         List<PathModel> paths = JGitUtils.getFilesInPath(getRepository(), StringUtils.getRootPath(blobPath), commit);
139         for (PathModel path : paths) {
140             if (path.path.equals(blobPath)) {
141                 pathModel = path;
142                 break;
143             }
144         }
699e71 145
ed9d67 146         if (pathModel == null) {
15e560 147             final String notFound = MessageFormat.format("Blame page failed to find {0} in {1} @ {2}",
JM 148                     blobPath, repositoryName, objectId);
149             logger.error(notFound);
ed9d67 150             add(new Label("annotation").setVisible(false));
JM 151             add(new Label("missingBlob", missingBlob(blobPath, commit)).setEscapeModelStrings(false));
152             return;
153         }
699e71 154
ed9d67 155         add(new Label("missingBlob").setVisible(false));
699e71 156
310a80 157         final int tabLength = app().settings().getInteger(Keys.web.tabLength, 4);
008322 158         List<AnnotatedLine> lines = DiffUtils.blame(getRepository(), blobPath, objectId);
e3733c 159         final Map<?, String> colorMap = initializeColors(activeBlameType, lines);
008322 160         ListDataProvider<AnnotatedLine> blameDp = new ListDataProvider<AnnotatedLine>(lines);
15e560 161         DataView<AnnotatedLine> blameView = new DataView<AnnotatedLine>("annotation", blameDp) {
716745 162             private static final long serialVersionUID = 1L;
008322 163             private String lastCommitId = "";
JM 164             private boolean showInitials = true;
0078c2 165             private String zeroId = ObjectId.zeroId().getName();
716745 166
699e71 167             @Override
008322 168             public void populateItem(final Item<AnnotatedLine> item) {
e3733c 169                 final AnnotatedLine entry = item.getModelObject();
AL 170
171                 // commit id and author
008322 172                 if (!lastCommitId.equals(entry.commitId)) {
JM 173                     lastCommitId = entry.commitId;
0078c2 174                     if (zeroId.equals(entry.commitId)) {
JM 175                         // unknown commit
176                         item.add(new Label("commit", "<?>"));
177                         showInitials = false;
178                     } else {
179                         // show the link for first line
180                         LinkPanel commitLink = new LinkPanel("commit", null,
181                                 getShortObjectId(entry.commitId), CommitPage.class,
182                                 newCommitParameter(entry.commitId));
183                         WicketUtils.setHtmlTooltip(commitLink,
184                                 MessageFormat.format("{0}, {1}", entry.author, df.format(entry.when)));
185                         item.add(commitLink);
e3733c 186                         WicketUtils.setCssStyle(item, "border-top: 1px solid #ddd;");
0078c2 187                         showInitials = true;
JM 188                     }
008322 189                 } else {
JM 190                     if (showInitials) {
191                         showInitials = false;
192                         // show author initials
193                         item.add(new Label("commit", getInitials(entry.author)));
194                     } else {
195                         // hide the commit link until the next block
196                         item.add(new Label("commit").setVisible(false));
197                     }
198                 }
e3733c 199
AL 200                 // line number
201                 item.add(new Label("line", "" + entry.lineNumber));
202
203                 // line content
204                 String color;
205                 switch (activeBlameType) {
206                 case AGE:
207                     color = colorMap.get(entry.when);
208                     break;
209                 case AUTHOR:
210                     color = colorMap.get(entry.author);
211                     break;
212                 default:
213                     color = colorMap.get(entry.commitId);
214                     break;
008322 215                 }
310a80 216                 Component data = new Label("data", StringUtils.escapeForHtml(entry.data, true, tabLength)).setEscapeModelStrings(false);
e3733c 217                 data.add(new SimpleAttributeModifier("style", "background-color: " + color + ";"));
AL 218                 item.add(data);
716745 219             }
JM 220         };
221         add(blameView);
222     }
223
008322 224     private String getInitials(String author) {
JM 225         StringBuilder sb = new StringBuilder();
226         String[] chunks = author.split(" ");
227         for (String chunk : chunks) {
228             sb.append(chunk.charAt(0));
229         }
230         return sb.toString().toUpperCase();
231     }
232
716745 233     @Override
JM 234     protected String getPageName() {
235         return getString("gb.blame");
236     }
699e71 237
6ef8d7 238     @Override
5d5e55 239     protected boolean isCommitPage() {
JM 240         return true;
241     }
242
243     @Override
6ef8d7 244     protected Class<? extends BasePage> getRepoNavPageClass() {
JM 245         return TreePage.class;
246     }
699e71 247
ed9d67 248     protected String missingBlob(String blobPath, RevCommit commit) {
JM 249         StringBuilder sb = new StringBuilder();
250         sb.append("<div class=\"alert alert-error\">");
251         String pattern = getString("gb.doesNotExistInTree").replace("{0}", "<b>{0}</b>").replace("{1}", "<b>{1}</b>");
252         sb.append(MessageFormat.format(pattern, blobPath, commit.getTree().getId().getName()));
253         sb.append("</div>");
254         return sb.toString();
255     }
e3733c 256
AL 257     private Map<?, String> initializeColors(BlameType blameType, List<AnnotatedLine> lines) {
258         ColorFactory colorFactory = new ColorFactory();
259         Map<?, String> colorMap;
260
261         if (BlameType.AGE == blameType) {
262             Set<Date> keys = new TreeSet<Date>(new Comparator<Date>() {
263                 @Override
264                 public int compare(Date o1, Date o2) {
265                     // younger code has a brighter, older code lightens to white
266                     return o1.compareTo(o2);
267                 }
268             });
269
270             for (AnnotatedLine line : lines) {
271                 keys.add(line.when);
272             }
273
274             // TODO consider making this a setting
275             colorMap = colorFactory.getGraduatedColorMap(keys, Color.decode("#FFA63A"));
276         } else {
277             Set<String> keys = new HashSet<String>();
278
279             for (AnnotatedLine line : lines) {
280                 if (blameType == BlameType.AUTHOR) {
281                     keys.add(line.author);
282                 } else {
283                     keys.add(line.commitId);
284                 }
285             }
286
287             colorMap = colorFactory.getRandomColorMap(keys);
288         }
289
290         return colorMap;
291     }
716745 292 }