--- /dev/null
+/*\r
+ * Copyright 2011 gitblit.com.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package com.gitblit.models;\r
+\r
+import java.io.Serializable;\r
+import java.util.Date;\r
+\r
+import org.eclipse.jgit.revwalk.RevCommit;\r
+\r
+public class AnnotatedLine implements Serializable {\r
+\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ public final String commitId;\r
+ public final String author;\r
+ public final Date when;\r
+ public final int lineNumber;\r
+ public final String data;\r
+\r
+ public AnnotatedLine(RevCommit commit, int lineNumber, String data) {\r
+ this.commitId = commit.getName();\r
+ this.author = commit.getAuthorIdent().getName();\r
+ this.when = commit.getAuthorIdent().getWhen();\r
+ this.lineNumber = lineNumber;\r
+ this.data = data;\r
+ }\r
+}
\ No newline at end of file
private static final long serialVersionUID = 1L;\r
\r
public String content;\r
- public RefModel notesRef; \r
+ public RefModel notesRef;\r
\r
public GitNote(RefModel notesRef, String text) {\r
this.notesRef = notesRef;\r
public int compareTo(RefModel o) {\r
return getDate().compareTo(o.getDate());\r
}\r
- \r
+\r
@Override\r
public String toString() {\r
return displayName;\r
package com.gitblit.utils;\r
\r
import java.io.ByteArrayOutputStream;\r
+import java.util.ArrayList;\r
import java.util.List;\r
\r
+import org.eclipse.jgit.api.BlameCommand;\r
+import org.eclipse.jgit.blame.BlameResult;\r
import org.eclipse.jgit.diff.DiffEntry;\r
import org.eclipse.jgit.diff.DiffFormatter;\r
+import org.eclipse.jgit.diff.RawText;\r
import org.eclipse.jgit.diff.RawTextComparator;\r
import org.eclipse.jgit.lib.Repository;\r
import org.eclipse.jgit.revwalk.RevCommit;\r
import org.slf4j.Logger;\r
import org.slf4j.LoggerFactory;\r
\r
+import com.gitblit.models.AnnotatedLine;\r
+\r
public class DiffUtils {\r
\r
private static final Logger LOGGER = LoggerFactory.getLogger(DiffUtils.class);\r
}\r
return null;\r
}\r
+\r
+ public static List<AnnotatedLine> blame(Repository r, String blobPath, String objectId) {\r
+ List<AnnotatedLine> lines = new ArrayList<AnnotatedLine>();\r
+ try {\r
+ BlameCommand blameCommand = new BlameCommand(r);\r
+ blameCommand.setFilePath(blobPath);\r
+ blameCommand.setStartCommit(r.resolve(objectId));\r
+ BlameResult blameResult = blameCommand.call();\r
+ RawText rawText = blameResult.getResultContents();\r
+ int length = rawText.size();\r
+ for (int i = 0; i < length; i++) {\r
+ RevCommit commit = blameResult.getSourceCommit(i);\r
+ AnnotatedLine line = new AnnotatedLine(commit, i + 1, rawText.getString(i));\r
+ lines.add(line);\r
+ }\r
+ } catch (Throwable t) {\r
+ LOGGER.error("failed to generate blame!", t);\r
+ }\r
+ return lines;\r
+ }\r
}\r
return r.toString().trim();\r
}\r
\r
- public static FetchResult cloneRepository(File repositoriesFolder, String name, String fromUrl) throws Exception {\r
+ public static FetchResult cloneRepository(File repositoriesFolder, String name, String fromUrl)\r
+ throws Exception {\r
FetchResult result = null;\r
if (!name.toLowerCase().endsWith(Constants.DOT_GIT_EXT)) {\r
name += Constants.DOT_GIT_EXT;\r
\r
private static final Logger LOGGER = LoggerFactory.getLogger(MetricUtils.class);\r
\r
- public static List<Metric> getDateMetrics(Repository r, String objectId, boolean includeTotal, String format) {\r
+ public static List<Metric> getDateMetrics(Repository r, String objectId, boolean includeTotal,\r
+ String format) {\r
Metric total = new Metric("TOTAL");\r
final Map<String, Metric> metricMap = new HashMap<String, Metric>();\r
if (StringUtils.isEmpty(objectId)) {\r
}\r
return "";\r
}\r
- \r
- public static String getRelativePath(String basePath, String fullPath) { \r
+\r
+ public static String getRelativePath(String basePath, String fullPath) {\r
String relativePath = fullPath.substring(basePath.length()).replace('\\', '/');\r
if (relativePath.charAt(0) == '/') {\r
relativePath = relativePath.substring(1);\r
public static final long ONEYEAR = ONEDAY * 365L;\r
\r
public static boolean isToday(Date date) {\r
- return (System.currentTimeMillis() - date.getTime()) < ONEDAY; \r
+ return (System.currentTimeMillis() - date.getTime()) < ONEDAY;\r
}\r
\r
public static boolean isYesterday(Date date) {\r
Calendar cal = Calendar.getInstance();\r
cal.setTime(date);\r
cal.add(Calendar.DATE, 1);\r
- return (System.currentTimeMillis() - cal.getTimeInMillis()) < ONEDAY; \r
+ return (System.currentTimeMillis() - cal.getTimeInMillis()) < ONEDAY;\r
}\r
\r
public static String duration(int days) {\r
mount("/search", SearchPage.class);\r
mount("/metrics", MetricsPage.class, "r");\r
mount("/blame", BlamePage.class, "r", "h", "f");\r
- \r
+\r
// setup ticket urls\r
mount("/tickets", TicketsPage.class, "r");\r
mount("/ticket", TicketPage.class, "r", "h", "f");\r
WicketUtils.setHtmlTooltip(label, title);\r
return label;\r
}\r
- \r
+\r
public static IChartData getChartData(Collection<Metric> metrics) {\r
final double[] commits = new double[metrics.size()];\r
final double[] tags = new double[metrics.size()];\r
}\r
return max;\r
}\r
- \r
+\r
public static IChartData getScatterData(Collection<Metric> metrics) {\r
final double[] y = new double[metrics.size()];\r
final double[] x = new double[metrics.size()];\r
<div wicket:id="breadcrumbs">[breadcrumbs]</div>\r
\r
<!-- blame content -->\r
- <table>\r
+ <table class="annotated" style="border-top: 0px; margin-bottom:5px;">\r
<tbody>\r
<tr>\r
<th>Commit</th>\r
<th>Data</th>\r
</tr>\r
<tr wicket:id="annotation">\r
- <td><span wicket:id="commit"></span></td>\r
- <td><span wicket:id="line"></span></td>\r
- <td><span wicket:id="data"></span></td>\r
+ <td><span class="sha1" wicket:id="commit"></span></td>\r
+ <td><span class="sha1" wicket:id="line"></span></td>\r
+ <td><span class="sha1" wicket:id="data"></span></td>\r
</tr>\r
</tbody>\r
</table>\r
*/\r
package com.gitblit.wicket.pages;\r
\r
-import java.io.Serializable;\r
-import java.util.Arrays;\r
+import java.text.DateFormat;\r
+import java.text.MessageFormat;\r
+import java.text.SimpleDateFormat;\r
import java.util.List;\r
\r
import org.apache.wicket.PageParameters;\r
import org.eclipse.jgit.lib.Constants;\r
import org.eclipse.jgit.revwalk.RevCommit;\r
\r
+import com.gitblit.GitBlit;\r
+import com.gitblit.Keys;\r
+import com.gitblit.models.AnnotatedLine;\r
+import com.gitblit.utils.DiffUtils;\r
+import com.gitblit.utils.StringUtils;\r
import com.gitblit.wicket.WicketUtils;\r
import com.gitblit.wicket.panels.CommitHeaderPanel;\r
import com.gitblit.wicket.panels.LinkPanel;\r
RevCommit commit = getCommit();\r
\r
add(new BookmarkablePageLink<Void>("blobLink", BlobPage.class,\r
- WicketUtils.newPathParameter(repositoryName, objectId,\r
- blobPath)));\r
+ WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));\r
add(new BookmarkablePageLink<Void>("commitLink", CommitPage.class,\r
WicketUtils.newObjectParameter(repositoryName, objectId)));\r
add(new BookmarkablePageLink<Void>("commitDiffLink", CommitDiffPage.class,\r
\r
add(new PathBreadcrumbsPanel("breadcrumbs", repositoryName, blobPath, objectId));\r
\r
- List<BlameLine> blame = Arrays.asList(new BlameLine("HEAD", "1", "Under Construction"));\r
- ListDataProvider<BlameLine> blameDp = new ListDataProvider<BlameLine>(blame);\r
- DataView<BlameLine> blameView = new DataView<BlameLine>("annotation", blameDp) {\r
+ String format = GitBlit.getString(Keys.web.datetimestampLongFormat,\r
+ "EEEE, MMMM d, yyyy h:mm a z");\r
+ final DateFormat df = new SimpleDateFormat(format);\r
+ df.setTimeZone(getTimeZone());\r
+ List<AnnotatedLine> lines = DiffUtils.blame(getRepository(), blobPath, objectId);\r
+ ListDataProvider<AnnotatedLine> blameDp = new ListDataProvider<AnnotatedLine>(lines);\r
+ DataView<AnnotatedLine> blameView = new DataView<AnnotatedLine>("annotation", blameDp) {\r
private static final long serialVersionUID = 1L;\r
+ private int count;\r
+ private String lastCommitId = "";\r
+ private boolean showInitials = true;\r
\r
- public void populateItem(final Item<BlameLine> item) {\r
- BlameLine entry = item.getModelObject();\r
- item.add(new LinkPanel("commit", "list", entry.objectId, CommitPage.class,\r
- newCommitParameter(entry.objectId)));\r
- item.add(new Label("line", entry.line));\r
- item.add(new Label("data", entry.data));\r
+ public void populateItem(final Item<AnnotatedLine> item) {\r
+ AnnotatedLine entry = item.getModelObject();\r
+ item.add(new Label("line", "" + entry.lineNumber));\r
+ item.add(new Label("data", StringUtils.escapeForHtml(entry.data, true))\r
+ .setEscapeModelStrings(false));\r
+ if (!lastCommitId.equals(entry.commitId)) {\r
+ lastCommitId = entry.commitId;\r
+ count++;\r
+ // show the link for first line\r
+ LinkPanel commitLink = new LinkPanel("commit", null,\r
+ getShortObjectId(entry.commitId), CommitPage.class,\r
+ newCommitParameter(entry.commitId));\r
+ WicketUtils.setHtmlTooltip(commitLink,\r
+ MessageFormat.format("{0}, {1}", entry.author, df.format(entry.when)));\r
+ item.add(commitLink);\r
+ showInitials = true;\r
+ } else {\r
+ if (showInitials) {\r
+ showInitials = false;\r
+ // show author initials\r
+ item.add(new Label("commit", getInitials(entry.author)));\r
+ } else {\r
+ // hide the commit link until the next block\r
+ item.add(new Label("commit").setVisible(false));\r
+ }\r
+ }\r
+ if (count % 2 == 0) {\r
+ WicketUtils.setCssClass(item, "even");\r
+ } else {\r
+ WicketUtils.setCssClass(item, "odd");\r
+ }\r
}\r
};\r
add(blameView);\r
}\r
\r
+ private String getInitials(String author) {\r
+ StringBuilder sb = new StringBuilder();\r
+ String[] chunks = author.split(" ");\r
+ for (String chunk : chunks) {\r
+ sb.append(chunk.charAt(0));\r
+ }\r
+ return sb.toString().toUpperCase();\r
+ }\r
+\r
@Override\r
protected String getPageName() {\r
return getString("gb.blame");\r
}\r
- \r
- private class BlameLine implements Serializable {\r
- \r
- private static final long serialVersionUID = 1L;\r
- \r
- final String objectId;\r
- final String line;\r
- final String data;\r
- BlameLine(String objectId, String line, String data) {\r
- this.objectId = objectId;\r
- this.line = line;\r
- this.data = data;\r
- }\r
- }\r
}\r
// blob by objectid\r
\r
add(new BookmarkablePageLink<Void>("blameLink", BlamePage.class,\r
- WicketUtils.newPathParameter(repositoryName, objectId, blobPath)).setEnabled(false));\r
+ WicketUtils.newPathParameter(repositoryName, objectId, blobPath))\r
+ .setEnabled(false));\r
add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class).setEnabled(false));\r
add(new BookmarkablePageLink<Void>("rawLink", RawPage.class,\r
WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));\r
item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class,\r
newPathParameter(entry.path)));\r
item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class,\r
- newPathParameter(entry.path)).setEnabled(!entry.changeType.equals(ChangeType.ADD)));\r
+ newPathParameter(entry.path)).setEnabled(!entry.changeType\r
+ .equals(ChangeType.ADD)));\r
\r
WicketUtils.setAlternatingBackground(item, counter);\r
counter++;\r
add(new Label("parentLink", "none"));\r
add(new Label("commitdiffLink", getString("gb.commitdiff")));\r
} else {\r
- add(new LinkPanel("parentLink", null, parents.get(0).substring(0, 8), CommitPage.class,\r
- newCommitParameter(parents.get(0))));\r
+ add(new LinkPanel("parentLink", null, getShortObjectId(parents.get(0)),\r
+ CommitPage.class, newCommitParameter(parents.get(0))));\r
add(new LinkPanel("commitdiffLink", null, new StringResourceModel("gb.commitdiff",\r
this, null), CommitDiffPage.class, WicketUtils.newObjectParameter(\r
repositoryName, objectId)));\r
repositoryModel.name = repositoryModel.name.replace("//", "/");\r
\r
// prohibit folder paths\r
- if (repositoryModel.name.startsWith("/")) { \r
+ if (repositoryModel.name.startsWith("/")) {\r
error("Leading root folder references (/) are prohibited.");\r
return;\r
}\r
- if (repositoryModel.name.startsWith("../")) { \r
+ if (repositoryModel.name.startsWith("../")) {\r
error("Relative folder references (../) are prohibited.");\r
return;\r
}\r
}\r
}\r
}\r
- \r
+\r
// confirm access restriction selection\r
if (repositoryModel.accessRestriction == null) {\r
error("Please select access restriction!");\r
public class MetricsPage extends RepositoryPage {\r
\r
public MetricsPage(PageParameters params) {\r
- super(params); \r
+ super(params);\r
Repository r = getRepository();\r
add(new Label("branchTitle", objectId));\r
Metric metricsTotal = null;\r
List<Metric> metrics = MetricUtils.getDateMetrics(r, objectId, true, null);\r
metricsTotal = metrics.remove(0);\r
if (metricsTotal == null) {\r
- add(new Label("branchStats", "")); \r
+ add(new Label("branchStats", ""));\r
} else {\r
- add(new Label("branchStats", MessageFormat.format(\r
- "{0} commits and {1} tags in {2}", metricsTotal.count, metricsTotal.tag,\r
- TimeUtils.duration(metricsTotal.duration))));\r
+ add(new Label("branchStats",\r
+ MessageFormat.format("{0} commits and {1} tags in {2}", metricsTotal.count,\r
+ metricsTotal.tag, TimeUtils.duration(metricsTotal.duration))));\r
}\r
insertLinePlot("commitsChart", metrics);\r
insertBarPlot("dayOfWeekChart", getDayOfWeekMetrics(r, objectId));\r
return commit;\r
}\r
\r
+ protected String getShortObjectId(String objectId) {\r
+ return objectId.substring(0, 8);\r
+ }\r
+\r
protected void addRefs(Repository r, RevCommit c) {\r
add(new RefsPanel("refsPanel", repositoryName, c, JGitUtils.getAllRefs(r)));\r
}\r
add(WicketUtils.createTimestampLabel("repositoryLastChange", JGitUtils.getLastChange(r),\r
getTimeZone()));\r
if (metricsTotal == null) {\r
- add(new Label("branchStats", "")); \r
+ add(new Label("branchStats", ""));\r
} else {\r
- add(new Label("branchStats", MessageFormat.format(\r
- "{0} commits and {1} tags in {2}", metricsTotal.count, metricsTotal.tag,\r
- TimeUtils.duration(metricsTotal.duration))));\r
+ add(new Label("branchStats",\r
+ MessageFormat.format("{0} commits and {1} tags in {2}", metricsTotal.count,\r
+ metricsTotal.tag, TimeUtils.duration(metricsTotal.duration))));\r
}\r
- add(new BookmarkablePageLink<Void>("metrics", MetricsPage.class, WicketUtils.newRepositoryParameter(repositoryName)));\r
+ add(new BookmarkablePageLink<Void>("metrics", MetricsPage.class,\r
+ WicketUtils.newRepositoryParameter(repositoryName)));\r
\r
List<String> repositoryUrls = new ArrayList<String>();\r
\r
metrics.get(metrics.size() / 2).name, metrics.get(metrics.size() - 1).name });\r
provider.addAxis(dateAxis);\r
\r
- ChartAxis commitAxis = new ChartAxis(ChartAxisType.LEFT); \r
+ ChartAxis commitAxis = new ChartAxis(ChartAxisType.LEFT);\r
commitAxis.setLabels(new String[] { "",\r
String.valueOf((int) WicketUtils.maxValue(metrics)) });\r
provider.addAxis(commitAxis);\r
add(new Label("author"));\r
add(new Label("date"));\r
}\r
- \r
+\r
public CommitHeaderPanel(String id, String repositoryName, RevCommit c) {\r
super(id);\r
add(new LinkPanel("shortmessage", "title", c.getShortMessage(), CommitPage.class,\r
};\r
add(legendsView);\r
}\r
- \r
+\r
protected Map<ChangeType, AtomicInteger> getChangedPathsStats(List<PathChangeModel> paths) {\r
Map<ChangeType, AtomicInteger> stats = new HashMap<ChangeType, AtomicInteger>();\r
for (PathChangeModel path : paths) {\r
item.add(new BookmarkablePageLink<Void>("view", CommitPage.class, WicketUtils\r
.newObjectParameter(repositoryName, entry.getName())));\r
item.add(new BookmarkablePageLink<Void>("diff", CommitDiffPage.class, WicketUtils\r
- .newObjectParameter(repositoryName, entry.getName())).setEnabled(entry.getParentCount() > 0));\r
+ .newObjectParameter(repositoryName, entry.getName())).setEnabled(entry\r
+ .getParentCount() > 0));\r
item.add(new BookmarkablePageLink<Void>("tree", TreePage.class, WicketUtils\r
.newObjectParameter(repositoryName, entry.getName())));\r
\r
public class TagsPanel extends BasePanel {\r
\r
private static final long serialVersionUID = 1L;\r
- \r
+\r
private final boolean hasTags;\r
\r
public TagsPanel(String wicketId, final String repositoryName, Repository r, final int maxCount) {\r
add(new LinkPanel("allTags", "link", new StringResourceModel("gb.allTags", this, null),\r
TagsPage.class, WicketUtils.newRepositoryParameter(repositoryName)));\r
}\r
- \r
+\r
hasTags = tags.size() > 0;\r
}\r
- \r
+\r
public TagsPanel hideIfEmpty() {\r
setVisible(hasTags);\r
return this;\r
border-left: 1px solid #ccc;\r
}\r
\r
+table.annotated {\r
+ width: 100%;\r
+ border: 1px solid #bbb;\r
+}\r
+\r
+table.annotated tr.even {\r
+ background-color: white;\r
+}\r
+\r
+table.annotated tr.odd {\r
+ background-color: #fdfbdf;\r
+}\r
+\r
tr th a { padding-right: 15px; background-position: right; background-repeat:no-repeat; }\r
tr th.wicket_orderDown a {background-image: url(arrow_down.png); }\r
tr th.wicket_orderUp a { background-image: url(arrow_up.png); }\r
\r
public void testCreateRepository() throws Exception {\r
String[] repositories = { "NewTestRepository.git", "NewTestRepository" };\r
- for (String repositoryName : repositories) { \r
+ for (String repositoryName : repositories) {\r
Repository repository = JGitUtils.createRepository(GitBlitSuite.REPOSITORIES,\r
repositoryName);\r
- File folder = FileKey.resolve(new File(GitBlitSuite.REPOSITORIES, repositoryName), FS.DETECTED);\r
+ File folder = FileKey.resolve(new File(GitBlitSuite.REPOSITORIES, repositoryName),\r
+ FS.DETECTED);\r
assertTrue(repository != null);\r
assertFalse(JGitUtils.hasCommits(repository));\r
assertTrue(JGitUtils.getFirstCommit(repository, null) == null);\r
List<RefModel> list = entry.getValue();\r
for (RefModel ref : list) {\r
if (ref.displayName.equals("refs/tags/spearce-gpg-pub")) {\r
- assertTrue(ref.getObjectId().getName().equals("8bbde7aacf771a9afb6992434f1ae413e010c6d8"));\r
+ assertTrue(ref.getObjectId().getName()\r
+ .equals("8bbde7aacf771a9afb6992434f1ae413e010c6d8"));\r
assertTrue(ref.getAuthorIdent().getEmailAddress().equals("spearce@spearce.org"));\r
assertTrue(ref.getShortMessage().startsWith("GPG key"));\r
- assertTrue(ref.getFullMessage().startsWith("GPG key")); \r
+ assertTrue(ref.getFullMessage().startsWith("GPG key"));\r
assertTrue(ref.getReferencedObjectType() == Constants.OBJ_BLOB);\r
} else if (ref.displayName.equals("refs/tags/v0.12.1")) {\r
assertTrue(ref.isAnnotatedTag());\r
+ model.getName().hashCode());\r
}\r
repository.close();\r
- \r
+\r
repository = GitBlitSuite.getBluezGnomeRepository();\r
for (RefModel model : JGitUtils.getTags(repository, true, -1)) {\r
if (model.getObjectId().getName().equals("728643ec0c438c77e182898c2f2967dbfdc231c8")) {\r
assertFalse(model.isAnnotatedTag());\r
assertTrue(model.getAuthorIdent().getEmailAddress().equals("marcel@holtmann.org"));\r
- assertTrue(model.getFullMessage().equals("Update changelog and bump version number\n"));\r
+ assertTrue(model.getFullMessage().equals(\r
+ "Update changelog and bump version number\n"));\r
}\r
}\r
repository.close();\r
repository.close();\r
assertTrue("No date metrics found!", metrics.size() > 0);\r
}\r
- \r
+\r
public void testAuthorMetrics() throws Exception {\r
Repository repository = GitBlitSuite.getHelloworldRepository();\r
List<Metric> byEmail = MetricUtils.getAuthorMetrics(repository, null, true);\r
\r
public void testEscapeForHtml() throws Exception {\r
String input = "& < > \" \t";\r
- String output_nochange = "& < > " \t";\r
- String output_change = "& < > " ";\r
- assertTrue(StringUtils.escapeForHtml(input, false).equals(output_nochange));\r
- assertTrue(StringUtils.escapeForHtml(input, true).equals(output_change));\r
+ String outputNoChange = "& < > " \t";\r
+ String outputChange = "& < > " ";\r
+ assertTrue(StringUtils.escapeForHtml(input, false).equals(outputNoChange));\r
+ assertTrue(StringUtils.escapeForHtml(input, true).equals(outputChange));\r
}\r
\r
public void testFlattenStrings() throws Exception {\r
RefModel branch = TicgitUtils.getTicketsBranch(repository);\r
repository.close();\r
assertTrue("Ticgit branch does not exist!", branch != null);\r
- \r
+\r
repository = GitBlitSuite.getHelloworldRepository();\r
branch = TicgitUtils.getTicketsBranch(repository);\r
repository.close();\r
assertTrue(commentA.hashCode() == commentA.text.hashCode());\r
}\r
}\r
- \r
+\r
repository = GitBlitSuite.getHelloworldRepository();\r
List<TicketModel> ticketsC = TicgitUtils.getTickets(repository);\r
repository.close();\r