James Moger
2011-07-22 a2709dd91e5d6b18f573016882ccc70799573ad3
Centralize default branch/HEAD resolution (issue-14)

If an object id was not specified Gitblit used HEAD to perform the
operation. This breaks under some conditions like working with a
repository that does not have any commits on master but does have
commits on a vcs-import branch.

The new approach is to centralize the resolution of unspecified object
ids to a common method which resolves HEAD first but uses the most
recently modified branch if HEAD points to nothing.

This commit also includes a non-functional method for creating an empty
branch. I couldn't figure out how to make JGit create an orphaned
branch.
19 files modified
439 ■■■■ changed files
distrib/users.properties 4 ●●●● patch | view | raw | blame | history
docs/00_index.mkd 1 ●●●● patch | view | raw | blame | history
docs/04_releases.mkd 1 ●●●● patch | view | raw | blame | history
src/com/gitblit/GitBlitServer.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/utils/DiffUtils.java 33 ●●●●● patch | view | raw | blame | history
src/com/gitblit/utils/FileUtils.java 22 ●●●●● patch | view | raw | blame | history
src/com/gitblit/utils/JGitUtils.java 195 ●●●● patch | view | raw | blame | history
src/com/gitblit/utils/MetricUtils.java 60 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/WicketUtils.java 25 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/CommitDiffPage.java 24 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/CommitPage.java 27 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/DocsPage.java 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/RepositoryPage.java 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/TicketsPage.java 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/TreePage.java 9 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/BranchesPanel.java 5 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/RepositoriesPanel.java 7 ●●●● patch | view | raw | blame | history
tests/com/gitblit/tests/GitBlitTest.java 3 ●●●● patch | view | raw | blame | history
tests/com/gitblit/tests/JGitUtilsTest.java 9 ●●●●● patch | view | raw | blame | history
distrib/users.properties
@@ -1,3 +1,3 @@
## Git:Blit realm file format: username=password,\#permission,repository1,repository2...
#Tue Jun 07 20:57:42 EDT 2011
## Gitblit realm file format: username=password,\#permission,repository1,repository2...
#Fri Jul 22 14:27:08 EDT 2011
admin=admin,\#admin
docs/00_index.mkd
@@ -23,6 +23,7 @@
**%VERSION%** ([go](http://code.google.com/p/gitblit/downloads/detail?name=%GO%)|[war](http://code.google.com/p/gitblit/downloads/detail?name=%WAR%)) based on [%JGIT%][jgit]   *released %BUILDDATE%*
- fixed: repositories with a HEAD that pointed to an empty branch caused internal errors (issue 14)
- fixed: bare-cloned repositories were listed as (empty) and were not clickable (issue 13)
- fixed: default port for Gitblit GO is now 8443 to be more linux/os x friendly (issue 12)
- fixed: forward-slashes ('/', %2F) can be encoded using a custom character to workaround some servlet container default security measures for proxy servers (issue 11)<br/>**New:** *web.forwardSlashCharacter = /*
docs/04_releases.mkd
@@ -3,6 +3,7 @@
### Current Release
**%VERSION%** ([go](http://code.google.com/p/gitblit/downloads/detail?name=%GO%)|[war](http://code.google.com/p/gitblit/downloads/detail?name=%WAR%)) based on [%JGIT%][jgit] &nbsp; *released %BUILDDATE%*
- fixed: repositories with a HEAD that pointed to an empty branch caused internal errors (issue 14)
- fixed: bare-cloned repositories were listed as (empty) and were not clickable (issue 13)
- fixed: default port for Gitblit GO is now 8443 to be more linux/os x friendly (issue 12)
- fixed: forward-slashes ('/', %2F) can be encoded using a custom character to workaround some servlet container default security measures for proxy servers (issue 11)<br/>**New:** *web.forwardSlashCharacter = /*
src/com/gitblit/GitBlitServer.java
@@ -65,7 +65,7 @@
    private static Logger logger;
    public static void main(String[] args) {
    public static void main(String... args) {
        Params params = new Params();
        JCommander jc = new JCommander(params);
        try {
src/com/gitblit/utils/DiffUtils.java
@@ -25,7 +25,7 @@
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.diff.RawTextComparator;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
@@ -147,10 +147,15 @@
            RevTree commitTree = commit.getTree();
            RevTree baseTree;
            if (baseCommit == null) {
                final RevWalk rw = new RevWalk(repository);
                RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
                rw.dispose();
                baseTree = parent.getTree();
                if (commit.getParentCount() > 0) {
                    final RevWalk rw = new RevWalk(repository);
                    RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
                    rw.dispose();
                    baseTree = parent.getTree();
                } else {
                    // FIXME initial commit. no parent?!
                    baseTree = commitTree;
                }
            } else {
                baseTree = baseCommit.getTree();
            }
@@ -208,9 +213,14 @@
            RevTree commitTree = commit.getTree();
            RevTree baseTree;
            if (baseCommit == null) {
                final RevWalk rw = new RevWalk(repository);
                RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
                baseTree = parent.getTree();
                if (commit.getParentCount() > 0) {
                    final RevWalk rw = new RevWalk(repository);
                    RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
                    baseTree = parent.getTree();
                } else {
                    // FIXME initial commit. no parent?!
                    baseTree = commitTree;
                }
            } else {
                baseTree = baseCommit.getTree();
            }
@@ -246,12 +256,15 @@
    public static List<AnnotatedLine> blame(Repository repository, String blobPath, String objectId) {
        List<AnnotatedLine> lines = new ArrayList<AnnotatedLine>();
        try {
            ObjectId object;
            if (StringUtils.isEmpty(objectId)) {
                objectId = Constants.HEAD;
                object = JGitUtils.getDefaultBranch(repository);
            } else {
                object = repository.resolve(objectId);
            }
            BlameCommand blameCommand = new BlameCommand(repository);
            blameCommand.setFilePath(blobPath);
            blameCommand.setStartCommit(repository.resolve(objectId));
            blameCommand.setStartCommit(object);
            BlameResult blameResult = blameCommand.call();
            RawText rawText = blameResult.getResultContents();
            int length = rawText.size();
src/com/gitblit/utils/FileUtils.java
@@ -16,9 +16,12 @@
package com.gitblit.utils;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
/**
@@ -58,6 +61,25 @@
    }
    /**
     * Writes the string content to the file.
     *
     * @param file
     * @param content
     */
    public static void writeContent(File file, String content) {
        try {
            OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file),
                    Charset.forName("UTF-8"));
            BufferedWriter writer = new BufferedWriter(os);
            writer.append(content);
            writer.close();
        } catch (Throwable t) {
            System.err.println("Failed to write content of " + file.getAbsolutePath());
            t.printStackTrace();
        }
    }
    /**
     * Recursively traverses a folder and its subfolders to calculate the total
     * size in bytes.
     * 
src/com/gitblit/utils/JGitUtils.java
@@ -21,6 +21,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -89,6 +90,29 @@
public class JGitUtils {
    static final Logger LOGGER = LoggerFactory.getLogger(JGitUtils.class);
    /**
     * Log an error message and exception.
     *
     * @param t
     * @param repository
     *            if repository is not null it MUST be the {0} parameter in the
     *            pattern.
     * @param pattern
     * @param objects
     */
    private static void error(Throwable t, Repository repository, String pattern, Object... objects) {
        List<Object> parameters = new ArrayList<Object>();
        if (objects != null && objects.length > 0) {
            for (Object o : objects) {
                parameters.add(o);
            }
        }
        if (repository != null) {
            parameters.add(0, repository.getDirectory().getAbsolutePath());
        }
        LOGGER.error(MessageFormat.format(pattern, parameters.toArray()), t);
    }
    /**
     * Returns the displayable name of the person in the form "Real Name <email
@@ -263,19 +287,24 @@
        if (!hasCommits(repository)) {
            return null;
        }
        if (StringUtils.isEmpty(branch)) {
            branch = Constants.HEAD;
        }
        RevCommit commit = null;
        try {
            // resolve branch
            ObjectId branchObject;
            if (StringUtils.isEmpty(branch)) {
                branchObject = getDefaultBranch(repository);
            } else {
                branchObject = repository.resolve(branch);
            }
            RevWalk walk = new RevWalk(repository);
            walk.sort(RevSort.REVERSE);
            RevCommit head = walk.parseCommit(repository.resolve(branch));
            RevCommit head = walk.parseCommit(branchObject);
            walk.markStart(head);
            commit = walk.next();
            walk.dispose();
        } catch (Throwable t) {
            LOGGER.error("Failed to determine first commit", t);
            error(t, repository, "{0} failed to determine first commit");
        }
        return commit;
    }
@@ -310,7 +339,7 @@
     * @return true if the repository has commits
     */
    public static boolean hasCommits(Repository repository) {
        if (repository != null && repository.getDirectory().exists()) {
        if (repository != null && repository.getDirectory().exists()) {
            return (new File(repository.getDirectory(), "objects").list().length > 2)
                    || (new File(repository.getDirectory(), "objects/pack").list().length > 0);
        }
@@ -324,7 +353,7 @@
     * 
     * @param repository
     * @param branch
     *            if unspecified, HEAD is assumed.
     *            if unspecified, all branches are checked.
     * @return
     */
    public static Date getLastChange(Repository repository, String branch) {
@@ -337,8 +366,23 @@
            return new Date(repository.getDirectory().lastModified());
        }
        if (StringUtils.isEmpty(branch)) {
            branch = Constants.HEAD;
            List<RefModel> branchModels = getLocalBranches(repository, true, -1);
            if (branchModels.size() > 0) {
                // find most recent branch update
                Date lastChange = new Date(0);
                for (RefModel branchModel : branchModels) {
                    if (branchModel.getDate().after(lastChange)) {
                        lastChange = branchModel.getDate();
                    }
                }
                return lastChange;
            } else {
                // try to find head
                branch = Constants.HEAD;
            }
        }
        // lookup specified branch
        RevCommit commit = getCommit(repository, branch);
        return getCommitDate(commit);
    }
@@ -347,9 +391,12 @@
     * Retrieves a Java Date from a Git commit.
     * 
     * @param commit
     * @return date of the commit
     * @return date of the commit or Date(0) if the commit is null
     */
    public static Date getCommitDate(RevCommit commit) {
        if (commit == null) {
            return new Date(0);
        }
        return new Date(commit.getCommitTime() * 1000L);
    }
@@ -368,16 +415,19 @@
        }
        RevCommit commit = null;
        try {
            // resolve object id
            ObjectId branchObject;
            if (StringUtils.isEmpty(objectId)) {
                objectId = Constants.HEAD;
                branchObject = getDefaultBranch(repository);
            } else {
                branchObject = repository.resolve(objectId);
            }
            ObjectId object = repository.resolve(objectId);
            RevWalk walk = new RevWalk(repository);
            RevCommit rev = walk.parseCommit(object);
            RevCommit rev = walk.parseCommit(branchObject);
            commit = rev;
            walk.dispose();
        } catch (Throwable t) {
            LOGGER.error("Failed to get commit " + objectId, t);
            error(t, repository, "{0} failed to get commit {1}", objectId);
        }
        return commit;
    }
@@ -398,7 +448,7 @@
        byte[] content = null;
        try {
            if (tree == null) {
                ObjectId object = repository.resolve(Constants.HEAD);
                ObjectId object = getDefaultBranch(repository);
                RevCommit commit = rw.parseCommit(object);
                tree = commit.getTree();
            }
@@ -424,7 +474,7 @@
                content = os.toByteArray();
            }
        } catch (Throwable t) {
            LOGGER.error("Can't find " + path + " in tree " + tree.name(), t);
            error(t, repository, "{0} can't find {1} in tree {2}", path, tree.name());
        } finally {
            rw.dispose();
            tw.release();
@@ -473,7 +523,7 @@
            in.close();
            content = os.toByteArray();
        } catch (Throwable t) {
            LOGGER.error("Can't find blob " + objectId, t);
            error(t, repository, "{0} can't find blob {1}", objectId);
        } finally {
            rw.dispose();
        }
@@ -514,7 +564,7 @@
            return list;
        }
        if (commit == null) {
            commit = getCommit(repository, Constants.HEAD);
            commit = getCommit(repository, null);
        }
        final TreeWalk tw = new TreeWalk(repository);
        try {
@@ -543,7 +593,7 @@
                }
            }
        } catch (IOException e) {
            LOGGER.error("Failed to get files for commit " + commit.getName(), e);
            error(e, repository, "{0} failed to get files for commit {1}", commit.getName());
        } finally {
            tw.release();
        }
@@ -568,7 +618,7 @@
        RevWalk rw = new RevWalk(repository);
        try {
            if (commit == null) {
                ObjectId object = repository.resolve(Constants.HEAD);
                ObjectId object = getDefaultBranch(repository);
                commit = rw.parseCommit(object);
            }
@@ -602,7 +652,7 @@
                }
            }
        } catch (Throwable t) {
            LOGGER.error("failed to determine files in commit!", t);
            error(t, repository, "{0} failed to determine files in commit!");
        } finally {
            rw.dispose();
        }
@@ -623,7 +673,7 @@
        if (!hasCommits(repository)) {
            return list;
        }
        RevCommit commit = getCommit(repository, Constants.HEAD);
        RevCommit commit = getCommit(repository, null);
        final TreeWalk tw = new TreeWalk(repository);
        try {
            tw.addTree(commit.getTree());
@@ -645,7 +695,7 @@
                list.add(getPathModel(tw, null, commit));
            }
        } catch (IOException e) {
            LOGGER.error("Failed to get documents for commit " + commit.getName(), e);
            error(e, repository, "{0} failed to get documents for commit {1}", commit.getName());
        } finally {
            tw.release();
        }
@@ -674,7 +724,7 @@
                size = tw.getObjectReader().getObjectSize(tw.getObjectId(0), Constants.OBJ_BLOB);
            }
        } catch (Throwable t) {
            LOGGER.error("Failed to retrieve blob size", t);
            error(t, null, "failed to retrieve blob size for " + tw.getPathString());
        }
        return new PathModel(name, tw.getPathString(), size, tw.getFileMode(0).getBits(),
                commit.getName());
@@ -713,7 +763,7 @@
     * @return list of commits
     */
    public static List<RevCommit> getRevLog(Repository repository, int maxCount) {
        return getRevLog(repository, Constants.HEAD, 0, maxCount);
        return getRevLog(repository, null, 0, maxCount);
    }
    /**
@@ -762,12 +812,16 @@
            return list;
        }
        try {
            // resolve branch
            ObjectId branchObject;
            if (StringUtils.isEmpty(objectId)) {
                objectId = Constants.HEAD;
                branchObject = getDefaultBranch(repository);
            } else {
                branchObject = repository.resolve(objectId);
            }
            RevWalk rw = new RevWalk(repository);
            ObjectId object = repository.resolve(objectId);
            rw.markStart(rw.parseCommit(object));
            rw.markStart(rw.parseCommit(branchObject));
            if (!StringUtils.isEmpty(path)) {
                TreeFilter filter = AndTreeFilter.create(
                        PathFilterGroup.createFromStrings(Collections.singleton(path)),
@@ -796,7 +850,7 @@
            }
            rw.dispose();
        } catch (Throwable t) {
            LOGGER.error("Failed to get revlog", t);
            error(t, repository, "{0} failed to get {1} revlog for path {2}", objectId, path);
        }
        return list;
    }
@@ -850,9 +904,14 @@
            return list;
        }
        try {
            // resolve branch
            ObjectId branchObject;
            if (StringUtils.isEmpty(objectId)) {
                objectId = Constants.HEAD;
                branchObject = getDefaultBranch(repository);
            } else {
                branchObject = repository.resolve(objectId);
            }
            RevWalk rw = new RevWalk(repository);
            rw.setRevFilter(new RevFilter() {
@@ -887,8 +946,7 @@
                }
            });
            ObjectId object = repository.resolve(objectId);
            rw.markStart(rw.parseCommit(object));
            rw.markStart(rw.parseCommit(branchObject));
            Iterable<RevCommit> revlog = rw;
            if (offset > 0) {
                int count = 0;
@@ -911,9 +969,40 @@
            }
            rw.dispose();
        } catch (Throwable t) {
            LOGGER.error("Failed to search revlogs", t);
            error(t, repository, "{0} failed to {1} search revlogs for {2}", type.name(), value);
        }
        return list;
    }
    /**
     * Returns the default branch to use for a repository. Normally returns
     * whatever branch HEAD points to, but if HEAD points to nothing it returns
     * the most recently updated branch.
     *
     * @param repository
     * @return the objectid of a branch
     * @throws Exception
     */
    public static ObjectId getDefaultBranch(Repository repository) throws Exception {
        ObjectId object = repository.resolve(Constants.HEAD);
        if (object == null) {
            // no HEAD
            // perhaps non-standard repository, try local branches
            List<RefModel> branchModels = getLocalBranches(repository, true, -1);
            if (branchModels.size() > 0) {
                // use most recently updated branch
                RefModel branch = null;
                Date lastDate = new Date(0);
                for (RefModel branchModel : branchModels) {
                    if (branchModel.getDate().after(lastDate)) {
                        branch = branchModel;
                        lastDate = branch.getDate();
                    }
                }
                object = branch.getReferencedObjectId();
            }
        }
        return object;
    }
    /**
@@ -1044,7 +1133,7 @@
                list = new ArrayList<RefModel>(list.subList(0, maxCount));
            }
        } catch (IOException e) {
            LOGGER.error("Failed to retrieve " + refs, e);
            error(e, repository, "{0} failed to retrieve {1}", refs);
        }
        return list;
    }
@@ -1082,6 +1171,40 @@
    }
    /**
     * Create an orphaned branch in a repository. This code does not work.
     *
     * @param repository
     * @param name
     * @return
     */
    public static boolean createOrphanBranch(Repository repository, String name) {
        return true;
        // boolean success = false;
        // try {
        // ObjectId prev = repository.resolve(Constants.HEAD + "^1");
        // // create the orphan branch
        // RefUpdate orphanRef = repository.updateRef(Constants.R_HEADS + name);
        // orphanRef.setNewObjectId(prev);
        // orphanRef.setExpectedOldObjectId(ObjectId.zeroId());
        // Result updateResult = orphanRef.update();
        //
        // switch (updateResult) {
        // case NEW:
        // success = true;
        // break;
        // case NO_CHANGE:
        // default:
        // break;
        // }
        //
        // } catch (Throwable t) {
        // error(t, repository, "{0} failed to create orphaned branch {1}",
        // name);
        // }
        // return success;
    }
    /**
     * Returns a StoredConfig object for the repository.
     * 
     * @param repository
@@ -1092,9 +1215,9 @@
        try {
            c.load();
        } catch (ConfigInvalidException cex) {
            LOGGER.error("Repository configuration is invalid!", cex);
            error(cex, repository, "{0} configuration is invalid!");
        } catch (IOException cex) {
            LOGGER.error("Could not open repository configuration!", cex);
            error(cex, repository, "Could not open configuration for {0}!");
        }
        return c;
    }
@@ -1154,7 +1277,7 @@
            zos.finish();
            success = true;
        } catch (IOException e) {
            LOGGER.error("Failed to zip files from commit " + commit.getName(), e);
            error(e, repository, "{0} failed to zip files from commit {1}", commit.getName());
        } finally {
            tw.release();
            rw.dispose();
src/com/gitblit/utils/MetricUtils.java
@@ -16,6 +16,7 @@
package com.gitblit.utils;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
@@ -47,6 +48,29 @@
    private static final Logger LOGGER = LoggerFactory.getLogger(MetricUtils.class);
    /**
     * Log an error message and exception.
     *
     * @param t
     * @param repository
     *            if repository is not null it MUST be the {0} parameter in the
     *            pattern.
     * @param pattern
     * @param objects
     */
    private static void error(Throwable t, Repository repository, String pattern, Object... objects) {
        List<Object> parameters = new ArrayList<Object>();
        if (objects != null && objects.length > 0) {
            for (Object o : objects) {
                parameters.add(o);
            }
        }
        if (repository != null) {
            parameters.add(0, repository.getDirectory().getAbsolutePath());
        }
        LOGGER.error(MessageFormat.format(pattern, parameters.toArray()), t);
    }
    /**
     * Returns the list of metrics for the specified commit reference, branch,
     * or tag within the repository. If includeTotal is true, the total of all
     * the metrics will be included as the first element in the returned list.
@@ -67,9 +91,7 @@
            boolean includeTotal, String dateFormat) {
        Metric total = new Metric("TOTAL");
        final Map<String, Metric> metricMap = new HashMap<String, Metric>();
        if (StringUtils.isEmpty(objectId)) {
            objectId = Constants.HEAD;
        }
        if (JGitUtils.hasCommits(repository)) {
            final List<RefModel> tags = JGitUtils.getTags(repository, true, -1);
            final Map<ObjectId, RefModel> tagMap = new HashMap<ObjectId, RefModel>();
@@ -78,15 +100,22 @@
            }
            RevWalk revWalk = null;
            try {
                // resolve branch
                ObjectId branchObject;
                if (StringUtils.isEmpty(objectId)) {
                    branchObject = JGitUtils.getDefaultBranch(repository);
                } else {
                    branchObject = repository.resolve(objectId);
                }
                revWalk = new RevWalk(repository);
                ObjectId object = repository.resolve(objectId);
                RevCommit lastCommit = revWalk.parseCommit(object);
                RevCommit lastCommit = revWalk.parseCommit(branchObject);
                revWalk.markStart(lastCommit);
                DateFormat df;
                if (StringUtils.isEmpty(dateFormat)) {
                    // dynamically determine date format
                    RevCommit firstCommit = JGitUtils.getFirstCommit(repository, Constants.HEAD);
                    RevCommit firstCommit = JGitUtils.getFirstCommit(repository, branchObject.getName());
                    int diffDays = (lastCommit.getCommitTime() - firstCommit.getCommitTime())
                            / (60 * 60 * 24);
                    total.duration = diffDays;
@@ -118,7 +147,8 @@
                    }
                }
            } catch (Throwable t) {
                LOGGER.error("Failed to mine log history for date metrics", t);
                error(t, repository, "{0} failed to mine log history for date metrics of {1}",
                        objectId);
            } finally {
                if (revWalk != null) {
                    revWalk.dispose();
@@ -150,14 +180,17 @@
    public static List<Metric> getAuthorMetrics(Repository repository, String objectId,
            boolean byEmailAddress) {
        final Map<String, Metric> metricMap = new HashMap<String, Metric>();
        if (StringUtils.isEmpty(objectId)) {
            objectId = Constants.HEAD;
        }
        if (JGitUtils.hasCommits(repository)) {
            try {
                RevWalk walk = new RevWalk(repository);
                ObjectId object = repository.resolve(objectId);
                RevCommit lastCommit = walk.parseCommit(object);
                // resolve branch
                ObjectId branchObject;
                if (StringUtils.isEmpty(objectId)) {
                    branchObject = JGitUtils.getDefaultBranch(repository);
                } else {
                    branchObject = repository.resolve(objectId);
                }
                RevCommit lastCommit = walk.parseCommit(branchObject);
                walk.markStart(lastCommit);
                Iterable<RevCommit> revlog = walk;
@@ -181,7 +214,8 @@
                    m.count++;
                }
            } catch (Throwable t) {
                LOGGER.error("Failed to mine log history for author metrics", t);
                error(t, repository, "{0} failed to mine log history for author metrics of {1}",
                        objectId);
            }
        }
        List<String> keys = new ArrayList<String>(metricMap.keySet());
src/com/gitblit/wicket/WicketUtils.java
@@ -235,6 +235,9 @@
        if (StringUtils.isEmpty(path)) {
            return newObjectParameter(repositoryName, objectId);
        }
        if (StringUtils.isEmpty(objectId)) {
            return new PageParameters("r=" + repositoryName + ",f=" + path);
        }
        return new PageParameters("r=" + repositoryName + ",h=" + objectId + ",f=" + path);
    }
@@ -242,6 +245,9 @@
            int pageNumber) {
        if (pageNumber <= 1) {
            return newObjectParameter(repositoryName, objectId);
        }
        if (StringUtils.isEmpty(objectId)) {
            return new PageParameters("r=" + repositoryName + ",page=" + pageNumber);
        }
        return new PageParameters("r=" + repositoryName + ",h=" + objectId + ",page=" + pageNumber);
    }
@@ -251,12 +257,18 @@
        if (pageNumber <= 1) {
            return newObjectParameter(repositoryName, objectId);
        }
        if (StringUtils.isEmpty(objectId)) {
            return new PageParameters("r=" + repositoryName + ",f=" + path + ",page=" + pageNumber);
        }
        return new PageParameters("r=" + repositoryName + ",h=" + objectId + ",f=" + path
                + ",page=" + pageNumber);
    }
    public static PageParameters newBlobDiffParameter(String repositoryName, String baseCommitId,
            String commitId, String path) {
        if (StringUtils.isEmpty(commitId)) {
            return new PageParameters("r=" + repositoryName + ",f=" + path + ",hb=" + baseCommitId);
        }
        return new PageParameters("r=" + repositoryName + ",h=" + commitId + ",f=" + path + ",hb="
                + baseCommitId);
    }
@@ -272,6 +284,10 @@
    public static PageParameters newSearchParameter(String repositoryName, String commitId,
            String search, SearchType type, int pageNumber) {
        if (StringUtils.isEmpty(commitId)) {
            return new PageParameters("r=" + repositoryName + ",s=" + search + ",st=" + type.name()
                    + ",page=" + pageNumber);
        }
        return new PageParameters("r=" + repositoryName + ",h=" + commitId + ",s=" + search
                + ",st=" + type.name() + ",page=" + pageNumber);
    }
@@ -281,7 +297,7 @@
    }
    public static String getObject(PageParameters params) {
        return params.getString("h", Constants.HEAD);
        return params.getString("h", null);
    }
    public static String getPath(PageParameters params) {
@@ -335,7 +351,12 @@
        if (timeZone != null) {
            df.setTimeZone(timeZone);
        }
        String dateString = df.format(date);
        String dateString;
        if (date.getTime() == 0) {
            dateString = "--";
        } else {
            dateString = df.format(date);
        }
        String title = TimeUtils.timeAgo(date);
        Label label = new Label(wicketId, dateString);
        WicketUtils.setHtmlTooltip(label, title);
src/com/gitblit/wicket/pages/CommitDiffPage.java
@@ -88,21 +88,23 @@
                if (entry.isTree()) {
                    item.add(new LinkPanel("pathName", null, entry.path, TreePage.class,
                            newPathParameter(entry.path)));
                            WicketUtils
                                    .newPathParameter(repositoryName, entry.commitId, entry.path)));
                } else {
                    item.add(new LinkPanel("pathName", "list", entry.path, BlobPage.class,
                            newPathParameter(entry.path)));
                            WicketUtils
                                    .newPathParameter(repositoryName, entry.commitId, entry.path)));
                }
                item.add(new BookmarkablePageLink<Void>("patch", PatchPage.class,
                        newPathParameter(entry.path)));
                item.add(new BookmarkablePageLink<Void>("view", BlobPage.class,
                        newPathParameter(entry.path)));
                item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class,
                        newPathParameter(entry.path)));
                item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class,
                        newPathParameter(entry.path)).setEnabled(!entry.changeType
                        .equals(ChangeType.ADD)));
                item.add(new BookmarkablePageLink<Void>("patch", PatchPage.class, WicketUtils
                        .newPathParameter(repositoryName, entry.commitId, entry.path)));
                item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils
                        .newPathParameter(repositoryName, entry.commitId, entry.path)));
                item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils
                        .newPathParameter(repositoryName, entry.commitId, entry.path)));
                item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils
                        .newPathParameter(repositoryName, entry.commitId, entry.path))
                        .setEnabled(!entry.changeType.equals(ChangeType.ADD)));
                WicketUtils.setAlternatingBackground(item, counter);
                counter++;
src/com/gitblit/wicket/pages/CommitPage.java
@@ -150,22 +150,25 @@
                item.add(changeType);
                if (entry.isTree()) {
                    item.add(new LinkPanel("pathName", null, entry.path, TreePage.class,
                            newPathParameter(entry.path)));
                            WicketUtils
                                    .newPathParameter(repositoryName, entry.commitId, entry.path)));
                } else {
                    item.add(new LinkPanel("pathName", "list", entry.path, BlobPage.class,
                            newPathParameter(entry.path)));
                            WicketUtils
                                    .newPathParameter(repositoryName, entry.commitId, entry.path)));
                }
                item.add(new BookmarkablePageLink<Void>("diff", BlobDiffPage.class,
                        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", BlamePage.class,
                        newPathParameter(entry.path)));
                item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class,
                        newPathParameter(entry.path)).setEnabled(!entry.changeType
                        .equals(ChangeType.ADD)));
                item.add(new BookmarkablePageLink<Void>("diff", BlobDiffPage.class, WicketUtils
                        .newPathParameter(repositoryName, entry.commitId, entry.path))
                        .setEnabled(!entry.changeType.equals(ChangeType.ADD)
                                && !entry.changeType.equals(ChangeType.DELETE)));
                item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils
                        .newPathParameter(repositoryName, entry.commitId, entry.path)));
                item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils
                        .newPathParameter(repositoryName, entry.commitId, entry.path)));
                item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils
                        .newPathParameter(repositoryName, entry.commitId, entry.path))
                        .setEnabled(!entry.changeType.equals(ChangeType.ADD)));
                WicketUtils.setAlternatingBackground(item, counter);
                counter++;
src/com/gitblit/wicket/pages/DocsPage.java
@@ -56,8 +56,8 @@
                PathModel entry = item.getModelObject();
                item.add(WicketUtils.newImage("docIcon", "file_world_16x16.png"));
                item.add(new Label("docSize", byteFormat.format(entry.size)));
                item.add(new LinkPanel("docName", "list", entry.name, BlobPage.class,
                        newPathParameter(entry.path)));
                item.add(new LinkPanel("docName", "list", entry.name, BlobPage.class, WicketUtils
                        .newPathParameter(repositoryName, entry.commitId, entry.path)));
                // links
                item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils
src/com/gitblit/wicket/pages/RepositoryPage.java
@@ -330,10 +330,6 @@
        return WicketUtils.newObjectParameter(repositoryName, commitId);
    }
    protected PageParameters newPathParameter(String path) {
        return WicketUtils.newPathParameter(repositoryName, objectId, path);
    }
    private static class PageRegistration implements Serializable {
        private static final long serialVersionUID = 1L;
src/com/gitblit/wicket/pages/TicketsPage.java
@@ -64,6 +64,10 @@
        };
        add(ticketsView);
    }
    protected PageParameters newPathParameter(String path) {
        return WicketUtils.newPathParameter(repositoryName, objectId, path);
    }
    @Override
    protected String getPageName() {
src/com/gitblit/wicket/pages/TreePage.java
@@ -92,7 +92,8 @@
                    item.add(WicketUtils.newBlankImage("pathIcon"));
                    item.add(new Label("pathSize", ""));
                    item.add(new LinkPanel("pathName", null, entry.name, TreePage.class,
                            newPathParameter(entry.path)));
                            WicketUtils
                                    .newPathParameter(repositoryName, entry.commitId, entry.path)));
                    item.add(new Label("pathLinks", ""));
                } else {
                    if (entry.isTree()) {
@@ -100,7 +101,8 @@
                        item.add(WicketUtils.newImage("pathIcon", "folder_16x16.png"));
                        item.add(new Label("pathSize", ""));
                        item.add(new LinkPanel("pathName", "list", entry.name, TreePage.class,
                                newPathParameter(entry.path)));
                                WicketUtils.newPathParameter(repositoryName, entry.commitId,
                                        entry.path)));
                        // links
                        Fragment links = new Fragment("pathLinks", "treeLinks", this);
@@ -120,7 +122,8 @@
                        item.add(WicketUtils.getFileImage("pathIcon", entry.name));
                        item.add(new Label("pathSize", byteFormat.format(entry.size)));
                        item.add(new LinkPanel("pathName", "list", entry.name, BlobPage.class,
                                newPathParameter(entry.path)));
                                WicketUtils.newPathParameter(repositoryName, entry.commitId,
                                        entry.path)));
                        // links
                        Fragment links = new Fragment("pathLinks", "blobLinks", this);
src/com/gitblit/wicket/panels/BranchesPanel.java
@@ -27,6 +27,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.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import com.gitblit.SyndicationServlet;
@@ -142,7 +143,9 @@
                    this, null), BranchesPage.class, WicketUtils.newRepositoryParameter(model.name)));
        }
        // We always have 1 branch
        hasBranches = branches.size() > 1;
        hasBranches = (branches.size() > 1)
                || ((branches.size() == 1) && !branches.get(0).displayName
                        .equalsIgnoreCase(Constants.HEAD));
    }
    public BranchesPanel hideIfEmpty() {
src/com/gitblit/wicket/panels/RepositoriesPanel.java
@@ -193,7 +193,12 @@
                row.add(new Label("repositoryOwner", entry.owner));
                String lastChange = TimeUtils.timeAgo(entry.lastChange);
                String lastChange;
                if (entry.lastChange.getTime() == 0) {
                    lastChange = "--";
                } else {
                    lastChange = TimeUtils.timeAgo(entry.lastChange);
                }
                Label lastChangeLabel = new Label("repositoryLastChange", lastChange);
                row.add(lastChangeLabel);
                WicketUtils.setCssClass(lastChangeLabel, TimeUtils.timeAgoCss(entry.lastChange));
tests/com/gitblit/tests/GitBlitTest.java
@@ -39,8 +39,7 @@
        assertTrue("Helloworld model is null!", model != null);
        assertTrue(model.toString().equals(
                GitBlitSuite.getHelloworldRepository().getDirectory().getName()));
        assertEquals("" + GitBlit.self().calculateSize(model), GitBlit.self().calculateSize(model),
                22004L);
        assertTrue(GitBlit.self().calculateSize(model) > 22000L);
    }
    public void testUserModel() throws Exception {
tests/com/gitblit/tests/JGitUtilsTest.java
@@ -33,6 +33,7 @@
import org.eclipse.jgit.lib.RepositoryCache.FileKey;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
import com.gitblit.GitBlit;
import com.gitblit.Keys;
@@ -197,6 +198,13 @@
                .equals("183474d554e6f68478a02d9d7888b67a9338cdff"));
    }
    public void testCreateOrphanedBranch() throws Exception {
        Repository repository = JGitUtils.createRepository(GitBlitSuite.REPOSITORIES, "orphantest");
        assertTrue(JGitUtils.createOrphanBranch(repository,
                "x" + Long.toHexString(System.currentTimeMillis()).toUpperCase()));
        FileUtils.delete(repository.getDirectory(), FileUtils.RECURSIVE);
    }
    public void testStringContent() throws Exception {
        Repository repository = GitBlitSuite.getHelloworldRepository();
        String contentA = JGitUtils.getStringContent(repository, null, "java.java");
@@ -357,4 +365,5 @@
        assertTrue(zipFileB.length() > 0);
        zipFileB.delete();
    }
}