### Todo List\r
- Code documentation\r
- Unit testing\r
-- Branch selector on Metrics\r
- Blame\r
- Clone remote repository\r
\r
public int compareTo(RefModel o) {\r
return getDate().compareTo(o.getDate());\r
}\r
+ \r
+ @Override\r
+ public String toString() {\r
+ return displayName;\r
+ }\r
}
\ No newline at end of file
\r
private static final Logger LOGGER = LoggerFactory.getLogger(MetricUtils.class);\r
\r
- public static List<Metric> getDateMetrics(Repository r, boolean includeTotal, String format) {\r
+ public static List<Metric> getDateMetrics(Repository r, String objectId, boolean includeTotal, String format) {\r
Metric total = new Metric("TOTAL");\r
final Map<String, Metric> metricMap = new HashMap<String, Metric>();\r
-\r
+ if (StringUtils.isEmpty(objectId)) {\r
+ objectId = Constants.HEAD;\r
+ }\r
if (JGitUtils.hasCommits(r)) {\r
final List<RefModel> tags = JGitUtils.getTags(r, true, -1);\r
final Map<ObjectId, RefModel> tagMap = new HashMap<ObjectId, RefModel>();\r
}\r
try {\r
RevWalk walk = new RevWalk(r);\r
- ObjectId object = r.resolve(Constants.HEAD);\r
+ ObjectId object = r.resolve(objectId);\r
RevCommit lastCommit = walk.parseCommit(object);\r
walk.markStart(lastCommit);\r
\r
int diffDays = (lastCommit.getCommitTime() - firstCommit.getCommitTime())\r
/ (60 * 60 * 24);\r
total.duration = diffDays;\r
- if (diffDays <= 90) {\r
+ if (diffDays <= 365) {\r
// Days\r
df = new SimpleDateFormat("yyyy-MM-dd");\r
- } else if (diffDays > 90 && diffDays < 365) {\r
- // Weeks\r
- df = new SimpleDateFormat("yyyy-MM (w)");\r
} else {\r
// Months\r
df = new SimpleDateFormat("yyyy-MM");\r
return metrics;\r
}\r
\r
- public static List<Metric> getAuthorMetrics(Repository r, boolean byEmail) {\r
+ public static List<Metric> getAuthorMetrics(Repository r, String objectId, boolean byEmail) {\r
final Map<String, Metric> metricMap = new HashMap<String, Metric>();\r
-\r
+ if (StringUtils.isEmpty(objectId)) {\r
+ objectId = Constants.HEAD;\r
+ }\r
if (JGitUtils.hasCommits(r)) {\r
try {\r
RevWalk walk = new RevWalk(r);\r
- ObjectId object = r.resolve(Constants.HEAD);\r
+ ObjectId object = r.resolve(objectId);\r
RevCommit lastCommit = walk.parseCommit(object);\r
walk.markStart(lastCommit);\r
\r
gb.deletion = deletion\r
gb.rename = rename\r
gb.metrics = metrics\r
+gb.stats = stats\r
gb.markdown = markdown\r
gb.changedFiles = changed files \r
gb.filesAdded = {0} files added\r
gb.showReadmeDescription = show a \"readme\" markdown file on the summary page\r
gb.nameDescription = use '/' to group repositories. e.g. libraries/mycoollib.git\r
gb.ownerDescription = the owner may edit repository settings\r
-gb.blob = blob
\ No newline at end of file
+gb.blob = blob\r
+gb.commitActivityTrend = commit activity trend\r
+gb.commitActivityDOW = commit activity by day of week\r
+gb.commitActivityAuthors = primary authors by commit activity
\ No newline at end of file
\r
<body>\r
<wicket:extend>\r
- <h2>Commit Activity</h2>\r
+ <div style="padding-top:10px;">\r
+ <!-- branch name -->\r
+ <div><span class="metricsTitle" wicket:id="branchTitle"></span></div>\r
+ \r
+ <!-- placeholder for more info -->\r
+ <div style="float:right;width:200px;text-align: left;">\r
+ </div>\r
+\r
+ <!-- branch stats -->\r
+ <h2><wicket:message key="gb.stats"></wicket:message></h2>\r
+ <span wicket:id="branchStats"></span>\r
+ \r
+ <!-- commit activity trend -->\r
+ <h2><wicket:message key="gb.commitActivityTrend"></wicket:message></h2>\r
<div><img wicket:id="commitsChart" /></div>\r
\r
- <h2>Commit Activity by Day of Week</h2>\r
+ <!-- commit activity by day of week -->\r
+ <h2><wicket:message key="gb.commitActivityDOW"></wicket:message></h2>\r
<div><img wicket:id="dayOfWeekChart" /></div>\r
\r
- <h2>Commit Activity by Time of Day</h2>\r
- <div><img wicket:id="timeOfDayChart" /></div>\r
-\r
- <h2>Most Prolific Authors</h2>\r
+ <!-- commit activity by primary authors -->\r
+ <h2><wicket:message key="gb.commitActivityAuthors"></wicket:message></h2>\r
<div><img wicket:id="authorsChart" /></div>\r
-\r
+ </div>\r
</wicket:extend>\r
</body>\r
</html>
\ No newline at end of file
\r
import java.awt.Color;\r
import java.awt.Dimension;\r
-import java.text.ParseException;\r
+import java.text.MessageFormat;\r
import java.text.SimpleDateFormat;\r
import java.util.ArrayList;\r
import java.util.Calendar;\r
import java.util.Collections;\r
import java.util.Comparator;\r
-import java.util.Date;\r
import java.util.List;\r
\r
import org.apache.wicket.PageParameters;\r
+import org.apache.wicket.markup.html.basic.Label;\r
import org.eclipse.jgit.lib.Repository;\r
import org.wicketstuff.googlecharts.Chart;\r
import org.wicketstuff.googlecharts.ChartAxis;\r
\r
import com.gitblit.models.Metric;\r
import com.gitblit.utils.MetricUtils;\r
+import com.gitblit.utils.TimeUtils;\r
import com.gitblit.wicket.WicketUtils;\r
\r
public class MetricsPage extends RepositoryPage {\r
\r
public MetricsPage(PageParameters params) {\r
- super(params);\r
+ super(params); \r
Repository r = getRepository();\r
- insertLinePlot("commitsChart", MetricUtils.getDateMetrics(r, false, null));\r
- insertBarPlot("dayOfWeekChart", getDayOfWeekMetrics(r));\r
- insertLinePlot("timeOfDayChart", getTimeOfDayMetrics(r));\r
- insertPieChart("authorsChart", getAuthorMetrics(r));\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
+ } 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
+ }\r
+ insertLinePlot("commitsChart", metrics);\r
+ insertBarPlot("dayOfWeekChart", getDayOfWeekMetrics(r, objectId));\r
+ insertPieChart("authorsChart", getAuthorMetrics(r, objectId));\r
}\r
\r
private void insertLinePlot(String wicketId, List<Metric> metrics) {\r
}\r
}\r
\r
- private List<Metric> getDayOfWeekMetrics(Repository repository) {\r
- List<Metric> list = MetricUtils.getDateMetrics(repository, false, "E");\r
+ private List<Metric> getDayOfWeekMetrics(Repository repository, String objectId) {\r
+ List<Metric> list = MetricUtils.getDateMetrics(repository, objectId, false, "E");\r
SimpleDateFormat sdf = new SimpleDateFormat("E");\r
Calendar cal = Calendar.getInstance();\r
\r
return sorted;\r
}\r
\r
- private List<Metric> getTimeOfDayMetrics(Repository repository) {\r
- SimpleDateFormat ndf = new SimpleDateFormat("yyyy-MM-dd");\r
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");\r
- List<Metric> list = MetricUtils.getDateMetrics(repository, false, "yyyy-MM-dd HH:mm");\r
- Calendar cal = Calendar.getInstance();\r
-\r
- for (Metric metric : list) {\r
- try {\r
- Date date = sdf.parse(metric.name);\r
- cal.setTime(date);\r
- double y = cal.get(Calendar.HOUR_OF_DAY) + (cal.get(Calendar.MINUTE) / 60d);\r
- metric.duration = (int) (date.getTime() / 60000L);\r
- metric.count = y;\r
- metric.name = ndf.format(date);\r
- } catch (ParseException p) {\r
- }\r
- }\r
- return list;\r
- }\r
-\r
- private List<Metric> getAuthorMetrics(Repository repository) {\r
- List<Metric> authors = MetricUtils.getAuthorMetrics(repository, true);\r
+ private List<Metric> getAuthorMetrics(Repository repository, String objectId) {\r
+ List<Metric> authors = MetricUtils.getAuthorMetrics(repository, objectId, true);\r
Collections.sort(authors, new Comparator<Metric>() {\r
@Override\r
public int compare(Metric o1, Metric o2) {\r
if (o1.count > o2.count) {\r
return -1;\r
} else if (o1.count < o2.count) {\r
- return 1; \r
+ return 1;\r
}\r
return 0;\r
}\r
\r
<!-- page nav links -->\r
<div class="page_nav"> \r
- <a wicket:id="summary"><wicket:message key="gb.summary"></wicket:message></a> | <a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a> | <a wicket:id="branches"><wicket:message key="gb.branches"></wicket:message></a> | <a wicket:id="tags"><wicket:message key="gb.tags"></wicket:message></a> | <a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a> | <a wicket:id="metrics"><wicket:message key="gb.metrics"></wicket:message></a> <span wicket:id="extra"><span wicket:id="extraSeparator"></span><span wicket:id="extraLink"></span></span>\r
+ <a wicket:id="summary"><wicket:message key="gb.summary"></wicket:message></a> | <a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a> | <a wicket:id="branches"><wicket:message key="gb.branches"></wicket:message></a> | <a wicket:id="tags"><wicket:message key="gb.tags"></wicket:message></a> | <a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a> <span wicket:id="extra"><span wicket:id="extraSeparator"></span><span wicket:id="extraLink"></span></span>\r
</div>\r
</div>\r
\r
put("branches", "gb.branches");\r
put("tags", "gb.tags");\r
put("tree", "gb.tree");\r
- put("metrics", "gb.metrics");\r
put("tickets", "gb.tickets");\r
put("edit", "gb.edit");\r
}\r
WicketUtils.newRepositoryParameter(repositoryName)));\r
add(new BookmarkablePageLink<Void>("tree", TreePage.class,\r
WicketUtils.newRepositoryParameter(repositoryName)));\r
- add(new BookmarkablePageLink<Void>("metrics", MetricsPage.class,\r
- WicketUtils.newRepositoryParameter(repositoryName)));\r
\r
// per-repository extra page links\r
List<String> extraPageLinks = new ArrayList<String>();\r
<tr><th><wicket:message key="gb.description">[description]</wicket:message></th><td><span wicket:id="repositoryDescription">[repository description]</span></td></tr>\r
<tr><th><wicket:message key="gb.owner">[owner]</wicket:message></th><td><span wicket:id="repositoryOwner">[repository owner]</span></td></tr>\r
<tr><th><wicket:message key="gb.lastChange">[last change]</wicket:message></th><td><span wicket:id="repositoryLastChange">[repository last change]</span></td></tr>\r
- <tr><th><wicket:message key="gb.metrics">[metrics]</wicket:message></th><td><span wicket:id="repositoryMetrics">[repository metrics]</span></td></tr>\r
+ <tr><th><wicket:message key="gb.stats">[stats]</wicket:message></th><td><span wicket:id="branchStats">[branch stats]</span> <span class="link"><a wicket:id="metrics"><wicket:message key="gb.metrics">[metrics]</wicket:message></a></span></td></tr>\r
<tr><th valign="top"><wicket:message key="gb.url">[URL]</wicket:message></th><td><img style="vertical-align: top; padding-right:5px;" wicket:id="accessRestrictionIcon" /><span wicket:id="repositoryCloneUrl">[repository clone url]</span></td></tr>\r
</table>\r
</div>\r
\r
import org.apache.wicket.PageParameters;\r
import org.apache.wicket.markup.html.basic.Label;\r
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;\r
import org.apache.wicket.protocol.http.WebRequest;\r
import org.eclipse.jgit.lib.Repository;\r
import org.eclipse.jgit.revwalk.RevCommit;\r
List<Metric> metrics = null;\r
Metric metricsTotal = null;\r
if (GitBlit.getBoolean(Keys.web.generateActivityGraph, true)) {\r
- metrics = MetricUtils.getDateMetrics(r, true, null);\r
+ metrics = MetricUtils.getDateMetrics(r, null, true, null);\r
metricsTotal = metrics.remove(0);\r
}\r
\r
add(WicketUtils.createTimestampLabel("repositoryLastChange", JGitUtils.getLastChange(r),\r
getTimeZone()));\r
if (metricsTotal == null) {\r
- add(new Label("repositoryMetrics", ""));\r
+ add(new Label("branchStats", "")); \r
} else {\r
- add(new Label("repositoryMetrics", MessageFormat.format(\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
}\r
+ add(new BookmarkablePageLink<Void>("metrics", MetricsPage.class, WicketUtils.newRepositoryParameter(repositoryName)));\r
\r
List<String> repositoryUrls = new ArrayList<String>();\r
\r
.setEscapeModelStrings(false));\r
\r
add(new LogPanel("commitsPanel", repositoryName, null, r, numberCommits, 0));\r
- add(new TagsPanel("tagsPanel", repositoryName, r, numberRefs));\r
- add(new BranchesPanel("branchesPanel", getRepositoryModel(), r, numberRefs));\r
+ add(new TagsPanel("tagsPanel", repositoryName, r, numberRefs).hideIfEmpty());\r
+ add(new BranchesPanel("branchesPanel", getRepositoryModel(), r, numberRefs).hideIfEmpty());\r
\r
if (getRepositoryModel().showReadme) {\r
String htmlText = null;\r
<td><span wicket:id="branchName">[branch name]</span></td>\r
<td><span wicket:id="branchType">[branch type]</span></td>\r
<td class="rightAlign">\r
- <span class="link">\r
- <a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a> | <a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a>\r
- </span> \r
+ <span wicket:id="branchLinks"></span>\r
</td>\r
</tr>\r
</tbody>\r
</table> \r
\r
<div wicket:id="allBranches">[all branches]</div> \r
- \r
+\r
+ <!-- branch page links -->\r
+ <wicket:fragment wicket:id="branchPageLinks">\r
+ <span class="link">\r
+ <a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a> | <a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a> | <a wicket:id="metrics"><wicket:message key="gb.metrics"></wicket:message></a>\r
+ </span>\r
+ </wicket:fragment>\r
+\r
+ <!-- branch panel links -->\r
+ <wicket:fragment wicket:id="branchPanelLinks">\r
+ <span class="link">\r
+ <a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a> | <a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a>\r
+ </span>\r
+ </wicket:fragment>\r
+ \r
</wicket:panel>\r
</body>\r
</html>
\ No newline at end of file
\r
import org.apache.wicket.markup.html.basic.Label;\r
import org.apache.wicket.markup.html.link.BookmarkablePageLink;\r
+import org.apache.wicket.markup.html.panel.Fragment;\r
import org.apache.wicket.markup.repeater.Item;\r
import org.apache.wicket.markup.repeater.data.DataView;\r
import org.apache.wicket.markup.repeater.data.ListDataProvider;\r
import com.gitblit.wicket.WicketUtils;\r
import com.gitblit.wicket.pages.BranchesPage;\r
import com.gitblit.wicket.pages.LogPage;\r
+import com.gitblit.wicket.pages.MetricsPage;\r
import com.gitblit.wicket.pages.SummaryPage;\r
import com.gitblit.wicket.pages.TreePage;\r
\r
\r
private static final long serialVersionUID = 1L;\r
\r
+ private final boolean hasBranches;\r
+\r
public BranchesPanel(String wicketId, final RepositoryModel model, Repository r,\r
final int maxCount) {\r
super(wicketId);\r
item.add(new Label("branchType", remote ? getString("gb.remote")\r
: getString("gb.local")).setVisible(maxCount <= 0));\r
\r
- item.add(new BookmarkablePageLink<Void>("log", LogPage.class, WicketUtils\r
- .newObjectParameter(model.name, entry.getName())));\r
- item.add(new BookmarkablePageLink<Void>("tree", TreePage.class, WicketUtils\r
- .newObjectParameter(model.name, entry.getName())));\r
-\r
+ if (maxCount <= 0) {\r
+ Fragment fragment = new Fragment("branchLinks", "branchPageLinks", this);\r
+ fragment.add(new BookmarkablePageLink<Void>("log", LogPage.class, WicketUtils\r
+ .newObjectParameter(model.name, entry.getName())));\r
+ fragment.add(new BookmarkablePageLink<Void>("tree", TreePage.class, WicketUtils\r
+ .newObjectParameter(model.name, entry.getName())));\r
+ fragment.add(new BookmarkablePageLink<Void>("metrics", MetricsPage.class,\r
+ WicketUtils.newObjectParameter(model.name, entry.getName())));\r
+ item.add(fragment);\r
+ } else {\r
+ Fragment fragment = new Fragment("branchLinks", "branchPanelLinks", this);\r
+ fragment.add(new BookmarkablePageLink<Void>("log", LogPage.class, WicketUtils\r
+ .newObjectParameter(model.name, entry.getName())));\r
+ fragment.add(new BookmarkablePageLink<Void>("tree", TreePage.class, WicketUtils\r
+ .newObjectParameter(model.name, entry.getName())));\r
+ item.add(fragment);\r
+ }\r
WicketUtils.setAlternatingBackground(item, counter);\r
counter++;\r
}\r
add(new LinkPanel("allBranches", "link", new StringResourceModel("gb.allBranches",\r
this, null), BranchesPage.class, WicketUtils.newRepositoryParameter(model.name)));\r
}\r
+ // We always have 1 branch\r
+ hasBranches = branches.size() > 1;\r
+ }\r
+\r
+ public BranchesPanel hideIfEmpty() {\r
+ setVisible(hasBranches);\r
+ return this;\r
}\r
}\r
Class<? extends RepositoryPage> linkClass = CommitPage.class;\r
String cssClass = "";\r
if (name.startsWith(Constants.R_HEADS)) {\r
- // local head\r
+ // local branch\r
linkClass = LogPage.class;\r
name = name.substring(Constants.R_HEADS.length());\r
- cssClass = "headRef";\r
+ cssClass = "localBranch";\r
} else if (name.equals(Constants.HEAD)) {\r
// local head\r
linkClass = LogPage.class;\r
cssClass = "headRef";\r
} else if (name.startsWith(Constants.R_REMOTES)) {\r
- // remote head\r
+ // remote branch\r
linkClass = LogPage.class;\r
name = name.substring(Constants.R_REMOTES.length());\r
- cssClass = "remoteRef";\r
+ cssClass = "remoteBranch";\r
} else if (name.startsWith(Constants.R_TAGS)) {\r
// tag\r
if (entry.isAnnotatedTag()) {\r
public class TagsPanel extends BasePanel {\r
\r
private static final long serialVersionUID = 1L;\r
+ \r
+ private final boolean hasTags;\r
\r
public TagsPanel(String wicketId, final String repositoryName, Repository r, final int maxCount) {\r
super(wicketId);\r
add(new LinkPanel("allTags", "link", new StringResourceModel("gb.allTags", this, null),\r
TagsPage.class, WicketUtils.newRepositoryParameter(repositoryName)));\r
}\r
+ \r
+ hasTags = tags.size() > 0;\r
+ }\r
+ \r
+ public TagsPanel hideIfEmpty() {\r
+ setVisible(hasTags);\r
+ return this;\r
}\r
}\r
width: 13em;\r
}\r
\r
-span .tagRef, span .headRef, span .remoteRef, span .otherRef { \r
+span.metricsTitle {\r
+ font-size: 2em;\r
+}\r
+\r
+span .tagRef, span .headRef, span .localBranch, span .remoteBranch, span .otherRef { \r
padding: 0px 3px;\r
margin-right:2px;\r
font-family: sans-serif;\r
color: black; \r
}\r
\r
-span .tagRef a span, span .headRef a span, span .remoteRef a span, span .otherRef a span {\r
+span .tagRef a span, span .headRef a span, span .localBranch a span, span .remoteBranch a span, span .otherRef a span {\r
font-size: 9px;\r
}\r
\r
-span .tagRef a, span .headRef a, span .remoteRef a, span .otherRef a {\r
+span .tagRef a, span .headRef a, span .localBranch a, span .remoteBranch a, span .otherRef a {\r
text-decoration: none;\r
color: black !important;\r
}\r
\r
-span .tagRef a:hover, span .headRef a:hover, span .remoteRef a:hover, span .otherRef a:hover {\r
+span .tagRef a:hover, span .headRef a:hover, span .localBranch a:hover, span .remoteBranch a:hover, span .otherRef a:hover {\r
color: black !important;\r
text-decoration: underline;\r
}\r
\r
span .otherRef {\r
- background-color: #ffaaff;\r
- border-color: #ff00ee;\r
+ background-color: #80ccdd;\r
+ border-color: #80aaaa; \r
}\r
\r
-span .remoteRef {\r
+span .remoteBranch {\r
background-color: #cAc2f5;\r
border-color: #6c6cbf;\r
}\r
}\r
\r
span .headRef {\r
+ background-color: #ffaaff;\r
+ border-color: #ff00ee;\r
+}\r
+\r
+span .localBranch {\r
background-color: #ccffcc;\r
border-color: #00cc33;\r
}\r
\r
public void testMetrics() throws Exception {\r
Repository repository = GitBlitSuite.getHelloworldRepository();\r
- List<Metric> metrics = MetricUtils.getDateMetrics(repository, true, null);\r
+ List<Metric> metrics = MetricUtils.getDateMetrics(repository, null, true, null);\r
repository.close();\r
assertTrue("No date metrics found!", metrics.size() > 0);\r
}\r
\r
public void testAuthorMetrics() throws Exception {\r
Repository repository = GitBlitSuite.getHelloworldRepository();\r
- List<Metric> byEmail = MetricUtils.getAuthorMetrics(repository, true);\r
- List<Metric> byName = MetricUtils.getAuthorMetrics(repository, false);\r
+ List<Metric> byEmail = MetricUtils.getAuthorMetrics(repository, null, true);\r
+ List<Metric> byName = MetricUtils.getAuthorMetrics(repository, null, false);\r
repository.close();\r
assertTrue("No author metrics found!", byEmail.size() == 9);\r
assertTrue("No author metrics found!", byName.size() == 8);\r