James Moger
2011-06-02 a125cf6876e0edc5a2498df57a9df06d60b1f572
Unit testing. Start of git-notes display feature.
2 files added
22 files modified
1066 ■■■■■ changed files
distrib/gitblit.properties 7 ●●●● patch | view | raw | blame | history
distrib/users.properties 2 ●●● patch | view | raw | blame | history
src/com/gitblit/GitBlit.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/models/GitNote.java 31 ●●●●● patch | view | raw | blame | history
src/com/gitblit/utils/DiffUtils.java 15 ●●●● patch | view | raw | blame | history
src/com/gitblit/utils/JGitUtils.java 454 ●●●●● patch | view | raw | blame | history
src/com/gitblit/utils/MarkdownUtils.java 21 ●●●● patch | view | raw | blame | history
src/com/gitblit/utils/MetricUtils.java 102 ●●●●● patch | view | raw | blame | history
src/com/gitblit/utils/StringUtils.java 8 ●●●●● patch | view | raw | blame | history
src/com/gitblit/utils/TicgitUtils.java 13 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/BlobDiffPage.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/CommitDiffPage.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/CommitPage.html 8 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/CommitPage.java 24 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditRepositoryPage.java 19 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/MetricsPage.java 10 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/SummaryPage.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/CommitLegendPanel.java 15 ●●●● patch | view | raw | blame | history
tests/com/gitblit/tests/DiffUtilsTest.java 2 ●●● patch | view | raw | blame | history
tests/com/gitblit/tests/GitBlitSuite.java 48 ●●●● patch | view | raw | blame | history
tests/com/gitblit/tests/JGitUtilsTest.java 202 ●●●● patch | view | raw | blame | history
tests/com/gitblit/tests/MarkdownUtilsTest.java 36 ●●●●● patch | view | raw | blame | history
tests/com/gitblit/tests/MetricUtilsTest.java 13 ●●●● patch | view | raw | blame | history
tests/com/gitblit/tests/TicgitUtilsTest.java 28 ●●●●● patch | view | raw | blame | history
distrib/gitblit.properties
@@ -17,9 +17,12 @@
# if false, each exported repository must have a .git/git-daemon-export-ok file
git.exportAll = true
# Search repositories folder for nested repositories
# Search the repositories folder subfolders for other repositories.
# Repositories MAY NOT be nested (i.e. one repository within another)
# but they may be grouped together in subfolders.
# e.g. c:/gitrepos/libraries/mylibrary.git
git.nestedRepositories = true
#      c:/gitrepos/libraries/myotherlibrary.git
git.searchRepositoriesSubfolders = true
#
# Authentication Settings
distrib/users.properties
@@ -1,3 +1,3 @@
## Git:Blit realm file format: username=password,\#permission,repository1,repository2...
#Tue May 31 11:19:53 EDT 2011
#Thu Jun 02 22:11:15 EDT 2011
admin=admin,\#admin
src/com/gitblit/GitBlit.java
@@ -142,7 +142,7 @@
    public List<String> getRepositoryList() {
        return JGitUtils.getRepositoryList(repositoriesFolder, exportAll,
                storedSettings.getBoolean(Keys.git.nestedRepositories, true));
                storedSettings.getBoolean(Keys.git.searchRepositoriesSubfolders, true));
    }
    public Repository getRepository(String repositoryName) {
src/com/gitblit/models/GitNote.java
New file
@@ -0,0 +1,31 @@
/*
 * 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.models;
import java.io.Serializable;
public class GitNote implements Serializable {
    private static final long serialVersionUID = 1L;
    public String content;
    public RefModel notesRef;
    public GitNote(RefModel notesRef, String text) {
        this.notesRef = notesRef;
        this.content = text;
    }
}
src/com/gitblit/utils/DiffUtils.java
@@ -30,12 +30,23 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.utils.JGitUtils.DiffOutputType;
public class DiffUtils {
    private static final Logger LOGGER = LoggerFactory.getLogger(DiffUtils.class);
    public static enum DiffOutputType {
        PLAIN, GITWEB, GITBLIT;
        public static DiffOutputType forName(String name) {
            for (DiffOutputType type : values()) {
                if (type.name().equalsIgnoreCase(name)) {
                    return type;
                }
            }
            return null;
        }
    }
    public static String getCommitDiff(Repository r, RevCommit commit, DiffOutputType outputType) {
        return getDiff(r, null, commit, null, outputType);
    }
src/com/gitblit/utils/JGitUtils.java
@@ -21,7 +21,6 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -31,7 +30,6 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@@ -52,6 +50,7 @@
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryCache.FileKey;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -67,19 +66,31 @@
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
import org.eclipse.jgit.treewalk.filter.PathSuffixFilter;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.io.DisabledOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.models.GitNote;
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.TicketModel.Comment;
public class JGitUtils {
    static final Logger LOGGER = LoggerFactory.getLogger(JGitUtils.class);
    public static String getDisplayName(PersonIdent person) {
        if (StringUtils.isEmpty(person.getEmailAddress())) {
            return person.getName();
        }
        final StringBuilder r = new StringBuilder();
        r.append(person.getName());
        r.append(" <");
        r.append(person.getEmailAddress());
        r.append('>');
        return r.toString().trim();
    }
    public static Repository createRepository(File repositoriesFolder, String name, boolean bare) {
        Git git = Git.init().setDirectory(new File(repositoriesFolder, name)).setBare(bare).call();
@@ -87,57 +98,37 @@
    }
    public static List<String> getRepositoryList(File repositoriesFolder, boolean exportAll,
            boolean readNested) {
            boolean searchSubfolders) {
        List<String> list = new ArrayList<String>();
        list.addAll(getNestedRepositories(repositoriesFolder, repositoriesFolder, exportAll,
                readNested));
        if (repositoriesFolder == null || !repositoriesFolder.exists()) {
            return list;
        }
        list.addAll(getRepositoryList(repositoriesFolder.getAbsolutePath(), repositoriesFolder,
                exportAll, searchSubfolders));
        Collections.sort(list);
        return list;
    }
    public static List<String> getNestedRepositories(File repositoriesFolder, File folder,
            boolean exportAll, boolean readNested) {
    private static List<String> getRepositoryList(String basePath, File searchFolder,
            boolean exportAll, boolean searchSubfolders) {
        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
                //
                // first look for standard folder/.git structure
                File gitFolder = new File(file, Constants.DOT_GIT);
                boolean isGitRepository = gitFolder.exists() && gitFolder.isDirectory();
        for (File file : searchFolder.listFiles()) {
            if (file.isDirectory()) {
                File gitDir = FileKey.resolve(new File(searchFolder, file.getName()), FS.DETECTED);
                if (gitDir != null) {
                    boolean exportRepository = exportAll
                            || new File(gitDir, "git-daemon-export-ok").exists();
                // then look for folder.git/HEAD or folder/HEAD and
                // folder/config
                if (!isGitRepository) {
                    if ((file.getName().endsWith(Constants.DOT_GIT_EXT) && new File(file,
                            Constants.HEAD).exists())
                            || (new File(file, "config").exists() && new File(file, Constants.HEAD)
                                    .exists())) {
                        gitFolder = file;
                        isGitRepository = true;
                    if (!exportRepository) {
                        continue;
                    }
                }
                boolean exportRepository = isGitRepository
                        && (exportAll || new File(gitFolder, "git-daemon-export-ok").exists());
                if (exportRepository) {
                    // determine repository name relative to repositories folder
                    String filename = file.getAbsolutePath();
                    String repo = filename.substring(basefile.length()).replace('\\', '/');
                    if (repo.charAt(0) == '/') {
                        repo = repo.substring(1);
                    }
                    list.add(repo);
                }
                // look for nested repositories
                if (readNested) {
                    list.addAll(getNestedRepositories(repositoriesFolder, file, exportAll,
                            readNested));
                    // determine repository name relative to base path
                    String repository = StringUtils.getRelativePath(basePath,
                            file.getAbsolutePath());
                    list.add(repository);
                } else if (searchSubfolders) {
                    // look for repositories in subfolders
                    list.addAll(getRepositoryList(basePath, file, exportAll, searchSubfolders));
                }
            }
        }
@@ -151,18 +142,18 @@
        if (StringUtils.isEmpty(branch)) {
            branch = Constants.HEAD;
        }
        RevCommit commit = null;
        try {
            RevWalk walk = new RevWalk(r);
            walk.sort(RevSort.REVERSE);
            RevCommit head = walk.parseCommit(r.resolve(branch));
            walk.markStart(head);
            RevCommit commit = walk.next();
            commit = walk.next();
            walk.dispose();
            return commit;
        } catch (Throwable t) {
            LOGGER.error("Failed to determine first commit", t);
        }
        return null;
        return commit;
    }
    public static Date getFirstChange(Repository r, String branch) {
@@ -197,13 +188,17 @@
        return getCommitDate(commit);
    }
    public static Date getCommitDate(RevCommit commit) {
        return new Date(commit.getCommitTime() * 1000L);
    }
    public static RevCommit getCommit(Repository r, String objectId) {
        RevCommit commit = null;
        if (!hasCommits(r)) {
            return null;
        }
        RevCommit commit = null;
        try {
            if (objectId == null || objectId.trim().length() == 0) {
            if (StringUtils.isEmpty(objectId)) {
                objectId = Constants.HEAD;
            }
            ObjectId object = r.resolve(objectId);
@@ -231,22 +226,6 @@
        return refs;
    }
    public static Map<ObjectId, List<String>> getRefs(Repository r, String baseRef) {
        Map<ObjectId, List<String>> refs = new HashMap<ObjectId, List<String>>();
        Map<AnyObjectId, Set<Ref>> allRefs = r.getAllRefsByPeeledObjectId();
        for (Entry<AnyObjectId, Set<Ref>> setRefs : allRefs.entrySet()) {
            List<String> list = new ArrayList<String>();
            for (Ref setRef : setRefs.getValue()) {
                String name = setRef.getName();
                if (name.startsWith(baseRef)) {
                    list.add(name);
                }
            }
            refs.put(setRefs.getKey().toObjectId(), list);
        }
        return refs;
    }
    /**
     * Lookup an entry stored in a tree, failing if not present.
     * 
@@ -257,13 +236,17 @@
     * @return the parsed object entry at this path
     * @throws Exception
     */
    public static RevObject getRevObject(Repository r, final RevTree tree, final String path) {
        RevObject ro = null;
    public static byte[] getRawContent(Repository r, RevCommit commit, final String path) {
        RevWalk rw = new RevWalk(r);
        TreeWalk tw = new TreeWalk(r);
        tw.setFilter(PathFilterGroup.createFromStrings(Collections.singleton(path)));
        byte[] content = null;
        try {
            tw.reset(tree);
            if (commit == null) {
                ObjectId object = r.resolve(Constants.HEAD);
                commit = rw.parseCommit(object);
            }
            tw.reset(commit.getTree());
            while (tw.next()) {
                if (tw.isSubtree() && !path.equals(tw.getPathString())) {
                    tw.enterSubtree();
@@ -271,123 +254,104 @@
                }
                ObjectId entid = tw.getObjectId(0);
                FileMode entmode = tw.getFileMode(0);
                ro = rw.lookupAny(entid, entmode.getObjectType());
                RevObject ro = rw.lookupAny(entid, entmode.getObjectType());
                rw.parseBody(ro);
                ByteArrayOutputStream os = new ByteArrayOutputStream();
                ObjectLoader ldr = r.open(ro.getId(), Constants.OBJ_BLOB);
                byte[] tmp = new byte[4096];
                InputStream in = ldr.openStream();
                int n;
                while ((n = in.read(tmp)) > 0) {
                    os.write(tmp, 0, n);
                }
                in.close();
                content = os.toByteArray();
            }
        } catch (Throwable t) {
            LOGGER.error("Can't find " + path + " in tree " + tree.name(), t);
            LOGGER.error("Can't find " + path + " in tree " + commit.getTree().name(), t);
        } finally {
            if (rw != null) {
                rw.dispose();
            }
            rw.dispose();
            tw.release();
        }
        return ro;
    }
    public static byte[] getRawContent(Repository r, RevBlob blob) {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        try {
            ObjectLoader ldr = r.open(blob.getId(), Constants.OBJ_BLOB);
            byte[] tmp = new byte[1024];
            InputStream in = ldr.openStream();
            int n;
            while ((n = in.read(tmp)) > 0) {
                os.write(tmp, 0, n);
            }
            in.close();
        } catch (Throwable t) {
            LOGGER.error("Failed to read raw content", t);
        }
        return os.toByteArray();
    }
    public static String getRawContentAsString(Repository r, RevBlob blob) {
        byte[] content = getRawContent(r, blob);
        return new String(content, Charset.forName(Constants.CHARACTER_ENCODING));
        return content;
    }
    public static String getRawContentAsString(Repository r, RevCommit commit, String blobPath) {
        RevObject obj = getRevObject(r, commit.getTree(), blobPath);
        byte[] content = getRawContent(r, (RevBlob) obj);
        byte[] content = getRawContent(r, commit, blobPath);
        if (content == null) {
            return null;
        }
        return new String(content, Charset.forName(Constants.CHARACTER_ENCODING));
    }
    public static List<PathModel> getFilesInPath(Repository r, String basePath, String objectId) {
        RevCommit commit = getCommit(r, objectId);
        return getFilesInPath(r, basePath, commit);
    }
    public static List<PathModel> getFilesInPath(Repository r, String basePath, RevCommit commit) {
        List<PathModel> list = new ArrayList<PathModel>();
        if (commit == null) {
        if (!hasCommits(r)) {
            return list;
        }
        final TreeWalk walk = new TreeWalk(r);
        if (commit == null) {
            commit = getCommit(r, Constants.HEAD);
        }
        final TreeWalk tw = new TreeWalk(r);
        try {
            walk.addTree(commit.getTree());
            if (basePath != null && basePath.length() > 0) {
            tw.addTree(commit.getTree());
            if (!StringUtils.isEmpty(basePath)) {
                PathFilter f = PathFilter.create(basePath);
                walk.setFilter(f);
                walk.setRecursive(false);
                tw.setFilter(f);
                tw.setRecursive(false);
                boolean foundFolder = false;
                while (walk.next()) {
                    if (!foundFolder && walk.isSubtree()) {
                        walk.enterSubtree();
                while (tw.next()) {
                    if (!foundFolder && tw.isSubtree()) {
                        tw.enterSubtree();
                    }
                    if (walk.getPathString().equals(basePath)) {
                    if (tw.getPathString().equals(basePath)) {
                        foundFolder = true;
                        continue;
                    }
                    if (foundFolder) {
                        list.add(getPathModel(walk, basePath, commit));
                        list.add(getPathModel(tw, basePath, commit));
                    }
                }
            } else {
                walk.setRecursive(false);
                while (walk.next()) {
                    list.add(getPathModel(walk, null, commit));
                tw.setRecursive(false);
                while (tw.next()) {
                    list.add(getPathModel(tw, null, commit));
                }
            }
        } catch (IOException e) {
            LOGGER.error("Failed to get files for commit " + commit.getName(), e);
        } finally {
            walk.release();
            tw.release();
        }
        Collections.sort(list);
        return list;
    }
    public static List<PathChangeModel> getFilesInCommit(Repository r, String commitId) {
        RevCommit commit = getCommit(r, commitId);
        return getFilesInCommit(r, commit);
    }
    public static List<PathChangeModel> getFilesInCommit(Repository r, RevCommit commit) {
        List<PathChangeModel> list = new ArrayList<PathChangeModel>();
        if (commit == null) {
            LOGGER.warn("getFilesInCommit for NULL commit");
            return list;
        }
        RevWalk rw = new RevWalk(r);
        TreeWalk tw = new TreeWalk(r);
        try {
            final RevWalk rw = new RevWalk(r);
            if (commit == null) {
                ObjectId object = r.resolve(Constants.HEAD);
                commit = rw.parseCommit(object);
            }
            RevTree commitTree = commit.getTree();
            final TreeWalk walk = new TreeWalk(r);
            walk.reset();
            walk.setRecursive(true);
            tw.reset();
            tw.setRecursive(true);
            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));
                tw.addTree(commitTree);
                while (tw.next()) {
                    list.add(new PathChangeModel(tw.getPathString(), tw.getPathString(), 0, tw
                            .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);
                tw.addTree(parentTree);
                tw.addTree(commitTree);
                tw.setFilter(TreeFilter.ANY_DIFF);
                RawTextComparator cmp = RawTextComparator.DEFAULT;
                DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE);
@@ -409,6 +373,9 @@
            }
        } catch (Throwable t) {
            LOGGER.error("failed to determine files in commit!", t);
        } finally {
            rw.dispose();
            tw.release();
        }
        return list;
    }
@@ -416,84 +383,51 @@
    public static List<PathModel> getDocuments(Repository r, List<String> extensions) {
        List<PathModel> list = new ArrayList<PathModel>();
        RevCommit commit = getCommit(r, Constants.HEAD);
        final TreeWalk walk = new TreeWalk(r);
        final TreeWalk tw = new TreeWalk(r);
        try {
            walk.addTree(commit.getTree());
            tw.addTree(commit.getTree());
            if (extensions != null && extensions.size() > 0) {
                Collection<TreeFilter> suffixFilters = new ArrayList<TreeFilter>();
                for (String extension : extensions) {
                    if (extension.charAt(0) == '.') {
                        suffixFilters.add(PathSuffixFilter.create(extension));
                        suffixFilters.add(PathSuffixFilter.create("\\" + extension));
                    } else {
                        // escape the . since this is a regexp filter
                        suffixFilters.add(PathSuffixFilter.create("\\." + extension));
                    }
                }
                TreeFilter filter = OrTreeFilter.create(suffixFilters);
                walk.setFilter(filter);
                walk.setRecursive(true);
                while (walk.next()) {
                    list.add(getPathModel(walk, null, commit));
                }
            } else {
                while (walk.next()) {
                    list.add(getPathModel(walk, null, commit));
                }
                tw.setFilter(filter);
                tw.setRecursive(true);
            }
            while (tw.next()) {
                list.add(getPathModel(tw, null, commit));
            }
        } catch (IOException e) {
            LOGGER.error("Failed to get files for commit " + commit.getName(), e);
            LOGGER.error("Failed to get documents for commit " + commit.getName(), e);
        } finally {
            walk.release();
            tw.release();
        }
        Collections.sort(list);
        return list;
    }
    public static Map<ChangeType, AtomicInteger> getChangedPathsStats(List<PathChangeModel> paths) {
        Map<ChangeType, AtomicInteger> stats = new HashMap<ChangeType, AtomicInteger>();
        for (PathChangeModel path : paths) {
            if (!stats.containsKey(path.changeType)) {
                stats.put(path.changeType, new AtomicInteger(0));
            }
            stats.get(path.changeType).incrementAndGet();
        }
        return stats;
    }
    public static enum DiffOutputType {
        PLAIN, GITWEB, GITBLIT;
        public static DiffOutputType forName(String name) {
            for (DiffOutputType type : values()) {
                if (type.name().equalsIgnoreCase(name)) {
                    return type;
                }
            }
            return null;
        }
    }
    private static PathModel getPathModel(TreeWalk walk, String basePath, RevCommit commit) {
    private static PathModel getPathModel(TreeWalk tw, String basePath, RevCommit commit) {
        String name;
        long size = 0;
        if (basePath == null) {
            name = walk.getPathString();
        if (StringUtils.isEmpty(basePath)) {
            name = tw.getPathString();
        } else {
            try {
                name = walk.getPathString().substring(basePath.length() + 1);
            } catch (Throwable t) {
                name = walk.getPathString();
            }
            name = tw.getPathString().substring(basePath.length() + 1);
        }
        try {
            if (!walk.isSubtree()) {
                size = walk.getObjectReader()
                        .getObjectSize(walk.getObjectId(0), Constants.OBJ_BLOB);
            if (!tw.isSubtree()) {
                size = tw.getObjectReader().getObjectSize(tw.getObjectId(0), Constants.OBJ_BLOB);
            }
        } catch (Throwable t) {
            LOGGER.error("Failed to retrieve blob size", t);
        }
        return new PathModel(name, walk.getPathString(), size, walk.getFileMode(0).getBits(),
        return new PathModel(name, tw.getPathString(), size, tw.getFileMode(0).getBits(),
                commit.getName());
    }
@@ -510,11 +444,9 @@
        } else if (FileMode.GITLINK.equals(mode)) {
            // FIXME gitlink permissions
            return "gitlink";
        } else if (FileMode.MISSING.equals(mode)) {
            // FIXME missing permissions
            return "missing";
        }
        return "" + mode;
        // FIXME missing permissions
        return "missing";
    }
    public static List<RevCommit> getRevLog(Repository r, int maxCount) {
@@ -532,19 +464,19 @@
            return list;
        }
        try {
            if (objectId == null || objectId.trim().length() == 0) {
            if (StringUtils.isEmpty(objectId)) {
                objectId = Constants.HEAD;
            }
            RevWalk walk = new RevWalk(r);
            RevWalk rw = new RevWalk(r);
            ObjectId object = r.resolve(objectId);
            walk.markStart(walk.parseCommit(object));
            rw.markStart(rw.parseCommit(object));
            if (!StringUtils.isEmpty(path)) {
                TreeFilter filter = AndTreeFilter.create(
                        PathFilterGroup.createFromStrings(Collections.singleton(path)),
                        TreeFilter.ANY_DIFF);
                walk.setTreeFilter(filter);
                rw.setTreeFilter(filter);
            }
            Iterable<RevCommit> revlog = walk;
            Iterable<RevCommit> revlog = rw;
            if (offset > 0) {
                int count = 0;
                for (RevCommit rev : revlog) {
@@ -564,9 +496,9 @@
                    }
                }
            }
            walk.dispose();
            rw.dispose();
        } catch (Throwable t) {
            LOGGER.error("Failed to determine last change", t);
            LOGGER.error("Failed to get revlog", t);
        }
        return list;
    }
@@ -580,9 +512,10 @@
                    return type;
                }
            }
            return null;
            return COMMIT;
        }
        @Override
        public String toString() {
            return name().toLowerCase();
        }
@@ -596,11 +529,11 @@
            return list;
        }
        try {
            if (objectId == null || objectId.trim().length() == 0) {
            if (StringUtils.isEmpty(objectId)) {
                objectId = Constants.HEAD;
            }
            RevWalk walk = new RevWalk(r);
            walk.setRevFilter(new RevFilter() {
            RevWalk rw = new RevWalk(r);
            rw.setRevFilter(new RevFilter() {
                @Override
                public RevFilter clone() {
@@ -610,25 +543,30 @@
                @Override
                public boolean include(RevWalk walker, RevCommit commit) throws StopWalkException,
                        MissingObjectException, IncorrectObjectTypeException, IOException {
                    boolean include = false;
                    switch (type) {
                    case AUTHOR:
                        return (commit.getAuthorIdent().getName().toLowerCase().indexOf(lcValue) > -1)
                        include = (commit.getAuthorIdent().getName().toLowerCase().indexOf(lcValue) > -1)
                                || (commit.getAuthorIdent().getEmailAddress().toLowerCase()
                                        .indexOf(lcValue) > -1);
                        break;
                    case COMMITTER:
                        return (commit.getCommitterIdent().getName().toLowerCase().indexOf(lcValue) > -1)
                        include = (commit.getCommitterIdent().getName().toLowerCase()
                                .indexOf(lcValue) > -1)
                                || (commit.getCommitterIdent().getEmailAddress().toLowerCase()
                                        .indexOf(lcValue) > -1);
                        break;
                    case COMMIT:
                        return commit.getFullMessage().toLowerCase().indexOf(lcValue) > -1;
                        include = commit.getFullMessage().toLowerCase().indexOf(lcValue) > -1;
                        break;
                    }
                    return false;
                    return include;
                }
            });
            ObjectId object = r.resolve(objectId);
            walk.markStart(walk.parseCommit(object));
            Iterable<RevCommit> revlog = walk;
            rw.markStart(rw.parseCommit(object));
            Iterable<RevCommit> revlog = rw;
            if (offset > 0) {
                int count = 0;
                for (RevCommit rev : revlog) {
@@ -648,9 +586,9 @@
                    }
                }
            }
            walk.dispose();
            rw.dispose();
        } catch (Throwable t) {
            LOGGER.error("Failed to determine last change", t);
            LOGGER.error("Failed to search revlogs", t);
        }
        return list;
    }
@@ -667,7 +605,11 @@
        return getRefs(r, Constants.R_REMOTES, maxCount);
    }
    public static List<RefModel> getRefs(Repository r, String refs, int maxCount) {
    public static List<RefModel> getNotes(Repository r, int maxCount) {
        return getRefs(r, Constants.R_NOTES, maxCount);
    }
    private static List<RefModel> getRefs(Repository r, String refs, int maxCount) {
        List<RefModel> list = new ArrayList<RefModel>();
        try {
            Map<String, Ref> map = r.getRefDatabase().getRefs(refs);
@@ -687,45 +629,32 @@
        return list;
    }
    public static Ref getRef(Repository r, String id) {
        // FIXME
        try {
            Map<String, Ref> map = r.getRefDatabase().getRefs(id);
            for (Entry<String, Ref> entry : map.entrySet()) {
                return entry.getValue();
    public static List<GitNote> getNotesOnCommit(Repository repository, RevCommit commit) {
        List<GitNote> list = new ArrayList<GitNote>();
        List<RefModel> notesRefs = getNotes(repository, -1);
        for (RefModel notesRef : notesRefs) {
            RevCommit notes = JGitUtils.getCommit(repository, notesRef.getName());
            StringBuilder sb = new StringBuilder(commit.getName());
            sb.insert(2, '/');
            String text = getRawContentAsString(repository, notes, sb.toString());
            if (!StringUtils.isEmpty(text)) {
                GitNote gitNote = new GitNote(notesRef, text);
                list.add(gitNote);
            }
        } catch (IOException e) {
            LOGGER.error("Failed to retrieve ref " + id, e);
        }
        return null;
    }
    public static Date getCommitDate(RevCommit commit) {
        return new Date(commit.getCommitTime() * 1000L);
    }
    public static String getDisplayName(PersonIdent person) {
        final StringBuilder r = new StringBuilder();
        r.append(person.getName());
        r.append(" <");
        r.append(person.getEmailAddress());
        r.append('>');
        return r.toString();
        return list;
    }
    public static StoredConfig readConfig(Repository r) {
        StoredConfig c = r.getConfig();
        if (c != null) {
            try {
                c.load();
            } catch (ConfigInvalidException cex) {
                LOGGER.error("Repository configuration is invalid!", cex);
            } catch (IOException cex) {
                LOGGER.error("Could not open repository configuration!", cex);
            }
            return c;
        try {
            c.load();
        } catch (ConfigInvalidException cex) {
            LOGGER.error("Repository configuration is invalid!", cex);
        } catch (IOException cex) {
            LOGGER.error("Could not open repository configuration!", cex);
        }
        return null;
        return c;
    }
    public static boolean zip(Repository r, String basePath, String objectId, OutputStream os)
@@ -734,26 +663,27 @@
        if (commit == null) {
            return false;
        }
        final RevWalk rw = new RevWalk(r);
        final TreeWalk walk = new TreeWalk(r);
        boolean success = false;
        RevWalk rw = new RevWalk(r);
        TreeWalk tw = new TreeWalk(r);
        try {
            walk.addTree(commit.getTree());
            tw.addTree(commit.getTree());
            ZipOutputStream zos = new ZipOutputStream(os);
            zos.setComment("Generated by Git:Blit");
            if (basePath != null && basePath.length() > 0) {
            if (!StringUtils.isEmpty(basePath)) {
                PathFilter f = PathFilter.create(basePath);
                walk.setFilter(f);
                tw.setFilter(f);
            }
            walk.setRecursive(true);
            while (walk.next()) {
                ZipEntry entry = new ZipEntry(walk.getPathString());
                entry.setSize(walk.getObjectReader().getObjectSize(walk.getObjectId(0),
            tw.setRecursive(true);
            while (tw.next()) {
                ZipEntry entry = new ZipEntry(tw.getPathString());
                entry.setSize(tw.getObjectReader().getObjectSize(tw.getObjectId(0),
                        Constants.OBJ_BLOB));
                entry.setComment(commit.getName());
                zos.putNextEntry(entry);
                ObjectId entid = walk.getObjectId(0);
                FileMode entmode = walk.getFileMode(0);
                ObjectId entid = tw.getObjectId(0);
                FileMode entmode = tw.getFileMode(0);
                RevBlob blob = (RevBlob) rw.lookupAny(entid, entmode.getObjectType());
                rw.parseBody(blob);
@@ -767,13 +697,13 @@
                in.close();
            }
            zos.finish();
            return true;
            success = true;
        } catch (IOException e) {
            LOGGER.error("Failed to zip files from commit " + commit.getName(), e);
        } finally {
            walk.release();
            tw.release();
            rw.dispose();
        }
        return false;
        return success;
    }
}
src/com/gitblit/utils/MarkdownUtils.java
@@ -26,22 +26,10 @@
public class MarkdownUtils {
    public static String transformMarkdown(String markdown) throws java.text.ParseException {
        // Read raw markdown content and transform it to html
        StringReader reader = new StringReader(markdown);
        StringWriter writer = new StringWriter();
        try {
            Markdown md = new Markdown();
            md.transform(reader, writer);
            return writer.toString();
        } catch (ParseException p) {
            throw new java.text.ParseException(p.getMessage(), 0);
        } finally {
            reader.close();
            try {
                writer.close();
            } catch (IOException e) {
                // IGNORE
            }
            return transformMarkdown(new StringReader(markdown));
        } catch (NullPointerException p) {
            throw new java.text.ParseException("Markdown string is null!", 0);
        }
    }
@@ -51,7 +39,7 @@
        try {
            Markdown md = new Markdown();
            md.transform(markdownReader, writer);
            return writer.toString();
            return writer.toString().trim();
        } catch (ParseException p) {
            throw new java.text.ParseException(p.getMessage(), 0);
        } finally {
@@ -67,5 +55,4 @@
            }
        }
    }
}
src/com/gitblit/utils/MetricUtils.java
@@ -42,45 +42,7 @@
    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 (JGitUtils.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 = JGitUtils.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) {
                JGitUtils.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>();
        if (JGitUtils.hasCommits(r)) {
            final List<RefModel> tags = JGitUtils.getTags(r, -1);
            final Map<ObjectId, RefModel> tagMap = new HashMap<ObjectId, RefModel>();
@@ -90,25 +52,31 @@
            try {
                RevWalk walk = new RevWalk(r);
                ObjectId object = r.resolve(Constants.HEAD);
                RevCommit firstCommit = JGitUtils.getFirstCommit(r, Constants.HEAD);
                RevCommit lastCommit = walk.parseCommit(object);
                int diffDays = (lastCommit.getCommitTime() - firstCommit.getCommitTime())
                        / (60 * 60 * 24);
                total.duration = diffDays;
                DateFormat df;
                if (diffDays <= 90) {
                    // Days
                    df = new SimpleDateFormat("yyyy-MM-dd");
                } else if (diffDays > 90 && diffDays < 365) {
                    // Weeks
                    df = new SimpleDateFormat("yyyy-MM (w)");
                } else {
                    // Months
                    df = new SimpleDateFormat("yyyy-MM");
                }
                walk.markStart(lastCommit);
                DateFormat df;
                if (StringUtils.isEmpty(format)) {
                    // dynamically determine date format
                    RevCommit firstCommit = JGitUtils.getFirstCommit(r, Constants.HEAD);
                    int diffDays = (lastCommit.getCommitTime() - firstCommit.getCommitTime())
                            / (60 * 60 * 24);
                    total.duration = diffDays;
                    if (diffDays <= 90) {
                        // Days
                        df = new SimpleDateFormat("yyyy-MM-dd");
                    } else if (diffDays > 90 && diffDays < 365) {
                        // Weeks
                        df = new SimpleDateFormat("yyyy-MM (w)");
                    } else {
                        // Months
                        df = new SimpleDateFormat("yyyy-MM");
                    }
                } else {
                    // use specified date format
                    df = new SimpleDateFormat(format);
                }
                Iterable<RevCommit> revlog = walk;
                for (RevCommit rev : revlog) {
                    Date d = JGitUtils.getCommitDate(rev);
@@ -125,7 +93,7 @@
                    }
                }
            } catch (Throwable t) {
                JGitUtils.LOGGER.error("Failed to mine log history for metrics", t);
                LOGGER.error("Failed to mine log history for date metrics", t);
            }
        }
        List<String> keys = new ArrayList<String>(metricMap.keySet());
@@ -140,32 +108,38 @@
        return metrics;
    }
    public static List<Metric> getAuthorMetrics(Repository r) {
        Metric total = new Metric("TOTAL");
    public static List<Metric> getAuthorMetrics(Repository r, boolean byEmail) {
        final Map<String, Metric> metricMap = new HashMap<String, Metric>();
        if (JGitUtils.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)) {
                    String p;
                    if (byEmail) {
                        p = rev.getAuthorIdent().getEmailAddress();
                        if (StringUtils.isEmpty(p)) {
                            p = rev.getAuthorIdent().getName();
                        }
                    } else {
                        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) {
                JGitUtils.LOGGER.error("Failed to mine log history for metrics", t);
                LOGGER.error("Failed to mine log history for author metrics", t);
            }
        }
        List<String> keys = new ArrayList<String>(metricMap.keySet());
src/com/gitblit/utils/StringUtils.java
@@ -134,4 +134,12 @@
        }
        return "";
    }
    public static String getRelativePath(String basePath, String fullPath) {
        String relativePath = fullPath.substring(basePath.length()).replace('\\', '/');
        if (relativePath.charAt(0) == '/') {
            relativePath = relativePath.substring(1);
        }
        return relativePath;
    }
}
src/com/gitblit/utils/TicgitUtils.java
@@ -61,6 +61,9 @@
    public static List<TicketModel> getTickets(Repository r) {
        RefModel ticgitBranch = getTicketsBranch(r);
        if (ticgitBranch == null) {
            return null;
        }
        List<PathModel> paths = JGitUtils.getFilesInPath(r, null, ticgitBranch.commit);
        List<TicketModel> tickets = new ArrayList<TicketModel>();
        for (PathModel ticketFolder : paths) {
@@ -112,7 +115,7 @@
                        Comment c = new Comment(file.name, content);
                        ticket.comments.add(c);
                    } catch (ParseException e) {
                        e.printStackTrace();
                        LOGGER.error("Failed to parse ticket comment", e);
                    }
                } else if (chunks[0].equals("TAG")) {
                    if (content.startsWith("TAG_")) {
@@ -126,13 +129,5 @@
            }
        }
        Collections.sort(ticket.comments);
    }
    public static String getTicketContent(Repository r, String filePath) {
        RefModel ticketsBranch = getTicketsBranch(r);
        if (ticketsBranch != null) {
            return JGitUtils.getRawContentAsString(r, ticketsBranch.commit, filePath);
        }
        return "";
    }
}
src/com/gitblit/wicket/pages/BlobDiffPage.java
@@ -24,8 +24,8 @@
import com.gitblit.GitBlit;
import com.gitblit.Keys;
import com.gitblit.utils.DiffUtils;
import com.gitblit.utils.DiffUtils.DiffOutputType;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.JGitUtils.DiffOutputType;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.CommitHeaderPanel;
src/com/gitblit/wicket/pages/CommitDiffPage.java
@@ -32,8 +32,8 @@
import com.gitblit.Keys;
import com.gitblit.models.PathModel.PathChangeModel;
import com.gitblit.utils.DiffUtils;
import com.gitblit.utils.DiffUtils.DiffOutputType;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.JGitUtils.DiffOutputType;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.CommitHeaderPanel;
import com.gitblit.wicket.panels.CommitLegendPanel;
src/com/gitblit/wicket/pages/CommitPage.html
@@ -44,6 +44,14 @@
    <!-- full message -->
    <div class="commit_message" wicket:id="fullMessage">[commit message]</div>
    <!--  git notes -->
    <table style="padding-bottom:5px;">
        <tr wicket:id="notes">
            <td style="vertical-align:top;"><span class="headRef" wicket:id="refName"></span><br/><span wicket:id="authorName"></span><br/><span wicket:id="authorDate"></span></td>
            <td><span wicket:id="noteContent"></span></td>
        </tr>
    </table>
    <!--  commit legend -->
    <div style="text-align:right;" wicket:id="commitLegend"></div>
    
src/com/gitblit/wicket/pages/CommitPage.java
@@ -18,6 +18,7 @@
import java.util.ArrayList;
import java.util.List;
import org.apache.wicket.Component;
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
@@ -33,8 +34,10 @@
import com.gitblit.DownloadZipServlet;
import com.gitblit.GitBlit;
import com.gitblit.Keys;
import com.gitblit.models.GitNote;
import com.gitblit.models.PathModel.PathChangeModel;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.JGitUtils.SearchType;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.CommitHeaderPanel;
@@ -112,6 +115,27 @@
        addFullText("fullMessage", c.getFullMessage(), true);
        // git notes
        List<GitNote> notes = JGitUtils.getNotesOnCommit(r, c);
        ListDataProvider<GitNote> notesDp = new ListDataProvider<GitNote>(notes);
        DataView<GitNote> notesView = new DataView<GitNote>("notes", notesDp) {
            private static final long serialVersionUID = 1L;
            public void populateItem(final Item<GitNote> item) {
                GitNote entry = item.getModelObject();
                Component c = new LinkPanel("refName", null, entry.notesRef.displayName,
                        CommitPage.class, newCommitParameter(entry.notesRef.commit.getName()));
                WicketUtils.setCssClass(c, "headRef");
                item.add(c);
                item.add(createPersonPanel("authorName", entry.notesRef.commit.getAuthorIdent(), SearchType.AUTHOR));
                item.add(WicketUtils.createTimestampLabel("authorDate",
                        entry.notesRef.commit.getAuthorIdent().getWhen(), getTimeZone()));
                item.add(new Label("noteContent", StringUtils.breakLinesForHtml(entry.content)).setEscapeModelStrings(false));
            }
        };
        add(notesView.setVisible(notes.size() > 0));
        // changed paths list
        List<PathChangeModel> paths = JGitUtils.getFilesInCommit(r, c);
        add(new CommitLegendPanel("commitLegend", paths));
src/com/gitblit/wicket/pages/EditRepositoryPage.java
@@ -19,7 +19,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -104,6 +103,22 @@
                    // automatically convert backslashes to forward slashes
                    repositoryModel.name = repositoryModel.name.replace('\\', '/');
                    // Automatically replace // with /
                    repositoryModel.name = repositoryModel.name.replace("//", "/");
                    // prohibit folder paths
                    if (repositoryModel.name.startsWith("/")) {
                        error("Leading root folder references (/) are prohibited.");
                        return;
                    }
                    if (repositoryModel.name.startsWith("../")) {
                        error("Relative folder references (../) are prohibited.");
                        return;
                    }
                    if (repositoryModel.name.contains("/../")) {
                        error("Relative folder references (../) are prohibited.");
                        return;
                    }
                    // confirm valid characters in repository name
                    char[] validChars = { '/', '.', '_', '-' };
@@ -120,7 +135,7 @@
                            }
                        }
                    }
                    // confirm access restriction selection
                    if (repositoryModel.accessRestriction == null) {
                        error("Please select access restriction!");
src/com/gitblit/wicket/pages/MetricsPage.java
@@ -47,7 +47,7 @@
    public MetricsPage(PageParameters params) {
        super(params);
        Repository r = getRepository();
        insertLinePlot("commitsChart", MetricUtils.getDateMetrics(r, false));
        insertLinePlot("commitsChart", MetricUtils.getDateMetrics(r, false, null));
        insertBarPlot("dayOfWeekChart", getDayOfWeekMetrics(r));
        insertLinePlot("timeOfDayChart", getTimeOfDayMetrics(r));
        insertPieChart("authorsChart", getAuthorMetrics(r));
@@ -57,7 +57,7 @@
        if ((metrics != null) && (metrics.size() > 0)) {
            IChartData data = WicketUtils.getChartData(metrics);
            ChartProvider provider = new ChartProvider(new Dimension(400, 100), ChartType.LINE,
            ChartProvider provider = new ChartProvider(new Dimension(500, 100), ChartType.LINE,
                    data);
            ChartAxis dateAxis = new ChartAxis(ChartAxisType.BOTTOM);
            dateAxis.setLabels(new String[] { metrics.get(0).name,
@@ -82,7 +82,7 @@
        if ((metrics != null) && (metrics.size() > 0)) {
            IChartData data = WicketUtils.getChartData(metrics);
            ChartProvider provider = new ChartProvider(new Dimension(400, 100),
            ChartProvider provider = new ChartProvider(new Dimension(500, 100),
                    ChartType.BAR_VERTICAL_SET, data);
            ChartAxis dateAxis = new ChartAxis(ChartAxisType.BOTTOM);
            List<String> labels = new ArrayList<String>();
@@ -110,7 +110,7 @@
            for (Metric metric : metrics) {
                labels.add(metric.name);
            }
            ChartProvider provider = new ChartProvider(new Dimension(400, 200), ChartType.PIE, data);
            ChartProvider provider = new ChartProvider(new Dimension(500, 200), ChartType.PIE, data);
            provider.setPieLabels(labels.toArray(new String[labels.size()]));
            add(new Chart(wicketId, provider));
        } else {
@@ -164,7 +164,7 @@
    }
    private List<Metric> getAuthorMetrics(Repository repository) {
        List<Metric> authors = MetricUtils.getAuthorMetrics(repository);
        List<Metric> authors = MetricUtils.getAuthorMetrics(repository, true);
        Collections.sort(authors, new Comparator<Metric>() {
            @Override
            public int compare(Metric o1, Metric o2) {
src/com/gitblit/wicket/pages/SummaryPage.java
@@ -77,7 +77,7 @@
        List<Metric> metrics = null;
        Metric metricsTotal = null;
        if (GitBlit.getBoolean(Keys.web.generateActivityGraph, true)) {
            metrics = MetricUtils.getDateMetrics(r, true);
            metrics = MetricUtils.getDateMetrics(r, true, null);
            metricsTotal = metrics.remove(0);
        }
src/com/gitblit/wicket/panels/CommitLegendPanel.java
@@ -17,6 +17,7 @@
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
@@ -29,7 +30,6 @@
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import com.gitblit.models.PathModel.PathChangeModel;
import com.gitblit.utils.JGitUtils;
import com.gitblit.wicket.WicketUtils;
public class CommitLegendPanel extends Panel {
@@ -38,7 +38,7 @@
    public CommitLegendPanel(String id, List<PathChangeModel> paths) {
        super(id);
        final Map<ChangeType, AtomicInteger> stats = JGitUtils.getChangedPathsStats(paths);
        final Map<ChangeType, AtomicInteger> stats = getChangedPathsStats(paths);
        ListDataProvider<ChangeType> legendDp = new ListDataProvider<ChangeType>(
                new ArrayList<ChangeType>(stats.keySet()));
        DataView<ChangeType> legendsView = new DataView<ChangeType>("legend", legendDp) {
@@ -74,4 +74,15 @@
        };
        add(legendsView);
    }
    protected Map<ChangeType, AtomicInteger> getChangedPathsStats(List<PathChangeModel> paths) {
        Map<ChangeType, AtomicInteger> stats = new HashMap<ChangeType, AtomicInteger>();
        for (PathChangeModel path : paths) {
            if (!stats.containsKey(path.changeType)) {
                stats.put(path.changeType, new AtomicInteger(0));
            }
            stats.get(path.changeType).incrementAndGet();
        }
        return stats;
    }
}
tests/com/gitblit/tests/DiffUtilsTest.java
@@ -21,8 +21,8 @@
import org.eclipse.jgit.revwalk.RevCommit;
import com.gitblit.utils.DiffUtils;
import com.gitblit.utils.DiffUtils.DiffOutputType;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.JGitUtils.DiffOutputType;
public class DiffUtilsTest extends TestCase {
tests/com/gitblit/tests/GitBlitSuite.java
@@ -28,7 +28,9 @@
import com.gitblit.FileSettings;
import com.gitblit.GitBlit;
import com.gitblit.GitBlitException;
import com.gitblit.JettyLoginService;
import com.gitblit.models.RepositoryModel;
public class GitBlitSuite extends TestSetup {
    public static final File REPOSITORIES = new File("git");
@@ -42,6 +44,7 @@
        suite.addTestSuite(TimeUtilsTest.class);
        suite.addTestSuite(StringUtilsTest.class);
        suite.addTestSuite(ByteFormatTest.class);
        suite.addTestSuite(MarkdownUtilsTest.class);
        suite.addTestSuite(JGitUtilsTest.class);
        suite.addTestSuite(DiffUtilsTest.class);
        suite.addTestSuite(MetricUtilsTest.class);
@@ -60,16 +63,21 @@
    @Override
    protected void setUp() throws Exception {
        if (REPOSITORIES.exists() || REPOSITORIES.mkdirs()) {
            cloneOrFetch("helloworld.git", "https://github.com/git/hello-world.git", true);
            cloneOrFetch("nested/helloworld.git", "https://github.com/git/hello-world.git", true);
            cloneOrFetch("ticgit.git", "https://github.com/jeffWelling/ticgit.git", true);
        }
        FileSettings settings = new FileSettings("distrib/gitblit.properties");
        GitBlit.self().configureContext(settings);
        JettyLoginService loginService = new JettyLoginService(new File("distrib/users.properties"));
        loginService.loadUsers();
        GitBlit.self().setLoginService(loginService);
        if (REPOSITORIES.exists() || REPOSITORIES.mkdirs()) {
            cloneOrFetch("helloworld.git", "https://github.com/git/hello-world.git", true);
            cloneOrFetch("nested/helloworld.git", "https://github.com/git/hello-world.git", true);
            cloneOrFetch("ticgit.git", "https://github.com/jeffWelling/ticgit.git", true);
            enableTickets("ticgit.git");
            enableDocs("ticgit.git");
            showRemoteBranches("ticgit.git");
        }
    }
    private void cloneOrFetch(String toFolder, String fromUrl, boolean bare) throws Exception {
@@ -92,4 +100,34 @@
            System.out.println("done.");
        }
    }
    private void enableTickets(String repositoryName) {
        try {
            RepositoryModel model = GitBlit.self().getRepositoryModel(repositoryName);
            model.useTickets = true;
            GitBlit.self().editRepositoryModel(model.name, model, false);
        } catch (GitBlitException g) {
            g.printStackTrace();
        }
    }
    private void enableDocs(String repositoryName) {
        try {
            RepositoryModel model = GitBlit.self().getRepositoryModel(repositoryName);
            model.useDocs = true;
            GitBlit.self().editRepositoryModel(model.name, model, false);
        } catch (GitBlitException g) {
            g.printStackTrace();
        }
    }
    private void showRemoteBranches(String repositoryName) {
        try {
            RepositoryModel model = GitBlit.self().getRepositoryModel(repositoryName);
            model.showRemoteBranches = true;
            GitBlit.self().editRepositoryModel(model.name, model, false);
        } catch (GitBlitException g) {
            g.printStackTrace();
        }
    }
}
tests/com/gitblit/tests/JGitUtilsTest.java
@@ -17,24 +17,40 @@
import java.io.File;
import java.io.FileOutputStream;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import junit.framework.TestCase;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTree;
import com.gitblit.GitBlit;
import com.gitblit.Keys;
import com.gitblit.models.PathModel;
import com.gitblit.models.PathModel.PathChangeModel;
import com.gitblit.models.RefModel;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.JGitUtils.SearchType;
public class JGitUtilsTest extends TestCase {
    public void testDisplayName() throws Exception {
        assertTrue(JGitUtils.getDisplayName(new PersonIdent("Napoleon Bonaparte", "")).equals(
                "Napoleon Bonaparte"));
        assertTrue(JGitUtils.getDisplayName(new PersonIdent("", "someone@somewhere.com")).equals(
                "<someone@somewhere.com>"));
        assertTrue(JGitUtils.getDisplayName(
                new PersonIdent("Napoleon Bonaparte", "someone@somewhere.com")).equals(
                "Napoleon Bonaparte <someone@somewhere.com>"));
    }
    public void testFindRepositories() {
        List<String> list = JGitUtils.getRepositoryList(null, true, true);
@@ -53,7 +69,7 @@
    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);
@@ -63,18 +79,16 @@
                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" };
@@ -84,17 +98,16 @@
                    repositoryName, isBare);
            File folder;
            if (isBare) {
                folder = new File(GitBlitSuite.REPOSITORIES, repositoryName);
                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.getLastChange(repository).getTime() == folder.lastModified());
            assertTrue(JGitUtils.getCommit(repository, null) == null);
            repository.close();
            assertTrue(GitBlit.self().deleteRepository(repositoryName));
@@ -102,6 +115,13 @@
    }
    public void testRefs() throws Exception {
        Repository repository = GitBlitSuite.getTicgitRepository();
        Map<ObjectId, List<String>> map = JGitUtils.getAllRefs(repository);
        repository.close();
        assertTrue(map.size() > 0);
    }
    public void testBranches() throws Exception {
        Repository repository = GitBlitSuite.getTicgitRepository();
        for (RefModel model : JGitUtils.getLocalBranches(repository, -1)) {
            assertTrue(model.getName().startsWith(Constants.R_HEADS));
@@ -119,6 +139,12 @@
                    + model.getName().hashCode());
            assertTrue(model.getShortLog().equals(model.commit.getShortMessage()));
        }
        assertTrue(JGitUtils.getRemoteBranches(repository, 10).size() == 10);
        repository.close();
    }
    public void testTags() throws Exception {
        Repository repository = GitBlitSuite.getTicgitRepository();
        for (RefModel model : JGitUtils.getTags(repository, -1)) {
            if (model.getObjectId().getName().equals("283035e4848054ff1803cb0e690270787dc92399")) {
                assertTrue("Not an annotated tag!", model.isAnnotatedTag());
@@ -133,23 +159,26 @@
        repository.close();
    }
    public void testRetrieveRevObject() throws Exception {
        Repository repository = GitBlitSuite.getHelloworldRepository();
        RevCommit commit = JGitUtils.getCommit(repository, Constants.HEAD);
        RevTree tree = commit.getTree();
        RevObject object = JGitUtils.getRevObject(repository, tree, "java.java");
        repository.close();
        assertTrue("Object is null!", object != null);
    public void testCommitNotes() throws Exception {
//        Repository repository = new FileRepository(new File("c:/projects/git/jgit.git/.git"));
//        RevCommit commit = JGitUtils.getCommit(repository,
//                "ada903085d1b4ef8c79e3e2d91f49fee7e188f53");
//        List<GitNote> list = JGitUtils.getNotesOnCommit(repository, commit);
//        repository.close();
//        assertTrue(list.size() > 0);
    }
    public void testRetrieveStringContent() throws Exception {
    public void testStringContent() throws Exception {
        Repository repository = GitBlitSuite.getHelloworldRepository();
        String contentA = JGitUtils.getRawContentAsString(repository, null, "java.java");
        RevCommit commit = JGitUtils.getCommit(repository, Constants.HEAD);
        RevTree tree = commit.getTree();
        RevBlob blob = (RevBlob) JGitUtils.getRevObject(repository, tree, "java.java");
        String content = JGitUtils.getRawContentAsString(repository, blob);
        String contentB = JGitUtils.getRawContentAsString(repository, commit, "java.java");
        String contentC = JGitUtils.getRawContentAsString(repository, commit, "missing.txt");
        repository.close();
        assertTrue("Content is null!", content != null && content.length() > 0);
        assertTrue("ContentA is null!", contentA != null && contentA.length() > 0);
        assertTrue("ContentB is null!", contentB != null && contentB.length() > 0);
        assertTrue(contentA.equals(contentB));
        assertTrue(contentC == null);
    }
    public void testFilesInCommit() throws Exception {
@@ -157,6 +186,15 @@
        RevCommit commit = JGitUtils.getCommit(repository,
                "1d0c2933a4ae69c362f76797d42d6bd182d05176");
        List<PathChangeModel> paths = JGitUtils.getFilesInCommit(repository, commit);
        commit = JGitUtils.getCommit(repository, "af0e9b2891fda85afc119f04a69acf7348922830");
        List<PathChangeModel> deletions = JGitUtils.getFilesInCommit(repository, commit);
        commit = JGitUtils.getFirstCommit(repository, null);
        List<PathChangeModel> additions = JGitUtils.getFilesInCommit(repository, commit);
        List<PathChangeModel> latestChanges = JGitUtils.getFilesInCommit(repository, null);
        repository.close();
        assertTrue("No changed paths found!", paths.size() == 1);
        for (PathChangeModel path : paths) {
@@ -165,17 +203,119 @@
            assertTrue("PathChangeModel equals itself failed!", path.equals(path));
            assertFalse("PathChangeModel equals string failed!", path.equals(""));
        }
        assertTrue(deletions.get(0).changeType.equals(ChangeType.DELETE));
        assertTrue(additions.get(0).changeType.equals(ChangeType.ADD));
        assertTrue(latestChanges.size() > 0);
    }
    public void testFilesInPath() throws Exception {
        assertTrue(JGitUtils.getFilesInPath(null, null, null).size() == 0);
        Repository repository = GitBlitSuite.getHelloworldRepository();
        List<PathModel> files = JGitUtils.getFilesInPath(repository, null, null);
        repository.close();
        assertTrue(files.size() > 10);
    }
    public void testDocuments() throws Exception {
        Repository repository = GitBlitSuite.getTicgitRepository();
        List<String> extensions = GitBlit.getStrings(Keys.web.markdownExtensions);
        List<PathModel> markdownDocs = JGitUtils.getDocuments(repository, extensions);
        List<PathModel> markdownDocs2 = JGitUtils.getDocuments(repository,
                Arrays.asList(new String[] { ".mkd", ".md" }));
        List<PathModel> allFiles = JGitUtils.getDocuments(repository, null);
        repository.close();
        assertTrue(markdownDocs.size() > 0);
        assertTrue(markdownDocs2.size() > 0);
        assertTrue(allFiles.size() > markdownDocs.size());
    }
    public void testFileModes() throws Exception {
        assertTrue(JGitUtils.getPermissionsFromMode(FileMode.TREE.getBits()).equals("drwxr-xr-x"));
        assertTrue(JGitUtils.getPermissionsFromMode(FileMode.REGULAR_FILE.getBits()).equals(
                "-rw-r--r--"));
        assertTrue(JGitUtils.getPermissionsFromMode(FileMode.EXECUTABLE_FILE.getBits()).equals(
                "-rwxr-xr-x"));
        assertTrue(JGitUtils.getPermissionsFromMode(FileMode.SYMLINK.getBits()).equals("symlink"));
        assertTrue(JGitUtils.getPermissionsFromMode(FileMode.GITLINK.getBits()).equals("gitlink"));
        assertTrue(JGitUtils.getPermissionsFromMode(FileMode.MISSING.getBits()).equals("missing"));
    }
    public void testRevlog() throws Exception {
        List<RevCommit> commits = JGitUtils.getRevLog(null, 10);
        assertTrue(commits.size() == 0);
        Repository repository = GitBlitSuite.getHelloworldRepository();
        // get most recent 10 commits
        commits = JGitUtils.getRevLog(repository, 10);
        assertTrue(commits.size() == 10);
        // test paging and offset by getting the 10th most recent commit
        RevCommit lastCommit = JGitUtils.getRevLog(repository, null, 9, 1).get(0);
        assertTrue(commits.get(9).equals(lastCommit));
        // grab the two most recent commits to java.java
        commits = JGitUtils.getRevLog(repository, null, "java.java", 0, 2);
        assertTrue(commits.size() == 2);
        repository.close();
    }
    public void testSearchTypes() throws Exception {
        assertTrue(SearchType.forName("commit").equals(SearchType.COMMIT));
        assertTrue(SearchType.forName("committer").equals(SearchType.COMMITTER));
        assertTrue(SearchType.forName("author").equals(SearchType.AUTHOR));
        assertTrue(SearchType.forName("unknown").equals(SearchType.COMMIT));
        assertTrue(SearchType.COMMIT.toString().equals("commit"));
        assertTrue(SearchType.COMMITTER.toString().equals("committer"));
        assertTrue(SearchType.AUTHOR.toString().equals("author"));
    }
    public void testSearchRevlogs() throws Exception {
        List<RevCommit> results = JGitUtils.searchRevlogs(null, null, "java", SearchType.COMMIT, 0,
                3);
        assertTrue(results.size() == 0);
        // test commit message search
        Repository repository = GitBlitSuite.getHelloworldRepository();
        results = JGitUtils.searchRevlogs(repository, null, "java", SearchType.COMMIT, 0, 3);
        assertTrue(results.size() == 3);
        // test author search
        results = JGitUtils.searchRevlogs(repository, null, "timothy", SearchType.AUTHOR, 0, -1);
        assertTrue(results.size() == 1);
        // test committer search
        results = JGitUtils.searchRevlogs(repository, null, "mike", SearchType.COMMITTER, 0, 10);
        assertTrue(results.size() == 10);
        // test paging and offset
        RevCommit commit = JGitUtils.searchRevlogs(repository, null, "mike", SearchType.COMMITTER,
                9, 1).get(0);
        assertTrue(results.get(9).equals(commit));
        repository.close();
    }
    public void testZip() throws Exception {
        assertFalse(JGitUtils.zip(null, null, null, null));
        Repository repository = GitBlitSuite.getHelloworldRepository();
        File zipFile = new File(GitBlitSuite.REPOSITORIES, "helloworld.zip");
        FileOutputStream fos = new FileOutputStream(zipFile);
        boolean success = JGitUtils.zip(repository, null, Constants.HEAD, fos);
        assertTrue("Failed to generate zip file!", success);
        assertTrue(zipFile.length() > 0);
        fos.close();
        zipFile.delete();
        File zipFileA = new File(GitBlitSuite.REPOSITORIES, "helloworld.zip");
        FileOutputStream fosA = new FileOutputStream(zipFileA);
        boolean successA = JGitUtils.zip(repository, null, Constants.HEAD, fosA);
        fosA.close();
        File zipFileB = new File(GitBlitSuite.REPOSITORIES, "helloworld-java.zip");
        FileOutputStream fosB = new FileOutputStream(zipFileB);
        boolean successB = JGitUtils.zip(repository, "java.java", Constants.HEAD, fosB);
        fosB.close();
        repository.close();
        assertTrue("Failed to generate zip file!", successA);
        assertTrue(zipFileA.length() > 0);
        zipFileA.delete();
        assertTrue("Failed to generate zip file!", successB);
        assertTrue(zipFileB.length() > 0);
        zipFileB.delete();
    }
}
tests/com/gitblit/tests/MarkdownUtilsTest.java
New file
@@ -0,0 +1,36 @@
/*
 * 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.tests;
import java.text.ParseException;
import junit.framework.TestCase;
import com.gitblit.utils.MarkdownUtils;
public class MarkdownUtilsTest extends TestCase {
    public void testMarkdown() throws Exception {
        assertTrue(MarkdownUtils.transformMarkdown("# H1").equals("<h1> H1</h1>"));
        assertTrue(MarkdownUtils.transformMarkdown("## H2").equals("<h2> H2</h2>"));
        try {
            MarkdownUtils.transformMarkdown((String) null);
            assertTrue(false);
        } catch (ParseException p) {
            assertTrue(p != null);
        }
    }
}
tests/com/gitblit/tests/MetricUtilsTest.java
@@ -28,8 +28,17 @@
    public void testMetrics() throws Exception {
        Repository repository = GitBlitSuite.getHelloworldRepository();
        List<Metric> metrics = MetricUtils.getDateMetrics(repository, true);
        List<Metric> metrics = MetricUtils.getDateMetrics(repository, true, null);
        repository.close();
        assertTrue("No metrics found!", metrics.size() > 0);
        assertTrue("No date metrics found!", metrics.size() > 0);
    }
    public void testAuthorMetrics() throws Exception {
        Repository repository = GitBlitSuite.getHelloworldRepository();
        List<Metric> byEmail = MetricUtils.getAuthorMetrics(repository, true);
        List<Metric> byName = MetricUtils.getAuthorMetrics(repository, false);
        repository.close();
        assertTrue("No author metrics found!", byEmail.size() == 9);
        assertTrue("No author metrics found!", byName.size() == 8);
    }
}
tests/com/gitblit/tests/TicgitUtilsTest.java
@@ -28,10 +28,20 @@
public class TicgitUtilsTest extends TestCase {
    public void testTicGit() throws Exception {
    public void testTicgitBranch() throws Exception {
        Repository repository = GitBlitSuite.getTicgitRepository();
        RefModel branch = TicgitUtils.getTicketsBranch(repository);
        repository.close();
        assertTrue("Ticgit branch does not exist!", branch != null);
        repository = GitBlitSuite.getHelloworldRepository();
        branch = TicgitUtils.getTicketsBranch(repository);
        repository.close();
        assertTrue("Ticgit branch exists!", branch == null);
    }
    public void testRetrieveTickets() throws Exception {
        Repository repository = GitBlitSuite.getTicgitRepository();
        List<TicketModel> ticketsA = TicgitUtils.getTickets(repository);
        List<TicketModel> ticketsB = TicgitUtils.getTickets(repository);
        repository.close();
@@ -50,5 +60,21 @@
                assertTrue(commentA.hashCode() == commentA.text.hashCode());
            }
        }
        repository = GitBlitSuite.getHelloworldRepository();
        List<TicketModel> ticketsC = TicgitUtils.getTickets(repository);
        repository.close();
        assertTrue(ticketsC == null);
    }
    public void testReadTicket() throws Exception {
        Repository repository = GitBlitSuite.getTicgitRepository();
        List<TicketModel> tickets = TicgitUtils.getTickets(repository);
        TicketModel ticket = TicgitUtils
                .getTicket(repository, tickets.get(tickets.size() - 1).name);
        repository.close();
        assertTrue(ticket != null);
        assertTrue(ticket.name
                .equals("1254123752_comments-on-ticgits-longer-than-5-lines-can-t-be-viewed-entirely_266"));
    }
}