James Moger
2013-05-31 9b26b74d198aa4efbe4b25f6667b98eb5261e13d
Refinements to push log display and daily digests

* Properly support timezone-based date groupings
* Polish css for the major browsers on Win & Linux
* Use Gitblit constants for refs instead of JGit constants
24 files modified
390 ■■■■■ changed files
src/main/java/com/gitblit/Constants.java 18 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/Translation.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/DailyLogEntry.java 22 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/PushLogEntry.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/RepositoryModel.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/ActivityUtils.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/PushLogUtils.java 61 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/TimeUtils.java 24 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/GitBlitWebApp.properties 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/WicketUtils.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/BasePage.java 5 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/DashboardPage.html 12 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/DashboardPage.java 94 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/RepositoryPage.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/BasePanel.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/BranchesPanel.html 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/LogPanel.html 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/PushesPanel.html 13 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/PushesPanel.java 62 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/RefsPanel.java 16 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/TagsPanel.html 2 ●●● patch | view | raw | blame | history
src/main/resources/bootstrap/css/iconic.css 4 ●●● patch | view | raw | blame | history
src/main/resources/gitblit.css 21 ●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/TimeUtilsTest.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/Constants.java
@@ -80,13 +80,27 @@
    
    public static final String ISO8601 = "yyyy-MM-dd'T'HH:mm:ssZ";
    
    public static final String R_GITBLIT = "refs/gitblit/";
    public static final String baseFolder = "baseFolder";
    
    public static final String baseFolder$ = "${" + baseFolder + "}";
    
    public static final String contextFolder$ = "${contextFolder}";
    public static final String HEAD = "HEAD";
    public static final String R_GITBLIT = "refs/gitblit/";
    public static final String R_HEADS = "refs/heads/";
    public static final String R_NOTES = "refs/notes/";
    public static final String R_CHANGES = "refs/changes/";
    public static final String R_PULL= "refs/pull/";
    public static final String R_TAGS = "refs/tags/";
    public static final String R_REMOTES = "refs/remotes/";
    public static String getVersion() {
        String v = Constants.class.getPackage().getImplementationVersion();
src/main/java/com/gitblit/client/Translation.java
@@ -43,7 +43,7 @@
        }
        translation = bundle;
        
        timeUtils = new TimeUtils(translation);
        timeUtils = new TimeUtils(translation, null);
    }
    public static String get(String key) {
src/main/java/com/gitblit/models/DailyLogEntry.java
@@ -19,6 +19,7 @@
import java.util.Date;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.transport.ReceiveCommand;
/**
 * Model class to simulate a push for presentation in the push log news feed
@@ -56,4 +57,25 @@
        
        return super.getAuthorIdent();
    }
    /**
     * Tracks the change type for the specified ref.
     *
     * @param ref
     * @param type
     * @param oldId
     * @param newId
     */
    public void updateRef(String ref, ReceiveCommand.Type type, String oldId, String newId) {
        // daily digests are filled from most recent to oldest
        String preservedNewId = getNewId(ref);
        if (preservedNewId == null) {
            // no preserved new id, this is newest commit
            // for this ref
            preservedNewId = newId;
        }
        refUpdates.put(ref, type);
        refIdChanges.put(ref, oldId + "-" + preservedNewId);
    }
}
src/main/java/com/gitblit/models/PushLogEntry.java
@@ -51,9 +51,9 @@
    private final Set<RepositoryCommit> commits;
    
    private final Map<String, ReceiveCommand.Type> refUpdates;
    protected final Map<String, ReceiveCommand.Type> refUpdates;
    
    private final Map<String, String> refIdChanges;
    protected final Map<String, String> refIdChanges;
    
    private int authorCount;
src/main/java/com/gitblit/models/RepositoryModel.java
@@ -186,6 +186,10 @@
        return !accessRestriction.atLeast(AccessRestrictionType.VIEW);
    }
    
    public boolean isShowActivity() {
        return maxActivityCommits > -1;
    }
    public boolean isSparkleshared() {
        return !StringUtils.isEmpty(sparkleshareId);
    }
src/main/java/com/gitblit/utils/ActivityUtils.java
@@ -81,7 +81,7 @@
        Map<String, Activity> activity = new HashMap<String, Activity>();
        for (RepositoryModel model : models) {
            if (model.maxActivityCommits == -1) {
            if (!model.isShowActivity()) {
                // skip this repository
                continue;
            }
src/main/java/com/gitblit/utils/PushLogUtils.java
@@ -20,7 +20,6 @@
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
@@ -28,6 +27,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.TreeSet;
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
@@ -37,7 +37,6 @@
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
@@ -53,6 +52,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.Constants;
import com.gitblit.models.DailyLogEntry;
import com.gitblit.models.PathModel.PathChangeModel;
import com.gitblit.models.PushLogEntry;
@@ -172,7 +172,7 @@
                CommitBuilder commit = new CommitBuilder();
                commit.setAuthor(ident);
                commit.setCommitter(ident);
                commit.setEncoding(Constants.CHARACTER_ENCODING);
                commit.setEncoding(Constants.ENCODING);
                commit.setMessage(message);
                commit.setParentId(headId);
                commit.setTreeId(indexTreeId);
@@ -272,7 +272,7 @@
                dcEntry.setFileMode(FileMode.REGULAR_FILE);
                // insert object
                dcEntry.setObjectId(inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8")));
                dcEntry.setObjectId(inserter.insert(org.eclipse.jgit.lib.Constants.OBJ_BLOB, content.getBytes("UTF-8")));
                // add to temporary in-core index
                dcBuilder.add(dcEntry);
@@ -510,47 +510,71 @@
     * @param offset
     * @param maxCount
     *             if < 0, all pushes are returned.
     * @param the timezone to use when aggregating commits by date
     * @return a list of grouped commit log entries
     */
    public static List<DailyLogEntry> getDailyLog(String repositoryName, Repository repository,
                                                 Date minimumDate, int offset, int maxCount) {
                                                 Date minimumDate, int offset, int maxCount,
                                                 TimeZone timezone) {
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
//        df.setTimeZone(timezone);
        df.setTimeZone(timezone);
        Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(repository);
        Map<String, DailyLogEntry> tags = new HashMap<String, DailyLogEntry>();
        Map<String, DailyLogEntry> pulls = new HashMap<String, DailyLogEntry>();
        Map<String, DailyLogEntry> dailydigests = new HashMap<String, DailyLogEntry>();
        String linearParent = null;
        for (RefModel local : JGitUtils.getLocalBranches(repository, true, -1)) {
            String branch = local.getName();
            List<RevCommit> commits = JGitUtils.getRevLog(repository, branch, minimumDate);
            for (RevCommit commit : commits) {
                if (linearParent != null) {
                    if (!commit.getName().equals(linearParent)) {
                        // only follow linear branch commits
                        continue;
                    }
                }
                Date date = JGitUtils.getCommitDate(commit);
                String dateStr = df.format(date);
                if (!dailydigests.containsKey(dateStr)) {
                    dailydigests.put(dateStr, new DailyLogEntry(repositoryName, date));
                }
                PushLogEntry digest = dailydigests.get(dateStr);
                DailyLogEntry digest = dailydigests.get(dateStr);
                if (commit.getParentCount() == 0) {
                    linearParent = null;
                    digest.updateRef(branch, ReceiveCommand.Type.CREATE);
                } else {
                    digest.updateRef(branch, ReceiveCommand.Type.UPDATE, commit.getParents()[0].getId().getName(), commit.getName());
                    linearParent = commit.getParents()[0].getId().getName();
                    digest.updateRef(branch, ReceiveCommand.Type.UPDATE, linearParent, commit.getName());
                }
                RepositoryCommit repoCommit = digest.addCommit(branch, commit);
                if (repoCommit != null) {
                    repoCommit.setRefs(allRefs.get(commit.getId()));
                    if (!ArrayUtils.isEmpty(repoCommit.getRefs())) {
                        // treat tags as special events in the log
                        for (RefModel ref : repoCommit.getRefs()) {
                    List<RefModel> matchedRefs = allRefs.get(commit.getId());
                    repoCommit.setRefs(matchedRefs);
                    if (!ArrayUtils.isEmpty(matchedRefs)) {
                        for (RefModel ref : matchedRefs) {
                            if (ref.getName().startsWith(Constants.R_TAGS)) {
                                // treat tags as special events in the log
                                if (!tags.containsKey(dateStr)) {
                                    UserModel tagUser = newUserModelFrom(commit.getAuthorIdent());
                                    Date tagDate = commit.getAuthorIdent().getWhen();
                                    tags.put(dateStr, new DailyLogEntry(repositoryName, tagDate, tagUser));
                                    tags.put(dateStr, new DailyLogEntry(repositoryName, tagDate, tagUser));
                                }
                                PushLogEntry tagEntry = tags.get(dateStr);
                                tagEntry.updateRef(ref.getName(), ReceiveCommand.Type.CREATE);
                                tagEntry.addCommits(Arrays.asList(repoCommit));
                                tagEntry.addCommit(ref.getName(), commit);
                            } else if (ref.getName().startsWith(Constants.R_PULL)) {
                                // treat pull requests as special events in the log
                                if (!pulls.containsKey(dateStr)) {
                                    UserModel commitUser = newUserModelFrom(commit.getAuthorIdent());
                                    Date commitDate = commit.getAuthorIdent().getWhen();
                                    pulls.put(dateStr, new DailyLogEntry(repositoryName, commitDate, commitUser));
                                }
                                PushLogEntry pullEntry = pulls.get(dateStr);
                                pullEntry.updateRef(ref.getName(), ReceiveCommand.Type.CREATE);
                                pullEntry.addCommit(ref.getName(), commit);
                            }
                        }
                    }
@@ -560,6 +584,7 @@
        List<DailyLogEntry> list = new ArrayList<DailyLogEntry>(dailydigests.values());
        list.addAll(tags.values());
        //list.addAll(pulls.values());
        Collections.sort(list);
        return list;
    }
@@ -571,12 +596,14 @@
     * @param repositoryName
     * @param repository
     * @param minimumDate
     * @param the timezone to use when aggregating commits by date
     * @return a list of push log entries separated by ref and date
     */
    public static List<DailyLogEntry> getDailyLogByRef(String repositoryName, Repository repository,  Date minimumDate) {
    public static List<DailyLogEntry> getDailyLogByRef(String repositoryName, Repository repository,
            Date minimumDate, TimeZone timezone) {
        // break the push log into ref push logs and then merge them back into a list
        Map<String, List<DailyLogEntry>> refMap = new HashMap<String, List<DailyLogEntry>>();
        List<DailyLogEntry> pushes = getDailyLog(repositoryName, repository, minimumDate, 0, -1);
        List<DailyLogEntry> pushes = getDailyLog(repositoryName, repository, minimumDate, 0, -1, timezone);
        for (DailyLogEntry push : pushes) {
            for (String ref : push.getChangedRefs()) {
                if (!refMap.containsKey(ref)) {
src/main/java/com/gitblit/utils/TimeUtils.java
@@ -20,6 +20,7 @@
import java.util.Calendar;
import java.util.Date;
import java.util.ResourceBundle;
import java.util.TimeZone;
/**
 * Utility class of time functions.
@@ -40,12 +41,15 @@
    
    private final ResourceBundle translation;
    
    private final TimeZone timezone;
    public TimeUtils() {
        this(null);
        this(null, null);
    }
    
    public TimeUtils(ResourceBundle translation) {
    public TimeUtils(ResourceBundle translation, TimeZone timezone) {
        this.translation = translation;
        this.timezone = timezone;
    }
    /**
@@ -54,8 +58,13 @@
     * @param date
     * @return true if date is today
     */
    public static boolean isToday(Date date) {
        return (System.currentTimeMillis() - date.getTime()) < ONEDAY;
    public static boolean isToday(Date date, TimeZone timezone) {
        Date now = new Date();
        SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");
        if (timezone != null) {
            df.setTimeZone(timezone);
        }
        return df.format(now).equals(df.format(date));
    }
    /**
@@ -64,11 +73,14 @@
     * @param date
     * @return true if date is yesterday
     */
    public static boolean isYesterday(Date date) {
    public static boolean isYesterday(Date date, TimeZone timezone) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(new Date());
        cal.add(Calendar.DATE, -1);
        SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");
        if (timezone != null) {
            df.setTimeZone(timezone);
        }
        return df.format(cal.getTime()).equals(df.format(date));
    }
@@ -205,7 +217,7 @@
     * @return the string representation of the duration OR the css class
     */
    private String timeAgo(Date date, boolean css) {
        if (isToday(date) || isYesterday(date)) {
        if (isToday(date, timezone) || isYesterday(date, timezone)) {
            int mins = minutesAgo(date, true);
            if (mins >= 120) {
                if (css) {
src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
@@ -479,6 +479,8 @@
gb.pushedNewBranch = pushed new branch
gb.createdNewBranch = created new branch
gb.deletedBranch = deleted branch
gb.createdNewPullRequest = created pull request
gb.mergedPullRequest = merged pull request
gb.rewind = REWIND
gb.star = star
gb.unstar = unstar
@@ -486,3 +488,5 @@
gb.starredRepositories = starred repositories
gb.failedToUpdateUser = Failed to update user account!
gb.myRepositories = my repositories
gb.noActivity = there has been no recent commit activity
gb.findSomeRepositories = find some repositories
src/main/java/com/gitblit/wicket/WicketUtils.java
@@ -520,9 +520,9 @@
            dateString = df.format(date);
        }
        String title = null;
        if (TimeUtils.isToday(date)) {
        if (TimeUtils.isToday(date, timeZone)) {
            title = timeUtils.today();
        } else if (TimeUtils.isYesterday(date)) {
        } else if (TimeUtils.isYesterday(date, timeZone)) {
                title = timeUtils.yesterday();
        } else if (date.getTime() <= System.currentTimeMillis()) {
            // past
src/main/java/com/gitblit/wicket/pages/BasePage.java
@@ -100,7 +100,7 @@
            } catch (Throwable t) {
                bundle = ResourceBundle.getBundle("com.gitblit.wicket.GitBlitWebApp");
            }
            timeUtils = new TimeUtils(bundle);
            timeUtils = new TimeUtils(bundle, getTimeZone());
        }
        return timeUtils;
    }
@@ -125,6 +125,9 @@
    protected void setupPage(String repositoryName, String pageName) {
        String siteName = GitBlit.getString(Keys.web.siteName, Constants.NAME);
        if (StringUtils.isEmpty(siteName)) {
            siteName = Constants.NAME;
        }
        if (repositoryName != null && repositoryName.trim().length() > 0) {
            add(new Label("title", repositoryName + " - " + siteName));
        } else {
src/main/java/com/gitblit/wicket/pages/DashboardPage.html
@@ -19,7 +19,7 @@
                    </tr>
                </table>
            </div>
            <div wicket:id="pushes"></div>
            <div wicket:id="digests"></div>
        </div>
        <div class="span5">
            <div wicket:id="active">[active]</div>
@@ -44,9 +44,9 @@
        <div ng-repeat="item in starred | limitTo: 20 | filter:query" style="padding: 3px;border-top: 1px solid #ddd;">
            <b><span class="repositorySwatch" style="background-color:{{item.c}};"><span ng-show="item.wc">!</span><span ng-show="!item.wc">&nbsp;</span></span></b>
            <a href="summary/?r={{item.r}}">{{item.p}}<b>{{item.n}}</b></a>
            <span class="link hidden-tablet hidden-phone" style="color: #bbb;" title="{{item.d}}">{{item.t}}</span>
            <span class="link hidden-tablet hidden-phone" style="color: #aaa;" title="{{item.d}}">{{item.t}}</span>
            <span ng-show="item.s" class="pull-right">
                <span style="padding: 0px 5px;color: #888;font-weight:bold;vertical-align:middle;">{{item.s | number}} <i class="iconic-star"></i></span>
                <span style="padding: 0px 5px;color: #888;font-weight:bold;vertical-align:middle;">{{item.s | number}} <i style="vertical-align:baseline;" class="iconic-star"></i></span>
            </span>
        </div>
        
@@ -69,14 +69,14 @@
            <a href="summary/?r={{item.r}}">{{item.p}}<b>{{item.n}}</b></a>
            <span class="link hidden-tablet hidden-phone" style="color: #bbb;" title="{{item.d}}">{{item.t}}</span>
            <span ng-show="item.s" class="pull-right">
                <span style="padding: 0px 5px;color: #888;font-weight:bold;vertical-align:middle;">{{item.s | number}} <i class="iconic-star"></i></span>
                <span style="padding: 0px 5px;color: #888;font-weight:bold;vertical-align:middle;">{{item.s | number}} <i style="vertical-align:baseline;" class="iconic-star"></i></span>
            </span>
        </div>        
    </div>
</wicket:fragment>
<wicket:fragment wicket:id="activeListFragment">
    <div ng-controller="activeCtrl" style="border: 1px solid #ddd;border-radius: 4px;">
    <div ng-controller="activeCtrl" style="border: 1px solid #ddd;border-radius: 4px;margin-bottom: 20px;">
        <div class="header" style="padding: 5px;border: none;"><i class="icon-user"></i> <wicket:message key="gb.activeRepositories"></wicket:message> ({{active.length}})
            <div style="padding: 5px 0px 0px;">
                <input type="text" ng-model="query.r" class="input-large" wicket:message="placeholder:gb.filter" style="border-radius: 14px; padding: 3px 14px;margin: 0px;"></input>
@@ -88,7 +88,7 @@
            <a href="summary/?r={{item.r}}">{{item.p}}<b>{{item.n}}</b></a>
            <span class="link hidden-tablet hidden-phone" style="color: #bbb;" title="{{item.d}}">{{item.t}}</span>
            <span ng-show="item.s" class="pull-right">
                <span style="padding: 0px 5px;color: #888;font-weight:bold;vertical-align:middle;">{{item.s | number}} <i class="iconic-star"></i></span>
                <span style="padding: 0px 5px;color: #888;font-weight:bold;vertical-align:middle;">{{item.s | number}} <i style="vertical-align:baseline;" class="iconic-star"></i></span>
            </span>
        </div>        
    </div>
src/main/java/com/gitblit/wicket/pages/DashboardPage.java
@@ -29,8 +29,11 @@
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import org.apache.wicket.Component;
import org.apache.wicket.PageParameters;
@@ -103,6 +106,9 @@
        add(repositoriesMessage);
        UserModel user = GitBlitWebSession.get().getUser();
        if (user == null) {
            user = UserModel.ANONYMOUS;
        }
        Comparator<RepositoryModel> lastUpdateSort = new Comparator<RepositoryModel>() {
            @Override
@@ -111,33 +117,6 @@
            }
        };
        
        Map<String, RepositoryModel> reposMap = new HashMap<String, RepositoryModel>();
        // owned repositories
        List<RepositoryModel> owned = new ArrayList<RepositoryModel>();
        if (user != null && !UserModel.ANONYMOUS.equals(user)) {
            for (RepositoryModel model : GitBlit.self().getRepositoryModels(user)) {
                reposMap.put(model.name, model);
                if (model.isUsersPersonalRepository(user.username) || model.isOwner(user.username)) {
                    owned.add(model);
                }
            }
        }
        Collections.sort(owned, lastUpdateSort);
        // starred repositories
        List<RepositoryModel> starred = new ArrayList<RepositoryModel>();
        if (user != null && !UserModel.ANONYMOUS.equals(user)) {
            for (String name : user.getPreferences().getStarredRepositories()) {
                if (!reposMap.containsKey(name)) {
                    RepositoryModel repo = GitBlit.self().getRepositoryModel(name);
                    reposMap.put(name, repo);
                }
                starred.add(reposMap.get(name));
            }
        }
        Collections.sort(starred, lastUpdateSort);
        // parameters
        int daysBack = params == null ? 0 : WicketUtils.getDaysBack(params);
        if (daysBack < 1) {
@@ -146,38 +125,61 @@
        Calendar c = Calendar.getInstance();
        c.add(Calendar.DATE, -1*daysBack);
        Date minimumDate = c.getTime();
        TimeZone timezone = getTimeZone();
        
        // active repositories (displayed for anonymous users)
        // build repo lists
        List<RepositoryModel> starred = new ArrayList<RepositoryModel>();
        List<RepositoryModel> owned = new ArrayList<RepositoryModel>();
        List<RepositoryModel> active = new ArrayList<RepositoryModel>();
        if (user == null || UserModel.ANONYMOUS.equals(user)) {
            List<RepositoryModel> list = GitBlit.self().getRepositoryModels(UserModel.ANONYMOUS);
            for (RepositoryModel model : list) {
                if (model.lastChange.after(minimumDate)) {
                    active.add(model);
                    reposMap.put(model.name, model);
                }
        for (RepositoryModel model : GitBlit.self().getRepositoryModels(user)) {
            if (model.isUsersPersonalRepository(user.username) || model.isOwner(user.username)) {
                owned.add(model);
            }
            Collections.sort(active, lastUpdateSort);
            if (user.getPreferences().isStarredRepository(model.name)) {
                starred.add(model);
            }
            if (model.isShowActivity() && model.lastChange.after(minimumDate)) {
                active.add(model);
            }
        }
        
        // show pushlog feed
        Collections.sort(owned, lastUpdateSort);
        Collections.sort(starred, lastUpdateSort);
        Collections.sort(active, lastUpdateSort);
        Set<RepositoryModel> feedSources = new HashSet<RepositoryModel>();
        feedSources.addAll(starred);
        if (feedSources.isEmpty()) {
            feedSources.addAll(active);
        }
        // create daily commit digest feed
        List<PushLogEntry> pushes = new ArrayList<PushLogEntry>();
        for (RepositoryModel model : reposMap.values()) {
        for (RepositoryModel model : feedSources) {
            Repository repository = GitBlit.self().getRepository(model.name);
            List<DailyLogEntry> entries = PushLogUtils.getDailyLogByRef(model.name, repository, minimumDate);
            List<DailyLogEntry> entries = PushLogUtils.getDailyLogByRef(model.name, repository, minimumDate, timezone);
            pushes.addAll(entries);
            repository.close();
        }
        
        if (pushes.size() == 0) {
            if (reposMap.size() == 0) {
                add(new LinkPanel("pushes", null, "find some repositories", RepositoriesPage.class));
            // quiet or no starred repositories
            if (feedSources.size() == 0) {
                if (UserModel.ANONYMOUS.equals(user)) {
                    add(new Label("digests", getString("gb.noActivity")));
                } else {
                    add(new LinkPanel("digests", null, getString("gb.findSomeRepositories"), RepositoriesPage.class));
                }
            } else {
                add(new Label("pushes", "all is quiet"));
                add(new Label("digests", getString("gb.noActivity")));
            }
        } else {
            // show daily commit digest feed
            Collections.sort(pushes);
            add(new PushesPanel("pushes", pushes));
            add(new PushesPanel("digests", pushes));
        }
        
        // add the nifty charts
@@ -187,11 +189,11 @@
        }
        
        // active repository list
        if (ArrayUtils.isEmpty(active)) {
            add(new Label("active").setVisible(false));
        } else {
        if (starred.isEmpty()) {
            Fragment activeView = createNgList("active", "activeListFragment", "activeCtrl", active);
            add(activeView);
        } else {
            add(new Label("active").setVisible(false));
        }
        
        // starred repository list
src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
@@ -286,8 +286,8 @@
            }
        }
        
        // (un)star link allows a user to star someone else's repository
        if (user.isAuthenticated && !model.isOwner(user.username) && !model.isUsersPersonalRepository(user.username)) {
        // (un)star link allows a user to star a repository
        if (user.isAuthenticated) {
            PageParameters starParams = DeepCopier.copy(getPageParameters());
            starParams.put(PARAM_STAR, !user.getPreferences().isStarredRepository(model.name));
            String toggleStarUrl = getRequestCycle().urlFor(getClass(), starParams).toString();
@@ -301,7 +301,7 @@
                add(new Label("unstarLink").setVisible(false));
            }
        } else {
            // anonymous user or the repository owner is browsing the repository
            // anonymous user
            add(new Label("starLink").setVisible(false));
            add(new Label("unstarLink").setVisible(false));
        }
src/main/java/com/gitblit/wicket/panels/BasePanel.java
@@ -53,7 +53,7 @@
            } catch (Throwable t) {
                bundle = ResourceBundle.getBundle("com.gitblit.wicket.GitBlitWebApp");
            }
            timeUtils = new TimeUtils(bundle);
            timeUtils = new TimeUtils(bundle, getTimeZone());
        }
        return timeUtils;
    }
src/main/java/com/gitblit/wicket/panels/BranchesPanel.html
@@ -8,7 +8,7 @@
<wicket:panel>
    <!-- header -->
    <div class="header"><i class="icon-random" style="vertical-align: middle;"></i> <b><span wicket:id="branches">[branches header]</span></b></div>
    <div class="header"><i class="icon-random"></i> <b><span wicket:id="branches">[branches header]</span></b></div>
    
    <table class="pretty">
        <tbody>
src/main/java/com/gitblit/wicket/panels/LogPanel.html
@@ -8,7 +8,7 @@
<wicket:panel>
    <!-- header -->    
    <div class="header"><i class="icon-refresh" style="vertical-align: middle;"></i> <b><span wicket:id="header">[log header]</span></b></div>
    <div class="header"><i class="icon-refresh"></i> <b><span wicket:id="header">[log header]</span></b></div>
    <table class="pretty">
        <tbody>
               <tr wicket:id="commit">
src/main/java/com/gitblit/wicket/panels/PushesPanel.html
@@ -9,13 +9,18 @@
<div wicket:id="push" class="push">
    <table style="padding: 3px 0px;">
    <tr>
        <td class="hidden-phone" style="vertical-align: top;padding-top:10px"><i wicket:id="pushIcon"></i></td>
        <td style="padding-left: 7px;">
        <td class="icon hidden-phone"><i wicket:id="pushIcon"></i></td>
        <td style="padding-left: 7px;vertical-align:middle;">
            <div>
                <span wicket:id="whenPushed"></span> <span wicket:id="refRewind" class="alert alert-error" style="padding: 1px 5px;font-size: 10px;font-weight: bold;margin-left: 10px;">[rewind]</span>
                <span style="color:#aaa;" wicket:id="whenPushed"></span> <span wicket:id="refRewind" class="alert alert-error" style="padding: 1px 5px;font-size: 10px;font-weight: bold;margin-left: 10px;">[rewind]</span>
            </div>
            <div style="font-weight:bold;"><span wicket:id="whoPushed">[pusher]</span> <span wicket:id="whatPushed"></span><span wicket:id="refPushed"></span> <span wicket:id="repoPreposition"></span> <span wicket:id="repoPushed"></span> <span wicket:id="byAuthors"></span></div>
            <div style="padding: 10px 0px 5px;">
        </td>
    </tr>
    <tr>
        <td></td>
        <td style="padding-left: 7px;">
            <div>
                <table>
                    <tr wicket:id="commit">
                        <td class="hidden-phone hidden-tablet" style="vertical-align:top;padding-left:7px;"><span wicket:id="commitAuthor"></span></td>
src/main/java/com/gitblit/wicket/panels/PushesPanel.java
@@ -15,9 +15,14 @@
 */
package com.gitblit.wicket.panels;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.repeater.Item;
@@ -35,6 +40,7 @@
import com.gitblit.models.RepositoryModel;
import com.gitblit.utils.PushLogUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.pages.CommitPage;
import com.gitblit.wicket.pages.ComparePage;
@@ -107,6 +113,12 @@
    protected void setup(List<PushLogEntry> pushes, final boolean showRepo) {
        final int hashLen = GitBlit.getInteger(Keys.web.shortCommitIdLength, 6);
        String dateFormat = GitBlit.getString(Keys.web.datestampLongFormat, "EEEE, MMMM d, yyyy");
        final TimeZone timezone = getTimeZone();
        final DateFormat df = new SimpleDateFormat(dateFormat);
        df.setTimeZone(timezone);
        final Calendar cal = Calendar.getInstance(timezone);
        ListDataProvider<PushLogEntry> dp = new ListDataProvider<PushLogEntry>(pushes);
        DataView<PushLogEntry> pushView = new DataView<PushLogEntry>("push", dp) {
            private static final long serialVersionUID = 1L;
@@ -116,15 +128,41 @@
                String fullRefName = push.getChangedRefs().get(0);
                String shortRefName = fullRefName;
                boolean isTag = false;
                if (shortRefName.startsWith(org.eclipse.jgit.lib.Constants.R_HEADS)) {
                    shortRefName = shortRefName.substring(org.eclipse.jgit.lib.Constants.R_HEADS.length());
                } else if (shortRefName.startsWith(org.eclipse.jgit.lib.Constants.R_TAGS)) {
                    shortRefName = shortRefName.substring(org.eclipse.jgit.lib.Constants.R_TAGS.length());
                boolean isPull = false;
                if (shortRefName.startsWith(Constants.R_HEADS)) {
                    shortRefName = shortRefName.substring(Constants.R_HEADS.length());
                } else if (shortRefName.startsWith(Constants.R_TAGS)) {
                    shortRefName = shortRefName.substring(Constants.R_TAGS.length());
                    isTag = true;
                } else if (shortRefName.startsWith(Constants.R_PULL)) {
                    shortRefName = "#" + shortRefName.substring(Constants.R_PULL.length());
                    if (shortRefName.endsWith("/head")) {
                        // strip pull request head from name
                        shortRefName = shortRefName.substring(0, shortRefName.length() - "/head".length());
                    }
                    isPull = true;
                }
                boolean isDigest = push instanceof DailyLogEntry;
                
                pushItem.add(WicketUtils.createDateLabel("whenPushed", push.date, getTimeZone(), getTimeUtils()));
                String fuzzydate;
                TimeUtils tu = getTimeUtils();
                Date pushDate = push.date;
                if (TimeUtils.isToday(pushDate, timezone)) {
                    fuzzydate = tu.today();
                } else if (TimeUtils.isYesterday(pushDate, timezone)) {
                    fuzzydate = tu.yesterday();
                } else {
                    // calculate a fuzzy time ago date
                    cal.setTime(pushDate);
                    cal.set(Calendar.HOUR_OF_DAY, 0);
                    cal.set(Calendar.MINUTE, 0);
                    cal.set(Calendar.SECOND, 0);
                    cal.set(Calendar.MILLISECOND, 0);
                    pushDate = cal.getTime();
                    fuzzydate = getTimeUtils().timeAgo(pushDate);
                }
                pushItem.add(new Label("whenPushed", fuzzydate + ", " + df.format(pushDate)));
                Label pushIcon = new Label("pushIcon");
                if (showRepo) {
                    // if we are showing the repo, we are showing multiple
@@ -135,6 +173,8 @@
                }
                if (isTag) {
                    WicketUtils.setCssClass(pushIcon, "iconic-tag");
                } else if (isPull) {
                    WicketUtils.setCssClass(pushIcon, "iconic-share");
                } else if (isDigest) {
                    WicketUtils.setCssClass(pushIcon, "iconic-loop");
                } else {
@@ -163,6 +203,7 @@
                switch(push.getChangeType(fullRefName)) {
                case CREATE:
                    if (isTag) {
                        // new tag
                        if (isDigest) {
                            what = getString("gb.createdNewTag");
                            preposition = "gb.in";
@@ -170,7 +211,12 @@
                            what = getString("gb.pushedNewTag");
                            preposition = "gb.to";
                        }
                    } else if (isPull) {
                        // merged pull request
                        what = getString("gb.mergedPullRequest");
                        preposition = "gb.in";
                    } else {
                        // new branch
                        if (isDigest) {
                            what = getString("gb.createdNewBranch");
                            preposition = "gb.in";
@@ -183,6 +229,8 @@
                case DELETE:
                    isDelete = true;
                    if (isTag) {
                        what = getString("gb.deletedTag");
                    } if (isPull) {
                        what = getString("gb.deletedTag");
                    } else {
                        what = getString("gb.deletedBranch");
@@ -217,6 +265,10 @@
                    // link to tag
                    pushItem.add(new LinkPanel("refPushed", null, shortRefName,
                            TagPage.class, WicketUtils.newObjectParameter(push.repository, fullRefName)));
                } else if (isPull) {
                    // link to pull request
                    pushItem.add(new LinkPanel("refPushed", null, shortRefName,
                            TagPage.class, WicketUtils.newObjectParameter(push.repository, fullRefName)));
                } else {
                    // link to tree
                    pushItem.add(new LinkPanel("refPushed", null, shortRefName,
src/main/java/com/gitblit/wicket/panels/RefsPanel.java
@@ -28,10 +28,10 @@
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.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevCommit;
import com.gitblit.Constants;
import com.gitblit.models.RefModel;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.pages.CommitPage;
@@ -42,10 +42,6 @@
    private static final long serialVersionUID = 1L;
    
    private static final String R_CHANGES = "refs/changes/";
    private static final String R_PULL= "refs/pull/";
    public RefsPanel(String id, final String repositoryName, RevCommit c,
            Map<ObjectId, List<RefModel>> refs) {
        this(id, repositoryName, refs.get(c.getId()));
@@ -112,17 +108,17 @@
                    // local head
                    linkClass = LogPage.class;
                    cssClass = "headRef";
                } else if (name.startsWith(R_CHANGES)) {
                } else if (name.startsWith(Constants.R_CHANGES)) {
                    // Gerrit change ref
                    name = name.substring(R_CHANGES.length());
                    name = name.substring(Constants.R_CHANGES.length());
                    cssClass = "otherRef";
                } else if (name.startsWith(R_PULL)) {
                } else if (name.startsWith(Constants.R_PULL)) {
                    // Pull Request ref
                    name = "pull #" + name.substring(R_PULL.length());
                    name = "pull #" + name.substring(Constants.R_PULL.length());
                    if (name.endsWith("/head")) {
                        // strip pull request head from name 
                        name = name.substring(0, name.length() - "/head".length());
                    }
                    }
                    cssClass = "pullRef";
                } else if (name.startsWith(Constants.R_REMOTES)) {
                    // remote branch
src/main/java/com/gitblit/wicket/panels/TagsPanel.html
@@ -8,7 +8,7 @@
<wicket:panel>
    <!-- tags -->
    <div class="header"><i class="icon-tags" style="vertical-align: middle;"></i> <b><span wicket:id="header">[tags header]</span></b></div>
    <div class="header"><i class="icon-tags"></i> <b><span wicket:id="header">[tags header]</span></b></div>
    <table class="pretty">
        <tbody>
            <tr wicket:id="tag">
src/main/resources/bootstrap/css/iconic.css
@@ -22,15 +22,13 @@
  [class*="iconic-"] {
    font-style: inherit;
    font-weight: normal;
    vertical-align: bottom;
    vertical-align: middle;
  }
  [class*="iconic-"]:before {
    display: inline-block;
    width: 1em;
    font-family: IconicFill;
    font-size: 0.9em;
    text-align: center;
    vertical-align: middle;
    content: "";
  }
  .iconic-stroke:before {
src/main/resources/gitblit.css
@@ -29,7 +29,12 @@
    outline: none;
}
[class^="icon-"], [class*=" icon-"] a i {
a.btn i {
    /* override for a links that look like bootstrap buttons */
    vertical-align: text-bottom;
}
[class^="icon-"], [class*=" icon-"] i {
    /* override for a links that look like bootstrap buttons */
    vertical-align: text-bottom;
}
@@ -126,11 +131,19 @@
div.push {
    border-bottom: 1px solid #ddd;
    margin-bottom: 15px;
    margin-bottom: 5px;
    padding-bottom: 5px;
}
div.push .icon {
    font-size: 42px;
    line-height: 42px;
}
div.push i {
    font-size:3.25em;
    color:#bbb;
    font-size: 42px;
    color: #bbb;
    vertical-align: middle;
}
.repositorynavbar {
src/test/java/com/gitblit/tests/TimeUtilsTest.java
@@ -43,12 +43,12 @@
    @Test
    public void testToday() throws Exception {
        assertTrue(TimeUtils.isToday(new Date()));
        assertTrue(TimeUtils.isToday(new Date(), null));
    }
    @Test
    public void testYesterday() throws Exception {
        assertTrue(TimeUtils.isYesterday(offset(TimeUtils.ONEDAY)));
        assertTrue(TimeUtils.isYesterday(offset(TimeUtils.ONEDAY), null));
    }
    @Test