From: James Moger Date: Thu, 17 Nov 2011 03:43:37 +0000 (-0500) Subject: Working draft of the aggregate activity page X-Git-Tag: v0.8.0~93 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=6e6f9fe4d0c42a40ba7981606fb526391be23f6d;p=gitblit.git Working draft of the aggregate activity page --- diff --git a/distrib/gitblit.properties b/distrib/gitblit.properties index 083a658e..5c50a2ce 100644 --- a/distrib/gitblit.properties +++ b/distrib/gitblit.properties @@ -161,12 +161,23 @@ web.repositoriesMessage = gitblit # RESTART REQUIRED web.useClientTimezone = false +# Time format +# +# +# SINCE 0.8.0 +web.timeFormat = HH:mm + # Short date format # # # SINCE 0.5.0 web.datestampShortFormat = yyyy-MM-dd +# Long date format +# +# SINCE 0.8.0 +web.datestampLongFormat = EEEE, MMMM d, yyyy + # Long timestamp format # # @@ -239,6 +250,12 @@ web.showSearchTypeSelection = false # SINCE 0.5.0 web.generateActivityGraph = true +# The number of days to show on the activity page. +# Value must exceed 0 else default of 14 is used +# +# SINCE 0.8.0 +web.activityDuration = 14 + # The number of commits to display on the summary page # Value must exceed 0 else default of 20 is used # diff --git a/docs/04_releases.mkd b/docs/04_releases.mkd index 5719b191..22ecce78 100644 --- a/docs/04_releases.mkd +++ b/docs/04_releases.mkd @@ -5,6 +5,11 @@ - added: optional Gravatar integration **New:** *web.allowGravatar = true* +- added: multi-repository activity page. this is a timeline of commit activity over the last N days for one or more repositories. + **New:** *web.activityDuration = 14* + **New:** *web.timeFormat = HH:mm* + **New:** *web.datestampLongFormat = EEEE, MMMM d, yyyy* + ### Older Releases diff --git a/src/com/gitblit/models/DailyActivity.java b/src/com/gitblit/models/DailyActivity.java new file mode 100644 index 00000000..f2e816cb --- /dev/null +++ b/src/com/gitblit/models/DailyActivity.java @@ -0,0 +1,47 @@ +/* + * Copyright 2011 gitblit.com. + * + * 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.gitblit.models; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * Model class to represent the commit activity across many repositories. This + * class is used by the Activity page. + * + * @author James Moger + */ +public class DailyActivity implements Serializable, Comparable { + + private static final long serialVersionUID = 1L; + + public final Date date; + + public final List commits; + + public DailyActivity(Date date) { + this.date = date; + commits = new ArrayList(); + } + + @Override + public int compareTo(DailyActivity o) { + // reverse chronological order + return o.date.compareTo(date); + } +} diff --git a/src/com/gitblit/models/RepositoryCommit.java b/src/com/gitblit/models/RepositoryCommit.java new file mode 100644 index 00000000..8f5b2bd0 --- /dev/null +++ b/src/com/gitblit/models/RepositoryCommit.java @@ -0,0 +1,86 @@ +/* + * Copyright 2011 gitblit.com. + * + * 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.gitblit.models; + +import java.io.Serializable; +import java.util.List; + +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.revwalk.RevCommit; + +/** + * Model class to represent a RevCommit, it's source repository, and the branch. + * This class is used by the activity page. + * + * @author James Moger + */ +public class RepositoryCommit implements Serializable, Comparable { + + private static final long serialVersionUID = 1L; + + public final String repository; + + public final String branch; + + private final RevCommit commit; + + private List refs; + + public RepositoryCommit(String repository, String branch, RevCommit commit) { + this.repository = repository; + this.branch = branch; + this.commit = commit; + } + + public void setRefs(List refs) { + this.refs = refs; + } + + public List getRefs() { + return refs; + } + + public String getName() { + return commit.getName(); + } + + public String getShortName() { + return commit.getName().substring(0, 8); + } + + public String getShortMessage() { + return commit.getShortMessage(); + } + + public int getParentCount() { + return commit.getParentCount(); + } + + public PersonIdent getAuthorIdent() { + return commit.getAuthorIdent(); + } + + @Override + public int compareTo(RepositoryCommit o) { + // reverse-chronological order + if (commit.getCommitTime() > o.commit.getCommitTime()) { + return -1; + } else if (commit.getCommitTime() < o.commit.getCommitTime()) { + return 1; + } + return 0; + } +} \ No newline at end of file diff --git a/src/com/gitblit/wicket/GitBlitWebApp.java b/src/com/gitblit/wicket/GitBlitWebApp.java index 301adb32..79083efb 100644 --- a/src/com/gitblit/wicket/GitBlitWebApp.java +++ b/src/com/gitblit/wicket/GitBlitWebApp.java @@ -25,6 +25,7 @@ import org.apache.wicket.protocol.http.WebApplication; import com.gitblit.GitBlit; import com.gitblit.Keys; +import com.gitblit.wicket.pages.ActivityPage; import com.gitblit.wicket.pages.BlamePage; import com.gitblit.wicket.pages.BlobDiffPage; import com.gitblit.wicket.pages.BlobPage; @@ -103,6 +104,8 @@ public class GitBlitWebApp extends WebApplication { // federation urls mount("/proposal", ReviewProposalPage.class, "t"); mount("/registration", FederationRegistrationPage.class, "u", "n"); + + mount("/activity", ActivityPage.class, "r", "h"); } private void mount(String location, Class clazz, String... parameters) { diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties index 51938433..f157a7e5 100644 --- a/src/com/gitblit/wicket/GitBlitWebApp.properties +++ b/src/com/gitblit/wicket/GitBlitWebApp.properties @@ -180,4 +180,10 @@ gb.date = date gb.activity = activity gb.subscribe = subscribe gb.branch = branch -gb.maxHits = max hits \ No newline at end of file +gb.maxHits = max hits +gb.recentActivity = recent activity +gb.recentActivitySubheader = last {0} days / {1} commits by {2} authors +gb.dailyActivity = daily activity +gb.activeRepositories = active repositories +gb.activeAuthors = active authors +gb.commits = commits \ No newline at end of file diff --git a/src/com/gitblit/wicket/WicketUtils.java b/src/com/gitblit/wicket/WicketUtils.java index 37b44472..6157a44f 100644 --- a/src/com/gitblit/wicket/WicketUtils.java +++ b/src/com/gitblit/wicket/WicketUtils.java @@ -358,6 +358,14 @@ public class WicketUtils { return params.getInt("pg", 1); } + public static String getSet(PageParameters params) { + return params.getString("set", ""); + } + + public static int getDaysBack(PageParameters params) { + return params.getInt("db", 14); + } + public static String getUsername(PageParameters params) { return params.getString("user", ""); } @@ -403,6 +411,57 @@ public class WicketUtils { } return label; } + + public static Label createTimeLabel(String wicketId, Date date, TimeZone timeZone) { + String format = GitBlit.getString(Keys.web.timeFormat, "HH:mm"); + DateFormat df = new SimpleDateFormat(format); + if (timeZone != null) { + df.setTimeZone(timeZone); + } + String timeString; + if (date.getTime() == 0) { + timeString = "--"; + } else { + timeString = df.format(date); + } + String title = TimeUtils.timeAgo(date); + Label label = new Label(wicketId, timeString); + WicketUtils.setCssClass(label, TimeUtils.timeAgoCss(date)); + if (!StringUtils.isEmpty(title)) { + WicketUtils.setHtmlTooltip(label, title); + } + return label; + } + + public static Label createDatestampLabel(String wicketId, Date date, TimeZone timeZone) { + String format = GitBlit.getString(Keys.web.datestampLongFormat, "EEEE, MMMM d, yyyy"); + DateFormat df = new SimpleDateFormat(format); + if (timeZone != null) { + df.setTimeZone(timeZone); + } + String dateString; + if (date.getTime() == 0) { + dateString = "--"; + } else { + dateString = df.format(date); + } + String title = null; + if (date.getTime() <= System.currentTimeMillis()) { + // past + title = TimeUtils.timeAgo(date); + } + if ((System.currentTimeMillis() - date.getTime()) < 10 * 24 * 60 * 60 * 1000L) { + String tmp = dateString; + dateString = title; + title = tmp; + } + Label label = new Label(wicketId, dateString); + WicketUtils.setCssClass(label, TimeUtils.timeAgoCss(date)); + if (!StringUtils.isEmpty(title)) { + WicketUtils.setHtmlTooltip(label, title); + } + return label; + } public static Label createTimestampLabel(String wicketId, Date date, TimeZone timeZone) { String format = GitBlit.getString(Keys.web.datetimestampLongFormat, diff --git a/src/com/gitblit/wicket/pages/ActivityPage.html b/src/com/gitblit/wicket/pages/ActivityPage.html new file mode 100644 index 00000000..c86028f2 --- /dev/null +++ b/src/com/gitblit/wicket/pages/ActivityPage.html @@ -0,0 +1,19 @@ + + + + + +
+ + + +
+
[activity panel]
+
+ + \ No newline at end of file diff --git a/src/com/gitblit/wicket/pages/ActivityPage.java b/src/com/gitblit/wicket/pages/ActivityPage.java new file mode 100644 index 00000000..53c7f41c --- /dev/null +++ b/src/com/gitblit/wicket/pages/ActivityPage.java @@ -0,0 +1,260 @@ +/* + * Copyright 2011 gitblit.com. + * + * 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.gitblit.wicket.pages; + +import java.text.DateFormat; +import java.text.MessageFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.wicket.PageParameters; +import org.apache.wicket.behavior.HeaderContributor; +import org.apache.wicket.markup.html.basic.Label; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; + +import com.gitblit.GitBlit; +import com.gitblit.models.DailyActivity; +import com.gitblit.models.Metric; +import com.gitblit.models.RefModel; +import com.gitblit.models.RepositoryCommit; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.UserModel; +import com.gitblit.utils.JGitUtils; +import com.gitblit.utils.StringUtils; +import com.gitblit.utils.TimeUtils; +import com.gitblit.wicket.GitBlitWebSession; +import com.gitblit.wicket.WicketUtils; +import com.gitblit.wicket.charting.GoogleChart; +import com.gitblit.wicket.charting.GoogleCharts; +import com.gitblit.wicket.charting.GoogleLineChart; +import com.gitblit.wicket.charting.GooglePieChart; +import com.gitblit.wicket.panels.ActivityPanel; + +/** + * Activity Page shows a list of recent commits across all visible Gitblit + * repositories. + * + * @author James Moger + * + */ +public class ActivityPage extends RootPage { + + public ActivityPage(PageParameters params) { + super(); + setupPage("", ""); + final UserModel user = GitBlitWebSession.get().getUser(); + + // parameters + int daysBack = WicketUtils.getDaysBack(params); + if (daysBack < 1) { + daysBack = 14; + } + String set = WicketUtils.getSet(params); + String repositoryName = WicketUtils.getRepositoryName(params); + String objectId = WicketUtils.getObject(params); + + List models = null; + if (!StringUtils.isEmpty(repositoryName)) { + // named repository + models = new ArrayList(); + RepositoryModel model = GitBlit.self().getRepositoryModel(repositoryName); + if (user.canAccessRepository(model)) { + models.add(model); + } + } + + // get all user accessible repositories + if (models == null) { + models = GitBlit.self().getRepositoryModels(user); + } + + // filter the repositories by the specified set + if (!StringUtils.isEmpty(set)) { + List sets = StringUtils.getStringsFromValue(set, ","); + List setModels = new ArrayList(); + for (RepositoryModel model : models) { + for (String curr : sets) { + if (model.federationSets.contains(curr)) { + setModels.add(model); + } + } + } + models = setModels; + } + + // Activity panel shows last daysBack of activity across all + // repositories. + Date thresholdDate = new Date(System.currentTimeMillis() - daysBack * TimeUtils.ONEDAY); + + // Build a map of DailyActivity from the available repositories for the + // specified threshold date. + DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); + Calendar cal = Calendar.getInstance(); + + Map activity = new HashMap(); + for (RepositoryModel model : models) { + if (model.hasCommits && model.lastChange.after(thresholdDate)) { + Repository repository = GitBlit.self().getRepository(model.name); + List commits = JGitUtils.getRevLog(repository, objectId, thresholdDate); + Map> allRefs = JGitUtils.getAllRefs(repository); + repository.close(); + + // determine commit branch + String branch = objectId; + if (StringUtils.isEmpty(branch)) { + List headRefs = allRefs.get(commits.get(0).getId()); + List localBranches = new ArrayList(); + for (RefModel ref : headRefs) { + if (ref.getName().startsWith(Constants.R_HEADS)) { + localBranches.add(ref.getName().substring(Constants.R_HEADS.length())); + } + } + // determine branch + if (localBranches.size() == 1) { + // only one branch, choose it + branch = localBranches.get(0); + } else if (localBranches.size() > 1) { + if (localBranches.contains("master")) { + // choose master + branch = "master"; + } else { + // choose first branch + branch = localBranches.get(0); + } + } + } + + for (RevCommit commit : commits) { + Date date = JGitUtils.getCommitDate(commit); + String dateStr = df.format(date); + if (!activity.containsKey(dateStr)) { + // Normalize the date to midnight + cal.setTime(date); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + activity.put(dateStr, new DailyActivity(cal.getTime())); + } + RepositoryCommit commitModel = new RepositoryCommit(model.name, branch, commit); + commitModel.setRefs(allRefs.get(commit.getId())); + activity.get(dateStr).commits.add(commitModel); + } + } + } + + // activity metrics + Map dayMetrics = new HashMap(); + Map repositoryMetrics = new HashMap(); + Map authorMetrics = new HashMap(); + + // prepare day metrics + cal.setTimeInMillis(System.currentTimeMillis()); + for (int i = 0; i < daysBack; i++) { + cal.add(Calendar.DATE, -1); + String key = df.format(cal.getTime()); + dayMetrics.put(key, new Metric(key)); + } + + // calculate activity metrics + for (Map.Entry entry : activity.entrySet()) { + // day metrics + Metric day = dayMetrics.get(entry.getKey()); + day.count = entry.getValue().commits.size(); + + for (RepositoryCommit commit : entry.getValue().commits) { + // repository metrics + String repository = commit.repository; + if (!repositoryMetrics.containsKey(repository)) { + repositoryMetrics.put(repository, new Metric(repository)); + } + repositoryMetrics.get(repository).count++; + + // author metrics + String author = commit.getAuthorIdent().getEmailAddress().toLowerCase(); + if (!authorMetrics.containsKey(author)) { + authorMetrics.put(author, new Metric(author)); + } + authorMetrics.get(author).count++; + } + } + + // sort the activity groups and their commit contents + int totalCommits = 0; + List recentActivity = new ArrayList(activity.values()); + for (DailyActivity daily : recentActivity) { + Collections.sort(daily.commits); + totalCommits += daily.commits.size(); + } + + // build google charts + int w = 310; + int h = 150; + GoogleCharts charts = new GoogleCharts(); + + // sort in reverse-chronological order and then reverse that + Collections.sort(recentActivity); + Collections.reverse(recentActivity); + + // daily line chart + GoogleChart chart = new GoogleLineChart("chartDaily", getString("gb.dailyActivity"), "day", + getString("gb.commits")); + df = new SimpleDateFormat("MMM dd"); + for (DailyActivity metric : recentActivity) { + chart.addValue(df.format(metric.date), metric.commits.size()); + } + chart.setWidth(w); + chart.setHeight(h); + charts.addChart(chart); + + // active repositories pie chart + chart = new GooglePieChart("chartRepositories", getString("gb.activeRepositories"), + getString("gb.repository"), getString("gb.commits")); + for (Metric metric : repositoryMetrics.values()) { + chart.addValue(metric.name, metric.count); + } + chart.setWidth(w); + chart.setHeight(h); + charts.addChart(chart); + + // active authors pie chart + chart = new GooglePieChart("chartAuthors", getString("gb.activeAuthors"), + getString("gb.author"), getString("gb.commits")); + for (Metric metric : authorMetrics.values()) { + chart.addValue(metric.name, metric.count); + } + chart.setWidth(w); + chart.setHeight(h); + charts.addChart(chart); + + add(new HeaderContributor(charts)); + + add(new Label("subheader", MessageFormat.format(getString("gb.recentActivitySubheader"), + daysBack, totalCommits, authorMetrics.size()))); + + // add activity panel + add(new ActivityPanel("activityPanel", recentActivity)); + } +} diff --git a/src/com/gitblit/wicket/pages/RootPage.java b/src/com/gitblit/wicket/pages/RootPage.java index f00c0411..06ab2985 100644 --- a/src/com/gitblit/wicket/pages/RootPage.java +++ b/src/com/gitblit/wicket/pages/RootPage.java @@ -82,6 +82,7 @@ public abstract class RootPage extends BasePage { // navigation links List pages = new ArrayList(); pages.add(new PageRegistration("gb.repositories", RepositoriesPage.class)); + pages.add(new PageRegistration("gb.activity", ActivityPage.class)); if (showAdmin) { pages.add(new PageRegistration("gb.users", UsersPage.class)); } diff --git a/src/com/gitblit/wicket/panels/ActivityPanel.html b/src/com/gitblit/wicket/panels/ActivityPanel.html new file mode 100644 index 00000000..703dc009 --- /dev/null +++ b/src/com/gitblit/wicket/panels/ActivityPanel.html @@ -0,0 +1,38 @@ + + + + + + +
+
[title]
+ + +
+
+ + + [time of day] + + + + [shortlog commit link]
+ [author link] committed [commit id] to + + +
[commit refs]
+ [repository link] + + + + | | + + +
+ +
+ + \ No newline at end of file diff --git a/src/com/gitblit/wicket/panels/ActivityPanel.java b/src/com/gitblit/wicket/panels/ActivityPanel.java new file mode 100644 index 00000000..128ef2bd --- /dev/null +++ b/src/com/gitblit/wicket/panels/ActivityPanel.java @@ -0,0 +1,142 @@ +/* + * Copyright 2011 gitblit.com. + * + * 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.gitblit.wicket.panels; + +import java.util.Collections; +import java.util.List; + +import org.apache.wicket.markup.html.link.BookmarkablePageLink; +import org.apache.wicket.markup.html.panel.Fragment; +import org.apache.wicket.markup.repeater.Item; +import org.apache.wicket.markup.repeater.data.DataView; +import org.apache.wicket.markup.repeater.data.ListDataProvider; + +import com.gitblit.Constants; +import com.gitblit.models.DailyActivity; +import com.gitblit.models.RepositoryCommit; +import com.gitblit.wicket.GitBlitWebSession; +import com.gitblit.wicket.GravatarImage; +import com.gitblit.wicket.WicketUtils; +import com.gitblit.wicket.pages.CommitDiffPage; +import com.gitblit.wicket.pages.CommitPage; +import com.gitblit.wicket.pages.LogPage; +import com.gitblit.wicket.pages.SearchPage; +import com.gitblit.wicket.pages.SummaryPage; +import com.gitblit.wicket.pages.TreePage; + +/** + * Renders activity in day-blocks in reverse-chronological order. + * + * @author James Moger + * + */ +public class ActivityPanel extends BasePanel { + + private static final long serialVersionUID = 1L; + + public ActivityPanel(String wicketId, List recentActivity) { + super(wicketId); + + Collections.sort(recentActivity); + + DataView activityView = new DataView("activity", + new ListDataProvider(recentActivity)) { + private static final long serialVersionUID = 1L; + + public void populateItem(final Item item) { + final DailyActivity entry = item.getModelObject(); + item.add(WicketUtils.createDatestampLabel("title", entry.date, GitBlitWebSession + .get().getTimezone())); + + // display the commits in chronological order + DataView commits = new DataView("commits", + new ListDataProvider(entry.commits)) { + private static final long serialVersionUID = 1L; + + public void populateItem(final Item item) { + final RepositoryCommit commit = item.getModelObject(); + Fragment fragment = new Fragment("commit", "commitFragment", this); + + // time of day + fragment.add(WicketUtils.createTimeLabel("time", commit.getAuthorIdent() + .getWhen(), GitBlitWebSession.get().getTimezone())); + + // avatar + fragment.add(new GravatarImage("avatar", commit.getAuthorIdent(), 36)); + + // merge icon + if (commit.getParentCount() > 1) { + fragment.add(WicketUtils.newImage("commitIcon", + "commit_merge_16x16.png")); + } else { + fragment.add(WicketUtils.newBlankImage("commitIcon")); + } + + // author search link + String author = commit.getAuthorIdent().getName(); + LinkPanel authorLink = new LinkPanel("author", "list", author, + SearchPage.class, WicketUtils.newSearchParameter(commit.repository, + commit.getName(), author, Constants.SearchType.AUTHOR)); + setPersonSearchTooltip(authorLink, author, Constants.SearchType.AUTHOR); + fragment.add(authorLink); + + // repository summary page link + LinkPanel repositoryLink = new LinkPanel("repository", "list", + commit.repository, SummaryPage.class, + WicketUtils.newRepositoryParameter(commit.repository)); + fragment.add(repositoryLink); + + // repository branch + LinkPanel branchLink = new LinkPanel("branch", "list", commit.branch, + LogPage.class, WicketUtils.newObjectParameter(commit.repository, + commit.branch)); + WicketUtils.setCssStyle(branchLink, "color: #008000;"); + fragment.add(branchLink); + + LinkPanel commitid = new LinkPanel("commitid", "list subject", + commit.getShortName(), CommitPage.class, + WicketUtils.newObjectParameter(commit.repository, commit.getName())); + fragment.add(commitid); + + // message/commit link + String shortMessage = commit.getShortMessage(); + LinkPanel shortlog = new LinkPanel("message", "list subject", shortMessage, + CommitPage.class, WicketUtils.newObjectParameter(commit.repository, + commit.getName())); + fragment.add(shortlog); + + // refs + fragment.add(new RefsPanel("commitRefs", commit.repository, commit + .getRefs())); + + // view, diff, tree links + fragment.add(new BookmarkablePageLink("view", CommitPage.class, + WicketUtils.newObjectParameter(commit.repository, commit.getName()))); + fragment.add(new BookmarkablePageLink("diff", CommitDiffPage.class, + WicketUtils.newObjectParameter(commit.repository, commit.getName())) + .setEnabled(commit.getParentCount() > 0)); + fragment.add(new BookmarkablePageLink("tree", TreePage.class, + WicketUtils.newObjectParameter(commit.repository, commit.getName()))); + + item.add(fragment); + } + }; + item.add(commits); + } + }; + add(activityView); + } +}