From f1720ca884bc3fa9da1288ad955e46f165aa4168 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Wed, 01 Jun 2011 08:09:46 -0400
Subject: [PATCH] Unit testing. Disable links on first commit. Initial stats page.

---
 src/com/gitblit/utils/TimeUtils.java             |    2 
 .gitignore                                       |    1 
 src/com/gitblit/utils/ByteFormat.java            |    2 
 src/com/gitblit/wicket/pages/RepositoryPage.java |    3 
 src/com/gitblit/wicket/resources/gitblit.css     |    2 
 src/com/gitblit/wicket/WicketUtils.java          |   65 ++++++
 src/com/gitblit/wicket/pages/StatsPage.java      |  189 ++++++++++++++++++
 src/com/gitblit/wicket/pages/CommitDiffPage.html |    2 
 src/com/gitblit/wicket/pages/CommitPage.java     |    5 
 tests/com/gitblit/tests/JGitUtilsTest.java       |   59 ++++-
 src/com/gitblit/wicket/pages/RepositoryPage.html |    2 
 distrib/users.properties                         |    3 
 src/com/gitblit/wicket/panels/LogPanel.java      |    2 
 src/com/gitblit/wicket/pages/CommitDiffPage.java |    3 
 src/com/gitblit/wicket/pages/StatsPage.html      |   23 ++
 src/com/gitblit/wicket/GitBlitWebApp.java        |    4 
 src/com/gitblit/wicket/pages/SummaryPage.java    |   50 ----
 src/com/gitblit/utils/JGitUtils.java             |  153 +++++++++++---
 18 files changed, 464 insertions(+), 106 deletions(-)

diff --git a/.gitignore b/.gitignore
index 4495e70..87e1b32 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@
 /users.properties
 /site
 /git
+/target
diff --git a/distrib/users.properties b/distrib/users.properties
index d552975..233e9f9 100644
--- a/distrib/users.properties
+++ b/distrib/users.properties
@@ -1,2 +1,3 @@
-# Gitblit realm file format: username=password,\#permission,repository1,repository2...
+## Git:Blit realm file format: username=password,\#permission,repository1,repository2...
+#Tue May 31 11:19:53 EDT 2011
 admin=admin,\#admin
diff --git a/src/com/gitblit/utils/ByteFormat.java b/src/com/gitblit/utils/ByteFormat.java
index 97b77bd..ea198de 100644
--- a/src/com/gitblit/utils/ByteFormat.java
+++ b/src/com/gitblit/utils/ByteFormat.java
@@ -28,7 +28,7 @@
 	}
 
 	public String format(long value) {
-		return format(new Long(value));
+		return format(Long.valueOf(value));
 	}
 
 	public StringBuffer format(Object obj, StringBuffer buf, FieldPosition pos) {
diff --git a/src/com/gitblit/utils/JGitUtils.java b/src/com/gitblit/utils/JGitUtils.java
index ecf12e7..4c7f14f 100644
--- a/src/com/gitblit/utils/JGitUtils.java
+++ b/src/com/gitblit/utils/JGitUtils.java
@@ -75,9 +75,9 @@
 
 import com.gitblit.models.Metric;
 import com.gitblit.models.PathModel;
+import com.gitblit.models.PathModel.PathChangeModel;
 import com.gitblit.models.RefModel;
 import com.gitblit.models.TicketModel;
-import com.gitblit.models.PathModel.PathChangeModel;
 import com.gitblit.models.TicketModel.Comment;
 
 public class JGitUtils {
@@ -100,11 +100,11 @@
 
 	public static List<String> getNestedRepositories(File repositoriesFolder, File folder,
 			boolean exportAll, boolean readNested) {
-		String basefile = repositoriesFolder.getAbsolutePath();
 		List<String> list = new ArrayList<String>();
 		if (folder == null || !folder.exists()) {
 			return list;
 		}
+		String basefile = repositoriesFolder.getAbsolutePath();
 		for (File file : folder.listFiles()) {
 			if (file.isDirectory() && !file.getName().equalsIgnoreCase(Constants.DOT_GIT)) {
 				// if this is a git repository add it to the list
@@ -169,17 +169,15 @@
 	}
 
 	public static Date getFirstChange(Repository r, String branch) {
-		try {
-			RevCommit commit = getFirstCommit(r, branch);
-			if (commit == null) {
-				// fresh repository
-				return new Date(r.getDirectory().lastModified());
+		RevCommit commit = getFirstCommit(r, branch);
+		if (commit == null) {
+			if (r == null || !r.getDirectory().exists()) {
+				return new Date(0);
 			}
-			return getCommitDate(commit);
-		} catch (Throwable t) {
-			LOGGER.error("Failed to determine first change", t);
+			// fresh repository
+			return new Date(r.getDirectory().lastModified());
 		}
-		return null;
+		return getCommitDate(commit);
 	}
 
 	public static boolean hasCommits(Repository r) {
@@ -375,30 +373,41 @@
 		}
 		try {
 			final RevWalk rw = new RevWalk(r);
-			RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
-			RevTree parentTree = parent.getTree();
+
 			RevTree commitTree = commit.getTree();
 
 			final TreeWalk walk = new TreeWalk(r);
 			walk.reset();
 			walk.setRecursive(true);
-			walk.addTree(parentTree);
-			walk.addTree(commitTree);
-			walk.setFilter(TreeFilter.ANY_DIFF);
+			if (commit.getParentCount() == 0) {
+				walk.addTree(commitTree);
+				while (walk.next()) {
+					list.add(new PathChangeModel(walk.getPathString(), walk.getPathString(), 0,
+							walk.getRawMode(0), commit.getId().getName(), ChangeType.ADD));
+				}
+			} else {
+				RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
+				RevTree parentTree = parent.getTree();
+				walk.addTree(parentTree);
+				walk.addTree(commitTree);
+				walk.setFilter(TreeFilter.ANY_DIFF);
 
-			RawTextComparator cmp = RawTextComparator.DEFAULT;
-			DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE);
-			df.setRepository(r);
-			df.setDiffComparator(cmp);
-			df.setDetectRenames(true);
-			List<DiffEntry> diffs = df.scan(parentTree, commitTree);
-			for (DiffEntry diff : diffs) {
-				if (diff.getChangeType().equals(ChangeType.DELETE)) {
-					list.add(new PathChangeModel(diff.getOldPath(), diff.getOldPath(), 0, diff
-							.getNewMode().getBits(), commit.getId().getName(), diff.getChangeType()));
-				} else {
-					list.add(new PathChangeModel(diff.getNewPath(), diff.getNewPath(), 0, diff
-							.getNewMode().getBits(), commit.getId().getName(), diff.getChangeType()));
+				RawTextComparator cmp = RawTextComparator.DEFAULT;
+				DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE);
+				df.setRepository(r);
+				df.setDiffComparator(cmp);
+				df.setDetectRenames(true);
+				List<DiffEntry> diffs = df.scan(parentTree, commitTree);
+				for (DiffEntry diff : diffs) {
+					if (diff.getChangeType().equals(ChangeType.DELETE)) {
+						list.add(new PathChangeModel(diff.getOldPath(), diff.getOldPath(), 0, diff
+								.getNewMode().getBits(), commit.getId().getName(), diff
+								.getChangeType()));
+					} else {
+						list.add(new PathChangeModel(diff.getNewPath(), diff.getNewPath(), 0, diff
+								.getNewMode().getBits(), commit.getId().getName(), diff
+								.getChangeType()));
+					}
 				}
 			}
 		} catch (Throwable t) {
@@ -509,10 +518,6 @@
 			return "missing";
 		}
 		return "" + mode;
-	}
-
-	public static boolean isTreeFromMode(int mode) {
-		return FileMode.TREE.equals(mode);
 	}
 
 	public static List<RevCommit> getRevLog(Repository r, int maxCount) {
@@ -775,7 +780,45 @@
 		return false;
 	}
 
-	public static List<Metric> getDateMetrics(Repository r) {
+	public static List<Metric> getDateMetrics(Repository r, boolean includeTotal, String format) {
+		Metric total = new Metric("TOTAL");
+		final Map<String, Metric> metricMap = new HashMap<String, Metric>();
+
+		if (hasCommits(r)) {			
+			try {
+				RevWalk walk = new RevWalk(r);
+				ObjectId object = r.resolve(Constants.HEAD);
+				RevCommit lastCommit = walk.parseCommit(object);
+				walk.markStart(lastCommit);
+				SimpleDateFormat df = new SimpleDateFormat(format);
+				Iterable<RevCommit> revlog = walk;
+				for (RevCommit rev : revlog) {
+					Date d = getCommitDate(rev);
+					String p = df.format(d);
+					if (!metricMap.containsKey(p)) {
+						metricMap.put(p, new Metric(p));
+					}
+					Metric m = metricMap.get(p);
+					m.count++;
+					total.count++;					
+				}
+			} catch (Throwable t) {
+				LOGGER.error("Failed to mine log history for metrics", t);
+			}
+		}
+		List<String> keys = new ArrayList<String>(metricMap.keySet());
+		Collections.sort(keys);
+		List<Metric> metrics = new ArrayList<Metric>();
+		for (String key : keys) {
+			metrics.add(metricMap.get(key));
+		}
+		if (includeTotal) {
+			metrics.add(0, total);
+		}
+		return metrics;
+	}
+
+	public static List<Metric> getDateMetrics(Repository r, boolean includeTotal) {
 		Metric total = new Metric("TOTAL");
 		final Map<String, Metric> metricMap = new HashMap<String, Metric>();
 
@@ -832,9 +875,49 @@
 		for (String key : keys) {
 			metrics.add(metricMap.get(key));
 		}
-		metrics.add(0, total);
+		if (includeTotal) {
+			metrics.add(0, total);
+		}
 		return metrics;
 	}
+	
+	public static List<Metric> getAuthorMetrics(Repository r) {
+		Metric total = new Metric("TOTAL");
+		final Map<String, Metric> metricMap = new HashMap<String, Metric>();
+
+		if (hasCommits(r)) {
+			try {
+				RevWalk walk = new RevWalk(r);
+				ObjectId object = r.resolve(Constants.HEAD);
+				RevCommit lastCommit = walk.parseCommit(object);
+				walk.markStart(lastCommit);
+
+				Iterable<RevCommit> revlog = walk;
+				for (RevCommit rev : revlog) {
+					String p = rev.getAuthorIdent().getName();
+					if (StringUtils.isEmpty(p)) {
+						p = rev.getAuthorIdent().getEmailAddress();
+					}
+					if (!metricMap.containsKey(p)) {
+						metricMap.put(p, new Metric(p));
+					}
+					Metric m = metricMap.get(p);
+					m.count++;
+					total.count++;
+				}
+			} catch (Throwable t) {
+				LOGGER.error("Failed to mine log history for metrics", t);
+			}
+		}
+		List<String> keys = new ArrayList<String>(metricMap.keySet());
+		Collections.sort(keys);
+		List<Metric> metrics = new ArrayList<Metric>();
+		for (String key : keys) {
+			metrics.add(metricMap.get(key));
+		}
+		return metrics;
+	}
+
 
 	public static RefModel getTicketsBranch(Repository r) {
 		RefModel ticgitBranch = null;
diff --git a/src/com/gitblit/utils/TimeUtils.java b/src/com/gitblit/utils/TimeUtils.java
index 805b44f..ece87dd 100644
--- a/src/com/gitblit/utils/TimeUtils.java
+++ b/src/com/gitblit/utils/TimeUtils.java
@@ -47,7 +47,7 @@
 			return days + (days > 1 ? " days" : " day");
 		} else if (days < 365) {
 			int rem = days % 30;
-			return (days / 30) + (rem >= 15 ? 1 : 0) + " months";
+			return ((days / 30) + (rem >= 15 ? 1 : 0)) + " months";
 		} else {
 			int years = days / 365;
 			int rem = days % 365;
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.java b/src/com/gitblit/wicket/GitBlitWebApp.java
index 1d251d9..71f5aad 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp.java
+++ b/src/com/gitblit/wicket/GitBlitWebApp.java
@@ -41,6 +41,7 @@
 import com.gitblit.wicket.pages.RawPage;
 import com.gitblit.wicket.pages.RepositoriesPage;
 import com.gitblit.wicket.pages.SearchPage;
+import com.gitblit.wicket.pages.StatsPage;
 import com.gitblit.wicket.pages.SummaryPage;
 import com.gitblit.wicket.pages.TagPage;
 import com.gitblit.wicket.pages.TagsPage;
@@ -83,7 +84,8 @@
 		mount("/patch", PatchPage.class, "r", "h", "f");
 		mount("/history", HistoryPage.class, "r", "h", "f");
 		mount("/search", SearchPage.class);
-
+		mount("/stats", StatsPage.class, "r");
+		
 		// setup ticket urls
 		mount("/tickets", TicketsPage.class, "r");
 		mount("/ticket", TicketPage.class, "r", "h", "f");
diff --git a/src/com/gitblit/wicket/WicketUtils.java b/src/com/gitblit/wicket/WicketUtils.java
index ac31488..aef68ee 100644
--- a/src/com/gitblit/wicket/WicketUtils.java
+++ b/src/com/gitblit/wicket/WicketUtils.java
@@ -17,6 +17,7 @@
 
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
+import java.util.Collection;
 import java.util.Date;
 import java.util.List;
 import java.util.TimeZone;
@@ -29,9 +30,12 @@
 import org.apache.wicket.resource.ContextRelativeResource;
 import org.eclipse.jgit.diff.DiffEntry.ChangeType;
 import org.eclipse.jgit.lib.Constants;
+import org.wicketstuff.googlecharts.AbstractChartData;
+import org.wicketstuff.googlecharts.IChartData;
 
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
+import com.gitblit.models.Metric;
 import com.gitblit.utils.JGitUtils.SearchType;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.utils.TimeUtils;
@@ -295,4 +299,65 @@
 		WicketUtils.setHtmlTooltip(label, title);
 		return label;
 	}
+	
+	public static IChartData getChartData(Collection<Metric> metrics) {
+		final double[] commits = new double[metrics.size()];
+		final double[] tags = new double[metrics.size()];
+		int i = 0;
+		double max = 0;
+		for (Metric m : metrics) {
+			commits[i] = m.count;
+			if (m.tag > 0) {
+				tags[i] = m.count;
+			} else {
+				tags[i] = -1d;
+			}
+			max = Math.max(max, m.count);
+			i++;
+		}
+		IChartData data = new AbstractChartData(max) {
+			private static final long serialVersionUID = 1L;
+
+			public double[][] getData() {
+				return new double[][] { commits, tags };
+			}
+		};
+		return data;
+	}
+
+	public static double maxValue(Collection<Metric> metrics) {
+		double max = Double.MIN_VALUE;
+		for (Metric m : metrics) {
+			if (m.count > max) {
+				max = m.count;
+			}
+		}
+		return max;
+	}
+	
+	public static IChartData getScatterData(Collection<Metric> metrics) {
+		final double[] y = new double[metrics.size()];
+		final double[] x = new double[metrics.size()];
+		int i = 0;
+		double max = 0;
+		for (Metric m : metrics) {
+			y[i] = m.count;
+			if (m.duration > 0) {
+				x[i] = m.duration;
+			} else {
+				x[i] = -1d;
+			}
+			max = Math.max(max, m.count);
+			i++;
+		}
+		IChartData data = new AbstractChartData(max) {
+			private static final long serialVersionUID = 1L;
+
+			public double[][] getData() {
+				return new double[][] { x, y };
+			}
+		};
+		return data;
+	}
+
 }
diff --git a/src/com/gitblit/wicket/pages/CommitDiffPage.html b/src/com/gitblit/wicket/pages/CommitDiffPage.html
index 50a8877..8f238a7 100644
--- a/src/com/gitblit/wicket/pages/CommitDiffPage.html
+++ b/src/com/gitblit/wicket/pages/CommitDiffPage.html
@@ -16,7 +16,7 @@
 	<div wicket:id="commitHeader">[commit header]</div>
 
 	<!-- changed paths -->
-	<div style="padding-top:15px;">
+	<div style="padding-top:15px">
 		<!-- commit legend -->
 		<div style="text-align:right;" wicket:id="commitLegend"></div>
 	
diff --git a/src/com/gitblit/wicket/pages/CommitDiffPage.java b/src/com/gitblit/wicket/pages/CommitDiffPage.java
index 1f492ac..e7af833 100644
--- a/src/com/gitblit/wicket/pages/CommitDiffPage.java
+++ b/src/com/gitblit/wicket/pages/CommitDiffPage.java
@@ -24,6 +24,7 @@
 import org.apache.wicket.markup.repeater.Item;
 import org.apache.wicket.markup.repeater.data.DataView;
 import org.apache.wicket.markup.repeater.data.ListDataProvider;
+import org.eclipse.jgit.diff.DiffEntry.ChangeType;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 
@@ -99,7 +100,7 @@
 						newPathParameter(entry.path)));
 				item.add(new BookmarkablePageLink<Void>("blame", BlobPage.class).setEnabled(false));
 				item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class,
-						newPathParameter(entry.path)));
+						newPathParameter(entry.path)).setEnabled(!entry.changeType.equals(ChangeType.ADD) && !entry.changeType.equals(ChangeType.DELETE)));
 
 				WicketUtils.setAlternatingBackground(item, counter);
 				counter++;
diff --git a/src/com/gitblit/wicket/pages/CommitPage.java b/src/com/gitblit/wicket/pages/CommitPage.java
index dc674a2..bc0d879 100644
--- a/src/com/gitblit/wicket/pages/CommitPage.java
+++ b/src/com/gitblit/wicket/pages/CommitPage.java
@@ -26,6 +26,7 @@
 import org.apache.wicket.markup.repeater.data.DataView;
 import org.apache.wicket.markup.repeater.data.ListDataProvider;
 import org.apache.wicket.model.StringResourceModel;
+import org.eclipse.jgit.diff.DiffEntry.ChangeType;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 
@@ -134,12 +135,12 @@
 				}
 
 				item.add(new BookmarkablePageLink<Void>("diff", BlobDiffPage.class,
-						newPathParameter(entry.path)));
+						newPathParameter(entry.path)).setEnabled(!entry.changeType.equals(ChangeType.ADD) && !entry.changeType.equals(ChangeType.DELETE)));
 				item.add(new BookmarkablePageLink<Void>("view", BlobPage.class,
 						newPathParameter(entry.path)));
 				item.add(new BookmarkablePageLink<Void>("blame", BlobPage.class).setEnabled(false));
 				item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class,
-						newPathParameter(entry.path)));
+						newPathParameter(entry.path)).setEnabled(!entry.changeType.equals(ChangeType.ADD)));
 
 				WicketUtils.setAlternatingBackground(item, counter);
 				counter++;
diff --git a/src/com/gitblit/wicket/pages/RepositoryPage.html b/src/com/gitblit/wicket/pages/RepositoryPage.html
index 0e0ce47..0f245ef 100644
--- a/src/com/gitblit/wicket/pages/RepositoryPage.html
+++ b/src/com/gitblit/wicket/pages/RepositoryPage.html
@@ -18,7 +18,7 @@
 		
 			<!-- page nav links -->
 			<div class="page_nav">		
-				<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>
+				<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="stats"><wicket:message key="gb.stats"></wicket:message></a> <span wicket:id="extra"><span wicket:id="extraSeparator"></span><span wicket:id="extraLink"></span></span>
 			</div>
 		</div>
 		
diff --git a/src/com/gitblit/wicket/pages/RepositoryPage.java b/src/com/gitblit/wicket/pages/RepositoryPage.java
index 109c51a..2610c4c 100644
--- a/src/com/gitblit/wicket/pages/RepositoryPage.java
+++ b/src/com/gitblit/wicket/pages/RepositoryPage.java
@@ -75,6 +75,7 @@
 			put("branches", "gb.branches");
 			put("tags", "gb.tags");
 			put("tree", "gb.tree");
+			put("stats", "gb.stats");
 			put("tickets", "gb.tickets");
 			put("edit", "gb.edit");
 		}
@@ -103,6 +104,8 @@
 				WicketUtils.newRepositoryParameter(repositoryName)));
 		add(new BookmarkablePageLink<Void>("tree", TreePage.class,
 				WicketUtils.newRepositoryParameter(repositoryName)));
+		add(new BookmarkablePageLink<Void>("stats", StatsPage.class,
+				WicketUtils.newRepositoryParameter(repositoryName)));
 
 		// per-repository extra page links
 		List<String> extraPageLinks = new ArrayList<String>();
diff --git a/src/com/gitblit/wicket/pages/StatsPage.html b/src/com/gitblit/wicket/pages/StatsPage.html
new file mode 100644
index 0000000..d6f23e0
--- /dev/null
+++ b/src/com/gitblit/wicket/pages/StatsPage.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en">
+      
+<body>
+<wicket:extend>
+		<h2>Commit Activity</h2>
+		<div><img wicket:id="commitsChart" /></div>
+		
+		<h2>Commit Activity by Day of Week</h2>
+		<div><img wicket:id="dayOfWeekChart" /></div>
+		
+		<h2>Commit Activity by Time of Day</h2>
+		<div><img wicket:id="timeOfDayChart" /></div>
+
+		<h2>Most Prolific Authors</h2>
+		<div><img wicket:id="authorsChart" /></div>
+
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/StatsPage.java b/src/com/gitblit/wicket/pages/StatsPage.java
new file mode 100644
index 0000000..0b16211
--- /dev/null
+++ b/src/com/gitblit/wicket/pages/StatsPage.java
@@ -0,0 +1,189 @@
+/*
+ * 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.awt.Color;
+import java.awt.Dimension;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.wicket.PageParameters;
+import org.eclipse.jgit.lib.Repository;
+import org.wicketstuff.googlecharts.Chart;
+import org.wicketstuff.googlecharts.ChartAxis;
+import org.wicketstuff.googlecharts.ChartAxisType;
+import org.wicketstuff.googlecharts.ChartProvider;
+import org.wicketstuff.googlecharts.ChartType;
+import org.wicketstuff.googlecharts.IChartData;
+import org.wicketstuff.googlecharts.LineStyle;
+import org.wicketstuff.googlecharts.MarkerType;
+import org.wicketstuff.googlecharts.ShapeMarker;
+
+import com.gitblit.models.Metric;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.wicket.WicketUtils;
+
+public class StatsPage extends RepositoryPage {
+
+	public StatsPage(PageParameters params) {
+		super(params);
+		Repository r = getRepository();
+		insertLinePlot("commitsChart", JGitUtils.getDateMetrics(r, false));
+		insertBarPlot("dayOfWeekChart", getDayOfWeekMetrics(r));
+		insertLinePlot("timeOfDayChart", getTimeOfDayMetrics(r));
+		insertPieChart("authorsChart", getAuthorMetrics(r));
+	}
+
+	private void insertLinePlot(String wicketId, List<Metric> metrics) {
+		if ((metrics != null) && (metrics.size() > 0)) {
+			IChartData data = WicketUtils.getChartData(metrics);
+
+			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);
+
+			ChartAxis commitAxis = new ChartAxis(ChartAxisType.LEFT);
+			commitAxis.setLabels(new String[] { "",
+					String.valueOf((int) WicketUtils.maxValue(metrics)) });
+			provider.addAxis(commitAxis);
+
+			provider.setLineStyles(new LineStyle[] { new LineStyle(2, 4, 0), new LineStyle(0, 4, 1) });
+			provider.addShapeMarker(new ShapeMarker(MarkerType.CIRCLE, Color.BLUE, 1, -1, 5));
+
+			add(new Chart(wicketId, provider));
+		} else {
+			add(WicketUtils.newBlankImage(wicketId));
+		}
+	}
+
+	private void insertBarPlot(String wicketId, List<Metric> metrics) {
+		if ((metrics != null) && (metrics.size() > 0)) {
+			IChartData data = WicketUtils.getChartData(metrics);
+
+			ChartProvider provider = new ChartProvider(new Dimension(400, 100),
+					ChartType.BAR_VERTICAL_SET, data);
+			ChartAxis dateAxis = new ChartAxis(ChartAxisType.BOTTOM);
+			List<String> labels = new ArrayList<String>();
+			for (Metric metric : metrics) {
+				labels.add(metric.name);
+			}
+			dateAxis.setLabels(labels.toArray(new String[labels.size()]));
+			provider.addAxis(dateAxis);
+
+			ChartAxis commitAxis = new ChartAxis(ChartAxisType.LEFT);
+			commitAxis.setLabels(new String[] { "",
+					String.valueOf((int) WicketUtils.maxValue(metrics)) });
+			provider.addAxis(commitAxis);
+
+			add(new Chart(wicketId, provider));
+		} else {
+			add(WicketUtils.newBlankImage(wicketId));
+		}
+	}
+
+	private void insertPieChart(String wicketId, List<Metric> metrics) {
+		if ((metrics != null) && (metrics.size() > 0)) {
+			IChartData data = WicketUtils.getChartData(metrics);
+			List<String> labels = new ArrayList<String>();
+			for (Metric metric : metrics) {
+				labels.add(metric.name);
+			}
+			ChartProvider provider = new ChartProvider(new Dimension(400, 200), ChartType.PIE, data);
+			provider.setPieLabels(labels.toArray(new String[labels.size()]));
+			add(new Chart(wicketId, provider));
+		} else {
+			add(WicketUtils.newBlankImage(wicketId));
+		}
+	}
+
+	private List<Metric> getDayOfWeekMetrics(Repository repository) {
+		List<Metric> list = JGitUtils.getDateMetrics(repository, false, "E");
+		SimpleDateFormat sdf = new SimpleDateFormat("E");
+		Calendar cal = Calendar.getInstance();
+
+		List<Metric> sorted = new ArrayList<Metric>(7);
+		int firstDayOfWeek = cal.getFirstDayOfWeek();
+		int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
+
+		// rewind date to first day of week
+		cal.add(Calendar.DATE, firstDayOfWeek - dayOfWeek);
+		for (int i = 0; i < 7; i++) {
+			String day = sdf.format(cal.getTime());
+			for (Metric metric : list) {
+				if (metric.name.equals(day)) {
+					sorted.add(i, metric);
+					list.remove(metric);
+					break;
+				}
+			}
+			cal.add(Calendar.DATE, 1);
+		}
+		return sorted;
+	}
+
+	private List<Metric> getTimeOfDayMetrics(Repository repository) {
+		SimpleDateFormat ndf = new SimpleDateFormat("yyyy-MM-dd");
+		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
+		List<Metric> list = JGitUtils.getDateMetrics(repository, false, "yyyy-MM-dd HH:mm");
+		Calendar cal = Calendar.getInstance();
+
+		for (Metric metric : list) {
+			try {
+				Date date = sdf.parse(metric.name);
+				cal.setTime(date);
+				double y = cal.get(Calendar.HOUR_OF_DAY) + (cal.get(Calendar.MINUTE) / 60d);
+				metric.duration = (int) (date.getTime() / 60000L);
+				metric.count = y;
+				metric.name = ndf.format(date);
+			} catch (ParseException p) {
+			}
+		}
+		return list;
+	}
+
+	private List<Metric> getAuthorMetrics(Repository repository) {
+		List<Metric> authors = JGitUtils.getAuthorMetrics(repository);
+		Collections.sort(authors, new Comparator<Metric>() {
+			@Override
+			public int compare(Metric o1, Metric o2) {
+				if (o1.count > o2.count) {
+					return -1;
+				} else if (o1.count < o2.count) {
+					return 1;					
+				}
+				return 0;
+			}
+		});
+		if (authors.size() > 10) {
+			return authors.subList(0, 9);
+		}
+		return authors;
+	}
+
+	@Override
+	protected String getPageName() {
+		return getString("gb.stats");
+	}
+}
diff --git a/src/com/gitblit/wicket/pages/SummaryPage.java b/src/com/gitblit/wicket/pages/SummaryPage.java
index 181de0d..1157d30 100644
--- a/src/com/gitblit/wicket/pages/SummaryPage.java
+++ b/src/com/gitblit/wicket/pages/SummaryPage.java
@@ -27,7 +27,6 @@
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.protocol.http.WebRequest;
 import org.eclipse.jgit.lib.Repository;
-import org.wicketstuff.googlecharts.AbstractChartData;
 import org.wicketstuff.googlecharts.Chart;
 import org.wicketstuff.googlecharts.ChartAxis;
 import org.wicketstuff.googlecharts.ChartAxisType;
@@ -73,7 +72,7 @@
 		List<Metric> metrics = null;
 		Metric metricsTotal = null;
 		if (GitBlit.getBoolean(Keys.web.generateActivityGraph, true)) {
-			metrics = JGitUtils.getDateMetrics(r);
+			metrics = JGitUtils.getDateMetrics(r, true);
 			metricsTotal = metrics.remove(0);
 		}
 
@@ -152,7 +151,7 @@
 	private void insertActivityGraph(List<Metric> metrics) {
 		if ((metrics != null) && (metrics.size() > 0)
 				&& GitBlit.getBoolean(Keys.web.generateActivityGraph, true)) {
-			IChartData data = getChartData(metrics);
+			IChartData data = WicketUtils.getChartData(metrics);
 
 			ChartProvider provider = new ChartProvider(new Dimension(400, 100), ChartType.LINE,
 					data);
@@ -162,7 +161,7 @@
 			provider.addAxis(dateAxis);
 
 			ChartAxis commitAxis = new ChartAxis(ChartAxisType.LEFT);
-			commitAxis.setLabels(new String[] { "", String.valueOf((int) maxValue(metrics)) });
+			commitAxis.setLabels(new String[] { "", String.valueOf((int) WicketUtils.maxValue(metrics)) });
 			provider.addAxis(commitAxis);
 
 			provider.setLineStyles(new LineStyle[] { new LineStyle(2, 4, 0), new LineStyle(0, 4, 1) });
@@ -172,48 +171,5 @@
 		} else {
 			add(WicketUtils.newBlankImage("commitsChart"));
 		}
-	}
-
-	protected IChartData getChartData(List<Metric> metrics) {
-		final double[] commits = new double[metrics.size()];
-		final double[] tags = new double[metrics.size()];
-		int i = 0;
-		double max = 0;
-		for (Metric m : metrics) {
-			commits[i] = m.count;
-			if (m.tag > 0) {
-				tags[i] = m.count;
-			} else {
-				tags[i] = -1d;
-			}
-			max = Math.max(max, m.count);
-			i++;
-		}
-		IChartData data = new AbstractChartData(max) {
-			private static final long serialVersionUID = 1L;
-
-			public double[][] getData() {
-				return new double[][] { commits, tags };
-			}
-		};
-		return data;
-	}
-
-	protected String[] getNames(List<Metric> results) {
-		String[] names = new String[results.size()];
-		for (int i = 0; i < results.size(); i++) {
-			names[i] = results.get(i).name;
-		}
-		return names;
-	}
-
-	protected double maxValue(List<Metric> metrics) {
-		double max = Double.MIN_VALUE;
-		for (Metric m : metrics) {
-			if (m.count > max) {
-				max = m.count;
-			}
-		}
-		return max;
 	}
 }
diff --git a/src/com/gitblit/wicket/panels/LogPanel.java b/src/com/gitblit/wicket/panels/LogPanel.java
index c5ccac4..436c24f 100644
--- a/src/com/gitblit/wicket/panels/LogPanel.java
+++ b/src/com/gitblit/wicket/panels/LogPanel.java
@@ -126,7 +126,7 @@
 				item.add(new BookmarkablePageLink<Void>("view", CommitPage.class, WicketUtils
 						.newObjectParameter(repositoryName, entry.getName())));
 				item.add(new BookmarkablePageLink<Void>("diff", CommitDiffPage.class, WicketUtils
-						.newObjectParameter(repositoryName, entry.getName())));
+						.newObjectParameter(repositoryName, entry.getName())).setEnabled(entry.getParentCount() > 0));
 				item.add(new BookmarkablePageLink<Void>("tree", TreePage.class, WicketUtils
 						.newObjectParameter(repositoryName, entry.getName())));
 
diff --git a/src/com/gitblit/wicket/resources/gitblit.css b/src/com/gitblit/wicket/resources/gitblit.css
index 64484d2..c9356c3 100644
--- a/src/com/gitblit/wicket/resources/gitblit.css
+++ b/src/com/gitblit/wicket/resources/gitblit.css
@@ -424,7 +424,7 @@
 
 div.commitLegend {
 	float: right;
-	padding: 0.4em;
+	padding: 0.4em 0.4em 0.2em 0.4em;
 	vertical-align:top;
 	margin: 0px;
 }
diff --git a/tests/com/gitblit/tests/JGitUtilsTest.java b/tests/com/gitblit/tests/JGitUtilsTest.java
index d17ab5d..9007b42 100644
--- a/tests/com/gitblit/tests/JGitUtilsTest.java
+++ b/tests/com/gitblit/tests/JGitUtilsTest.java
@@ -29,6 +29,7 @@
 import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevTree;
 
+import com.gitblit.GitBlit;
 import com.gitblit.models.Metric;
 import com.gitblit.models.PathModel.PathChangeModel;
 import com.gitblit.models.RefModel;
@@ -38,12 +39,12 @@
 
 public class JGitUtilsTest extends TestCase {
 
-	private List<String> getRepositories() {
-		return JGitUtils.getRepositoryList(GitBlitSuite.REPOSITORIES, true, true);
-	}
-
 	public void testFindRepositories() {
-		List<String> list = getRepositories();
+		List<String> list = JGitUtils.getRepositoryList(null, true, true);
+		assertTrue(list.size() == 0);
+		list.addAll(JGitUtils.getRepositoryList(new File("DoesNotExist"), true, true));
+		assertTrue(list.size() == 0);
+		list.addAll(JGitUtils.getRepositoryList(GitBlitSuite.REPOSITORIES, true, true));
 		assertTrue("No repositories found in " + GitBlitSuite.REPOSITORIES, list.size() > 0);
 	}
 
@@ -53,14 +54,9 @@
 		assertTrue("Could not find repository!", repository != null);
 	}
 
-	public void testLastChangeRepository() throws Exception {
-		Repository repository = GitBlitSuite.getHelloworldRepository();
-		Date date = JGitUtils.getLastChange(repository);
-		repository.close();
-		assertTrue("Could not get last repository change date!", date != null);
-	}
-
 	public void testFirstCommit() throws Exception {
+		assertTrue(JGitUtils.getFirstChange(null, null).equals(new Date(0)));
+		
 		Repository repository = GitBlitSuite.getHelloworldRepository();
 		RevCommit commit = JGitUtils.getFirstCommit(repository, null);
 		Date firstChange = JGitUtils.getFirstChange(repository, null);
@@ -69,6 +65,43 @@
 		assertTrue("Incorrect first commit!",
 				commit.getName().equals("f554664a346629dc2b839f7292d06bad2db4aece"));
 		assertTrue(firstChange.equals(new Date(commit.getCommitTime() * 1000L)));
+	}
+	
+	public void testLastCommit() throws Exception {
+		assertTrue(JGitUtils.getLastChange(null).equals(new Date(0)));
+		
+		Repository repository = GitBlitSuite.getHelloworldRepository();
+		assertTrue(JGitUtils.getCommit(repository, null) != null);
+		Date date = JGitUtils.getLastChange(repository);
+		repository.close();
+		assertTrue("Could not get last repository change date!", date != null);
+	}
+
+	
+
+	public void testCreateRepository() throws Exception {
+		String[] repositories = { "NewTestRepository.git", "NewTestRepository" };
+		for (String repositoryName : repositories) {
+			boolean isBare = repositoryName.endsWith(".git");
+			Repository repository = JGitUtils.createRepository(GitBlitSuite.REPOSITORIES,
+					repositoryName, isBare);
+			File folder;
+			if (isBare) {
+				folder = new File(GitBlitSuite.REPOSITORIES, repositoryName);	
+			} else {
+				folder = new File(GitBlitSuite.REPOSITORIES, repositoryName + "/.git");
+			}			
+			assertTrue(repository != null);
+			assertFalse(JGitUtils.hasCommits(repository));
+			assertTrue(JGitUtils.getFirstCommit(repository, null) == null);
+			assertTrue(JGitUtils.getFirstChange(repository, null).getTime() == folder
+					.lastModified());
+			assertTrue(JGitUtils.getLastChange(repository).getTime() == folder
+					.lastModified());
+			assertTrue(JGitUtils.getCommit(repository, null) == null);
+			repository.close();
+			assertTrue(GitBlit.self().deleteRepository(repositoryName));
+		}
 	}
 
 	public void testRefs() throws Exception {
@@ -151,7 +184,7 @@
 
 	public void testMetrics() throws Exception {
 		Repository repository = GitBlitSuite.getHelloworldRepository();
-		List<Metric> metrics = JGitUtils.getDateMetrics(repository);
+		List<Metric> metrics = JGitUtils.getDateMetrics(repository, true);
 		repository.close();
 		assertTrue("No metrics found!", metrics.size() > 0);
 	}

--
Gitblit v1.9.1