James Moger
2011-06-01 f1720ca884bc3fa9da1288ad955e46f165aa4168
Unit testing. Disable links on first commit. Initial stats page.
2 files added
16 files modified
570 ■■■■ changed files
.gitignore 1 ●●●● patch | view | raw | blame | history
distrib/users.properties 3 ●●●● patch | view | raw | blame | history
src/com/gitblit/utils/ByteFormat.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/utils/JGitUtils.java 153 ●●●● patch | view | raw | blame | history
src/com/gitblit/utils/TimeUtils.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/GitBlitWebApp.java 4 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/WicketUtils.java 65 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/CommitDiffPage.html 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/CommitDiffPage.java 3 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/CommitPage.java 5 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/RepositoryPage.html 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/RepositoryPage.java 3 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/StatsPage.html 23 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/StatsPage.java 189 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/SummaryPage.java 50 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/LogPanel.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/resources/gitblit.css 2 ●●● patch | view | raw | blame | history
tests/com/gitblit/tests/JGitUtilsTest.java 59 ●●●● patch | view | raw | blame | history
.gitignore
@@ -8,3 +8,4 @@
/users.properties
/site
/git
/target
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
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) {
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;
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;
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");
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;
    }
}
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>
    
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++;
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++;
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>
        
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>();
src/com/gitblit/wicket/pages/StatsPage.html
New file
@@ -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>
src/com/gitblit/wicket/pages/StatsPage.java
New file
@@ -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");
    }
}
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;
    }
}
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())));
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;
}
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);
    }