James Moger
2015-01-13 ffe9423942a8a8c8f69d7b77c6a59e6435002d83
Merged #234 "#230 - Improve empty folder navigation."
2 files added
4 files modified
269 ■■■■■ changed files
src/main/java/com/gitblit/utils/JGitUtils.java 98 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/PathUtils.java 92 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/TreePage.java 2 ●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/GitBlitSuite.java 2 ●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/JGitUtilsTest.java 9 ●●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/PathUtilsTest.java 66 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/JGitUtils.java
@@ -30,6 +30,7 @@
import java.util.Map.Entry;
import java.util.regex.Pattern;
import com.google.common.base.Strings;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.FetchCommand;
@@ -891,6 +892,63 @@
    }
    /**
     * Returns the list of files in the specified folder at the specified
     * commit. If the repository does not exist or is empty, an empty list is
     * returned.
     *
     * This is modified version that implements path compression feature.
     *
     * @param repository
     * @param path
     *            if unspecified, root folder is assumed.
     * @param commit
     *            if null, HEAD is assumed.
     * @return list of files in specified path
     */
    public static List<PathModel> getFilesInPath2(Repository repository, String path, RevCommit commit) {
        List<PathModel> list = new ArrayList<PathModel>();
        if (!hasCommits(repository)) {
            return list;
        }
        if (commit == null) {
            commit = getCommit(repository, null);
        }
        final TreeWalk tw = new TreeWalk(repository);
        try {
            tw.addTree(commit.getTree());
            final boolean isPathEmpty = Strings.isNullOrEmpty(path);
            if (!isPathEmpty) {
                PathFilter f = PathFilter.create(path);
                tw.setFilter(f);
            }
            tw.setRecursive(true);
            List<String> paths = new ArrayList<>();
            while (tw.next()) {
                    String child = isPathEmpty ? tw.getPathString()
                            : tw.getPathString().replaceFirst(String.format("%s/", path), "");
                    paths.add(child);
            }
            for(String p: PathUtils.compressPaths(paths)) {
                String pathString = isPathEmpty ? p : String.format("%s/%s", path, p);
                list.add(getPathModel(repository, pathString, path, commit));
            }
        } catch (IOException e) {
            error(e, repository, "{0} failed to get files for commit {1}", commit.getName());
        } finally {
            tw.release();
        }
        Collections.sort(list);
        return list;
    }
    /**
     * Returns the list of files changed in a specified commit. If the
     * repository does not exist or is empty, an empty list is returned.
     *
@@ -1124,6 +1182,46 @@
    }
    /**
     * Returns a path model by path string
     *
     * @param repo
     * @param path
     * @param filter
     * @param commit
     * @return a path model of the specified object
     */
    private static PathModel getPathModel(Repository repo, String path, String filter, RevCommit commit)
            throws IOException {
        long size = 0;
        TreeWalk tw = TreeWalk.forPath(repo, path, commit.getTree());
        String pathString = path;
            if (!tw.isSubtree() && (tw.getFileMode(0) != FileMode.GITLINK)) {
                size = tw.getObjectReader().getObjectSize(tw.getObjectId(0), Constants.OBJ_BLOB);
                pathString = PathUtils.getLastPathComponent(pathString);
            } else if (tw.isSubtree()) {
                // do not display dirs that are behind in the path
                if (!Strings.isNullOrEmpty(filter)) {
                    pathString = path.replaceFirst(filter + "/", "");
                }
                // remove the last slash from path in displayed link
                if (pathString != null && pathString.charAt(pathString.length()-1) == '/') {
                    pathString = pathString.substring(0, pathString.length()-1);
                }
            }
            return new PathModel(pathString, tw.getPathString(), size, tw.getFileMode(0).getBits(),
                    tw.getObjectId(0).getName(), commit.getName());
    }
    /**
     * Returns a permissions representation of the mode bits.
     *
     * @param mode
src/main/java/com/gitblit/utils/PathUtils.java
New file
@@ -0,0 +1,92 @@
package com.gitblit.utils;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import java.util.*;
/**
 *  Utils for handling path strings
 *
 */
public class PathUtils {
    private PathUtils() {}
    /**
     *  Compress paths containing no files
     *
     * @param paths lines from `git ls-tree -r --name-only ${branch}`
     * @return compressed paths
     */
    public static List<String> compressPaths(final Iterable<String> paths)  {
        ArrayList<String> pathList = new ArrayList<>();
        Map<String, List<String[]>> folderRoots = new LinkedHashMap<>();
        for (String s: paths) {
            String[] components = s.split("/");
            // File in current directory
            if (components.length == 1) {
                pathList.add(components[0]);
                // Directory path
            } else {
                List<String[]> rootedPaths = folderRoots.get(components[0]);
                if (rootedPaths == null) {
                    rootedPaths = new ArrayList<>();
                }
                rootedPaths.add(components);
                folderRoots.put(components[0], rootedPaths);
            }
        }
        for (String folder: folderRoots.keySet()) {
            List<String[]> matchingPaths = folderRoots.get(folder);
            if (matchingPaths.size() == 1) {
                pathList.add(toStringPath(matchingPaths.get(0)));
            } else {
                pathList.add(longestCommonSequence(matchingPaths));
            }
        }
        return pathList;
    }
    /**
     *  Get last path component
     *
     *
     * @param path string path separated by slashes
     * @return rightmost entry
     */
    public static String getLastPathComponent(final String path) {
        return Iterables.getLast(Splitter.on("/").omitEmptyStrings().split(path), path);
    }
    private static String toStringPath(final String[] pathComponents) {
        List<String> tmp = Arrays.asList(pathComponents);
        return Joiner.on('/').join(tmp.subList(0,tmp.size()-1)) + '/';
    }
    private static String longestCommonSequence(final List<String[]> paths) {
        StringBuilder path = new StringBuilder();
        for (int i = 0; i < paths.get(0).length; i++) {
            String current = paths.get(0)[i];
            for (int j = 1; j < paths.size(); j++) {
                if (!current.equals(paths.get(j)[i])) {
                    return path.toString();
                }
            }
            path.append(current);
            path.append('/');
        }
        return path.toString();
    }
}
src/main/java/com/gitblit/wicket/pages/TreePage.java
@@ -52,7 +52,7 @@
        Repository r = getRepository();
        RevCommit commit = getCommit();
        List<PathModel> paths = JGitUtils.getFilesInPath(r, path, commit);
        List<PathModel> paths = JGitUtils.getFilesInPath2(r, path, commit);
        // tree page links
        add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
src/test/java/com/gitblit/tests/GitBlitSuite.java
@@ -65,7 +65,7 @@
        FanoutServiceTest.class, Issue0259Test.class, Issue0271Test.class, HtpasswdAuthenticationTest.class,
        ModelUtilsTest.class, JnaUtilsTest.class, LdapSyncServiceTest.class, FileTicketServiceTest.class,
        BranchTicketServiceTest.class, RedisTicketServiceTest.class, AuthenticationManagerTest.class,
        SshKeysDispatcherTest.class, UITicketTest.class })
        SshKeysDispatcherTest.class, UITicketTest.class, PathUtilsTest.class })
public class GitBlitSuite {
    public static final File BASEFOLDER = new File("data");
src/test/java/com/gitblit/tests/JGitUtilsTest.java
@@ -476,6 +476,15 @@
    }
    @Test
    public void testFilesInPath2() throws Exception {
        assertEquals(0, JGitUtils.getFilesInPath2(null, null, null).size());
        Repository repository = GitBlitSuite.getHelloworldRepository();
        List<PathModel> files = JGitUtils.getFilesInPath2(repository, null, null);
        repository.close();
        assertTrue(files.size() > 10);
    }
    @Test
    public void testDocuments() throws Exception {
        Repository repository = GitBlitSuite.getTicgitRepository();
        List<String> extensions = Arrays.asList(new String[] { ".mkd", ".md" });
src/test/java/com/gitblit/tests/PathUtilsTest.java
New file
@@ -0,0 +1,66 @@
/*
 * 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 com.gitblit.utils.PathUtils;
import org.junit.Test;
import java.util.Arrays;
public class PathUtilsTest extends GitblitUnitTest {
    private static final String[][][] testData = {
            {
                    // Folder contents
                    {".gitignore","src/main/java/a.java", "src/main/java/b.java", "docs/c.md"},
                    // Expected after compressing
                    {".gitignore", "src/main/java/", "docs/"}
            },
            {
                    {".gitignore","src/main/java/a.java", "src/main/b.java", "docs/c.md"},
                    {".gitignore", "src/main/", "docs/"}
            },
            {
                    {".gitignore","src/x.java","src/main/java/a.java", "src/main/java/b.java", "docs/c.md"},
                    {".gitignore", "src/", "docs/"}
            },
    };
    @Test
    public void testCompressPaths() throws Exception {
        for (String[][] test : testData ) {
            assertArrayEquals(test[1], PathUtils.compressPaths(Arrays.asList(test[0])).toArray(new String[]{}));
        }
    }
    @Test
    public void testGetLastPathComponent() {
        assertEquals(PathUtils.getLastPathComponent("/a/b/c/d/e.out"), "e.out");
        assertEquals(PathUtils.getLastPathComponent("e.out"), "e.out");
        assertEquals(PathUtils.getLastPathComponent("/a/b/c/d/"), "d");
        assertEquals(PathUtils.getLastPathComponent("/a/b/c/d"), "d");
        assertEquals(PathUtils.getLastPathComponent("/"), "/");
    }
}