@@ -3,10 +3,15 @@ | |||
*/ | |||
package com.codecommit.wicket; | |||
import java.io.Serializable; | |||
/** | |||
* @author Daniel Spiewak | |||
*/ | |||
public class LineStyle implements ILineStyle { | |||
public class LineStyle implements ILineStyle, Serializable { | |||
private static final long serialVersionUID = 1L; | |||
private int blankLength = -1; | |||
private int segmentLength = -1; | |||
private int thickness = -1; |
@@ -3,10 +3,12 @@ | |||
*/ | |||
package com.codecommit.wicket; | |||
import java.io.Serializable; | |||
/** | |||
* @author Daniel Spiewak | |||
*/ | |||
public enum MarkerType { | |||
public enum MarkerType implements Serializable { | |||
ARROW("a"), | |||
CROSS("c"), | |||
DIAMOND("d"), |
@@ -4,11 +4,15 @@ | |||
package com.codecommit.wicket; | |||
import java.awt.Color; | |||
import java.io.Serializable; | |||
/** | |||
* @author Daniel Spiewak | |||
*/ | |||
public class ShapeMarker implements IShapeMarker { | |||
public class ShapeMarker implements IShapeMarker, Serializable { | |||
private static final long serialVersionUID = 1L; | |||
private Color color; | |||
private int index = -1; | |||
private double point = -1; |
@@ -65,7 +65,7 @@ web.generateActivityGraph = true | |||
# The number of commits to display on the summary page | |||
# Value must exceed 0 else default of 20 is used | |||
web.summaryCommitCount = 20 | |||
web.summaryCommitCount = 16 | |||
# The number of tags/heads to display on the summary page | |||
# Value must exceed 0 else default of 5 is used | |||
@@ -130,11 +130,11 @@ server.httpsPort = 443 | |||
# Specify the interface for Jetty to bind the standard connector. | |||
# You may specify an ip or an empty value to bind to all interfaces. | |||
server.httpBindInterface = | |||
server.httpBindInterface = localhost | |||
# Specify the interface for Jetty to bind the secure connector. | |||
# You may specify an ip or an empty value to bind to all interfaces. | |||
server.httpsBindInterface = | |||
server.httpsBindInterface = localhost | |||
# Password for SSL keystore (keystore password and certificate password must match) | |||
server.storePassword = dosomegit |
@@ -50,6 +50,14 @@ public class JGitUtilsTest extends TestCase { | |||
r.close(); | |||
assertTrue("Could not get last repository change date!", date != null); | |||
} | |||
public void testFirstCommit() throws Exception { | |||
Repository r = getRepository(); | |||
RevCommit commit = JGitUtils.getFirstCommit(r, null); | |||
r.close(); | |||
assertTrue("Could not get first commit!", commit != null); | |||
System.out.println(commit.getName() + " " + commit.getShortMessage()); | |||
} | |||
public void testRetrieveRevObject() throws Exception { | |||
Repository r = getRepository(); |
@@ -36,6 +36,7 @@ import org.eclipse.jgit.lib.StoredConfig; | |||
import org.eclipse.jgit.revwalk.RevBlob; | |||
import org.eclipse.jgit.revwalk.RevCommit; | |||
import org.eclipse.jgit.revwalk.RevObject; | |||
import org.eclipse.jgit.revwalk.RevSort; | |||
import org.eclipse.jgit.revwalk.RevTree; | |||
import org.eclipse.jgit.revwalk.RevWalk; | |||
import org.eclipse.jgit.revwalk.filter.RevFilter; | |||
@@ -101,6 +102,34 @@ public class JGitUtils { | |||
return list; | |||
} | |||
public static RevCommit getFirstCommit(Repository r, String branch) { | |||
if (StringUtils.isEmpty(branch)) { | |||
branch = Constants.HEAD; | |||
} | |||
try { | |||
RevWalk walk = new RevWalk(r); | |||
walk.sort(RevSort.REVERSE); | |||
RevCommit head = walk.parseCommit(r.resolve(branch)); | |||
walk.markStart(head); | |||
RevCommit commit = walk.next(); | |||
walk.dispose(); | |||
return commit; | |||
} catch (Throwable t) { | |||
LOGGER.error("Failed to determine first commit", t); | |||
} | |||
return null; | |||
} | |||
public static Date getFirstChange(Repository r, String branch) { | |||
try { | |||
RevCommit commit = getFirstCommit(r, branch); | |||
return getCommitDate(commit); | |||
} catch (Throwable t) { | |||
LOGGER.error("Failed to determine first change", t); | |||
} | |||
return null; | |||
} | |||
public static Date getLastChange(Repository r) { | |||
RevCommit commit = getCommit(r, Constants.HEAD); | |||
return getCommitDate(commit); | |||
@@ -525,7 +554,7 @@ public class JGitUtils { | |||
} | |||
return null; | |||
} | |||
public String toString() { | |||
return name().toLowerCase(); | |||
} | |||
@@ -552,7 +581,7 @@ public class JGitUtils { | |||
case AUTHOR: | |||
return (commit.getAuthorIdent().getName().toLowerCase().indexOf(lcValue) > -1) || (commit.getAuthorIdent().getEmailAddress().toLowerCase().indexOf(lcValue) > -1); | |||
case COMMITTER: | |||
return (commit.getCommitterIdent().getName().toLowerCase().indexOf(lcValue) > -1)|| (commit.getCommitterIdent().getEmailAddress().toLowerCase().indexOf(lcValue) > -1); | |||
return (commit.getCommitterIdent().getName().toLowerCase().indexOf(lcValue) > -1) || (commit.getCommitterIdent().getEmailAddress().toLowerCase().indexOf(lcValue) > -1); | |||
case COMMIT: | |||
return commit.getFullMessage().toLowerCase().indexOf(lcValue) > -1; | |||
} | |||
@@ -695,29 +724,57 @@ public class JGitUtils { | |||
public static List<Metric> getDateMetrics(Repository r) { | |||
final List<RefModel> tags = getTags(r, -1); | |||
final Map<String, Metric> map = new HashMap<String, Metric>(); | |||
final Map<ObjectId, RefModel> tagMap = new HashMap<ObjectId, RefModel>(); | |||
for (RefModel tag : tags) { | |||
tagMap.put(tag.getCommitId(), tag); | |||
} | |||
Metric total = new Metric("TOTAL"); | |||
final Map<String, Metric> metricMap = new HashMap<String, Metric>(); | |||
try { | |||
DateFormat df = new SimpleDateFormat("yyyy-MM"); | |||
RevWalk walk = new RevWalk(r); | |||
ObjectId object = r.resolve(Constants.HEAD); | |||
walk.markStart(walk.parseCommit(object)); | |||
RevCommit firstCommit = getFirstCommit(r, Constants.HEAD); | |||
RevCommit lastCommit = walk.parseCommit(object); | |||
int diffDays = (lastCommit.getCommitTime() - firstCommit.getCommitTime()) / (60 * 60 * 24); | |||
total.duration = diffDays; | |||
DateFormat df; | |||
if (diffDays <= 90) { | |||
// Days | |||
df = new SimpleDateFormat("yyyy-MM-dd"); | |||
} else if (diffDays > 90 && diffDays < 365) { | |||
// Weeks | |||
df = new SimpleDateFormat("yyyy-MM (w)"); | |||
} else { | |||
// Months | |||
df = new SimpleDateFormat("yyyy-MM"); | |||
} | |||
walk.markStart(lastCommit); | |||
Iterable<RevCommit> revlog = walk; | |||
for (RevCommit rev : revlog) { | |||
Date d = getCommitDate(rev); | |||
String p = df.format(d); | |||
if (!map.containsKey(p)) | |||
map.put(p, new Metric(p)); | |||
map.get(p).count++; | |||
} | |||
if (!metricMap.containsKey(p)) | |||
metricMap.put(p, new Metric(p)); | |||
Metric m = metricMap.get(p); | |||
m.count++; | |||
total.count++; | |||
if (tagMap.containsKey(rev.getId())) { | |||
m.tag++; | |||
total.tag++; | |||
} | |||
} | |||
} catch (Throwable t) { | |||
LOGGER.error("Failed to mine log history for metrics", t); | |||
} | |||
List<String> keys = new ArrayList<String>(map.keySet()); | |||
List<String> keys = new ArrayList<String>(metricMap.keySet()); | |||
Collections.sort(keys); | |||
List<Metric> metrics = new ArrayList<Metric>(); | |||
for (String key : keys) { | |||
metrics.add(map.get(key)); | |||
metrics.add(metricMap.get(key)); | |||
} | |||
metrics.add(0, total); | |||
return metrics; | |||
} | |||
@@ -23,6 +23,40 @@ public class TimeUtils { | |||
return now.getDate() == (date.getDate() + 1) && now.getMonth() == date.getMonth() && now.getYear() == date.getYear(); | |||
} | |||
public static String duration(int days) { | |||
if (days <= 60) { | |||
return days + (days > 1 ? " days" : " day"); | |||
} else if (days <= 365) { | |||
int rem = days % 30; | |||
return (days / 30) + " months, " + rem + (rem > 1 ? " days" : " day"); | |||
} else { | |||
int years = days / 365; | |||
int rem = days % 365; | |||
String yearsString = years + (years > 1 ? " years" : " year"); | |||
if (rem < 30) { | |||
if (rem == 0) { | |||
return yearsString; | |||
} else { | |||
return yearsString + ", " + rem + (rem > 1 ? " days" : " day"); | |||
} | |||
} else { | |||
int months = rem / 30; | |||
int remDays = (rem % 30); | |||
String monthsString; | |||
if (months == 0) { | |||
monthsString = yearsString; | |||
} else { | |||
monthsString = yearsString + ", " + months + (months > 1 ? " months" : " month"); | |||
} | |||
if (remDays == 0) { | |||
return monthsString; | |||
} else { | |||
return monthsString + ", " + remDays + (remDays > 1 ? " days":" day"); | |||
} | |||
} | |||
} | |||
} | |||
public static int minutesAgo(Date date, long endTime, boolean roundup) { | |||
long diff = endTime - date.getTime(); | |||
int mins = (int) (diff / min); |
@@ -54,4 +54,5 @@ gb.searchForCommitter = Search for commits committed by | |||
gb.addition = addition | |||
gb.modification = modification | |||
gb.deletion = deletion | |||
gb.rename = rename | |||
gb.rename = rename | |||
gb.stats = stats |
@@ -8,6 +8,8 @@ public class Metric implements Serializable { | |||
public String name; | |||
public double count; | |||
public double tag; | |||
public int duration; | |||
public Metric(String name) { | |||
this.name = name; |
@@ -12,17 +12,18 @@ | |||
<div style="clear:both;"> | |||
<!-- Repository Activity Chart --> | |||
<div style="width:400px;float:right;"> | |||
<div style="float:right;"> | |||
<img class="activityGraph" wicket:id="commitsChart" /> | |||
</div> | |||
<!-- Repository info --> | |||
<div style="margin-right:410px;"> | |||
<div> | |||
<table class="plain"> | |||
<tr><th><wicket:message key="gb.description">description</wicket:message></th><td><span wicket:id="repositoryDescription">[repository description]</span></td></tr> | |||
<tr><th><wicket:message key="gb.owner">owner</wicket:message></th><td><span wicket:id="repositoryOwner">[repository owner]</span></td></tr> | |||
<tr><th><wicket:message key="gb.lastChange">last change</wicket:message></th><td><span wicket:id="repositoryLastChange">[repository last change]</span></td></tr> | |||
<tr><th><wicket:message key="gb.url">URL</wicket:message></th><td><span wicket:id="repositoryCloneUrl">[repository clone url]</span></td></tr> | |||
<tr><th><wicket:message key="gb.description">[description]</wicket:message></th><td><span wicket:id="repositoryDescription">[repository description]</span></td></tr> | |||
<tr><th><wicket:message key="gb.owner">[owner]</wicket:message></th><td><span wicket:id="repositoryOwner">[repository owner]</span></td></tr> | |||
<tr><th><wicket:message key="gb.lastChange">[last change]</wicket:message></th><td><span wicket:id="repositoryLastChange">[repository last change]</span></td></tr> | |||
<tr><th><wicket:message key="gb.stats">[stats]</wicket:message></th><td><span wicket:id="repositoryStats">[repository stats]</span></td></tr> | |||
<tr><th><wicket:message key="gb.url">[URL]</wicket:message></th><td><span wicket:id="repositoryCloneUrl">[repository clone url]</span></td></tr> | |||
</table> | |||
</div> | |||
</div> |
@@ -1,6 +1,8 @@ | |||
package com.gitblit.wicket.pages; | |||
import java.awt.Color; | |||
import java.awt.Dimension; | |||
import java.text.MessageFormat; | |||
import java.util.List; | |||
import org.apache.wicket.PageParameters; | |||
@@ -15,9 +17,13 @@ import com.codecommit.wicket.ChartAxisType; | |||
import com.codecommit.wicket.ChartProvider; | |||
import com.codecommit.wicket.ChartType; | |||
import com.codecommit.wicket.IChartData; | |||
import com.codecommit.wicket.LineStyle; | |||
import com.codecommit.wicket.MarkerType; | |||
import com.codecommit.wicket.ShapeMarker; | |||
import com.gitblit.GitBlit; | |||
import com.gitblit.Keys; | |||
import com.gitblit.utils.JGitUtils; | |||
import com.gitblit.utils.TimeUtils; | |||
import com.gitblit.wicket.RepositoryPage; | |||
import com.gitblit.wicket.WicketUtils; | |||
import com.gitblit.wicket.models.Metric; | |||
@@ -44,11 +50,11 @@ public class SummaryPage extends RepositoryPage { | |||
} | |||
Repository r = getRepository(); | |||
List<Metric> metrics = JGitUtils.getDateMetrics(r); | |||
long numberOfCommits = 0; | |||
for (Metric m : metrics) { | |||
numberOfCommits += m.count; | |||
List<Metric> metrics = null; | |||
Metric metricsTotal = null; | |||
if (GitBlit.self().settings().getBoolean(Keys.web.generateActivityGraph, true)) { | |||
metrics = JGitUtils.getDateMetrics(r); | |||
metricsTotal = metrics.remove(0); | |||
} | |||
// repository description | |||
@@ -56,6 +62,11 @@ public class SummaryPage extends RepositoryPage { | |||
add(new Label("repositoryOwner", JGitUtils.getRepositoryOwner(r))); | |||
add(WicketUtils.createTimestampLabel("repositoryLastChange", JGitUtils.getLastChange(r), getTimeZone())); | |||
if (metricsTotal == null) { | |||
add(new Label("repositoryStats", "")); | |||
} else { | |||
add(new Label("repositoryStats", MessageFormat.format("{0} commits and {1} tags in {2}", metricsTotal.count, metricsTotal.tag, TimeUtils.duration(metricsTotal.duration)))); | |||
} | |||
add(new Label("repositoryCloneUrl", GitBlit.self().getCloneUrl(repositoryName))); | |||
add(new LogPanel("commitsPanel", repositoryName, null, r, numberCommits, 0)); | |||
@@ -75,7 +86,7 @@ public class SummaryPage extends RepositoryPage { | |||
if (GitBlit.self().settings().getBoolean(Keys.web.generateActivityGraph, true)) { | |||
IChartData data = getChartData(metrics); | |||
ChartProvider provider = new ChartProvider(new Dimension(400, 80), ChartType.LINE, data); | |||
ChartProvider provider = new ChartProvider(new Dimension(400, 100), ChartType.LINE, data); | |||
ChartAxis dateAxis = new ChartAxis(ChartAxisType.BOTTOM); | |||
dateAxis.setLabels(new String[] { metrics.get(0).name, metrics.get(metrics.size() / 2).name, metrics.get(metrics.size() - 1).name }); | |||
provider.addAxis(dateAxis); | |||
@@ -84,6 +95,9 @@ public class SummaryPage extends RepositoryPage { | |||
commitAxis.setLabels(new String[] { "", String.valueOf((int) maxValue(metrics)) }); | |||
provider.addAxis(commitAxis); | |||
provider.setLineStyles(new LineStyle[] {new LineStyle(2, 4, 0), new LineStyle(0, 4, 1)}); | |||
provider.addShapeMarker(new ShapeMarker(MarkerType.DIAMOND, Color.BLUE, 1, -1, 5)); | |||
add(new Chart("commitsChart", provider)); | |||
} else { | |||
add(new ContextImage("commitsChart", "blank.png")); | |||
@@ -91,23 +105,25 @@ public class SummaryPage extends RepositoryPage { | |||
} | |||
protected IChartData getChartData(List<Metric> metrics) { | |||
final double[] counts = new double[metrics.size()]; | |||
final double[] commits = new double[metrics.size()]; | |||
final double[] tags = new double[metrics.size()]; | |||
int i = 0; | |||
double max = 0; | |||
for (Metric m : metrics) { | |||
counts[i++] = m.count; | |||
commits[i] = m.count; | |||
if (m.tag > 0) { | |||
tags[i] = m.count; | |||
} else { | |||
tags[i] = -1d; | |||
} | |||
max = Math.max(max, m.count); | |||
i++; | |||
} | |||
final double dmax = max; | |||
IChartData data = new AbstractChartData() { | |||
IChartData data = new AbstractChartData(max) { | |||
private static final long serialVersionUID = 1L; | |||
public double[][] getData() { | |||
return new double[][] { counts }; | |||
} | |||
public double getMax() { | |||
return dmax; | |||
return new double[][] { commits, tags }; | |||
} | |||
}; | |||
return data; |