From: Milos Cubrilo Date: Sun, 11 Jan 2015 12:41:29 +0000 (+0100) Subject: #230 - Improve empty folder navigation. X-Git-Tag: v1.7.0~1^2~91^2 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=refs%2Fpull%2F241%2Fhead;p=gitblit.git #230 - Improve empty folder navigation. Empty folders are automatically skipped when browsing repository tree (similar to github "folder jumping" feature). --- diff --git a/src/main/java/com/gitblit/utils/JGitUtils.java b/src/main/java/com/gitblit/utils/JGitUtils.java index 68c62ead..69084ca1 100644 --- a/src/main/java/com/gitblit/utils/JGitUtils.java +++ b/src/main/java/com/gitblit/utils/JGitUtils.java @@ -30,6 +30,7 @@ import java.util.Map; 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; @@ -890,6 +891,63 @@ public class JGitUtils { return list; } + /** + * 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 getFilesInPath2(Repository repository, String path, RevCommit commit) { + + List list = new ArrayList(); + 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 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. @@ -1123,6 +1181,46 @@ public class JGitUtils { objectId.getName(), commit.getName()); } + /** + * 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. * @@ -2194,97 +2292,97 @@ public class JGitUtils { } return false; } - - /** - * Returns true if the commit identified by commitId is an ancestor or the - * the commit identified by tipId. - * - * @param repository - * @param commitId - * @param tipId - * @return true if there is the commit is an ancestor of the tip - */ - public static boolean isMergedInto(Repository repository, String commitId, String tipId) { - try { - return isMergedInto(repository, repository.resolve(commitId), repository.resolve(tipId)); - } catch (Exception e) { - LOGGER.error("Failed to determine isMergedInto", e); - } - return false; - } - - /** - * Returns true if the commit identified by commitId is an ancestor or the - * the commit identified by tipId. - * - * @param repository - * @param commitId - * @param tipId - * @return true if there is the commit is an ancestor of the tip - */ - public static boolean isMergedInto(Repository repository, ObjectId commitId, ObjectId tipCommitId) { - // traverse the revlog looking for a commit chain between the endpoints - RevWalk rw = new RevWalk(repository); - try { - // must re-lookup RevCommits to workaround undocumented RevWalk bug - RevCommit tip = rw.lookupCommit(tipCommitId); - RevCommit commit = rw.lookupCommit(commitId); - return rw.isMergedInto(commit, tip); - } catch (Exception e) { - LOGGER.error("Failed to determine isMergedInto", e); - } finally { - rw.dispose(); - } - return false; - } - - /** - * Returns the merge base of two commits or null if there is no common - * ancestry. - * - * @param repository - * @param commitIdA - * @param commitIdB - * @return the commit id of the merge base or null if there is no common base - */ - public static String getMergeBase(Repository repository, ObjectId commitIdA, ObjectId commitIdB) { - RevWalk rw = new RevWalk(repository); - try { - RevCommit a = rw.lookupCommit(commitIdA); - RevCommit b = rw.lookupCommit(commitIdB); - - rw.setRevFilter(RevFilter.MERGE_BASE); - rw.markStart(a); - rw.markStart(b); - RevCommit mergeBase = rw.next(); - if (mergeBase == null) { - return null; - } - return mergeBase.getName(); - } catch (Exception e) { - LOGGER.error("Failed to determine merge base", e); - } finally { - rw.dispose(); - } - return null; - } - - public static enum MergeStatus { - MISSING_INTEGRATION_BRANCH, MISSING_SRC_BRANCH, NOT_MERGEABLE, FAILED, ALREADY_MERGED, MERGEABLE, MERGED; - } - - /** - * Determines if we can cleanly merge one branch into another. Returns true - * if we can merge without conflict, otherwise returns false. - * - * @param repository - * @param src - * @param toBranch - * @return true if we can merge without conflict - */ - public static MergeStatus canMerge(Repository repository, String src, String toBranch) { - RevWalk revWalk = null; - try { + + /** + * Returns true if the commit identified by commitId is an ancestor or the + * the commit identified by tipId. + * + * @param repository + * @param commitId + * @param tipId + * @return true if there is the commit is an ancestor of the tip + */ + public static boolean isMergedInto(Repository repository, String commitId, String tipId) { + try { + return isMergedInto(repository, repository.resolve(commitId), repository.resolve(tipId)); + } catch (Exception e) { + LOGGER.error("Failed to determine isMergedInto", e); + } + return false; + } + + /** + * Returns true if the commit identified by commitId is an ancestor or the + * the commit identified by tipId. + * + * @param repository + * @param commitId + * @param tipId + * @return true if there is the commit is an ancestor of the tip + */ + public static boolean isMergedInto(Repository repository, ObjectId commitId, ObjectId tipCommitId) { + // traverse the revlog looking for a commit chain between the endpoints + RevWalk rw = new RevWalk(repository); + try { + // must re-lookup RevCommits to workaround undocumented RevWalk bug + RevCommit tip = rw.lookupCommit(tipCommitId); + RevCommit commit = rw.lookupCommit(commitId); + return rw.isMergedInto(commit, tip); + } catch (Exception e) { + LOGGER.error("Failed to determine isMergedInto", e); + } finally { + rw.dispose(); + } + return false; + } + + /** + * Returns the merge base of two commits or null if there is no common + * ancestry. + * + * @param repository + * @param commitIdA + * @param commitIdB + * @return the commit id of the merge base or null if there is no common base + */ + public static String getMergeBase(Repository repository, ObjectId commitIdA, ObjectId commitIdB) { + RevWalk rw = new RevWalk(repository); + try { + RevCommit a = rw.lookupCommit(commitIdA); + RevCommit b = rw.lookupCommit(commitIdB); + + rw.setRevFilter(RevFilter.MERGE_BASE); + rw.markStart(a); + rw.markStart(b); + RevCommit mergeBase = rw.next(); + if (mergeBase == null) { + return null; + } + return mergeBase.getName(); + } catch (Exception e) { + LOGGER.error("Failed to determine merge base", e); + } finally { + rw.dispose(); + } + return null; + } + + public static enum MergeStatus { + MISSING_INTEGRATION_BRANCH, MISSING_SRC_BRANCH, NOT_MERGEABLE, FAILED, ALREADY_MERGED, MERGEABLE, MERGED; + } + + /** + * Determines if we can cleanly merge one branch into another. Returns true + * if we can merge without conflict, otherwise returns false. + * + * @param repository + * @param src + * @param toBranch + * @return true if we can merge without conflict + */ + public static MergeStatus canMerge(Repository repository, String src, String toBranch) { + RevWalk revWalk = null; + try { revWalk = new RevWalk(repository); ObjectId branchId = repository.resolve(toBranch); if (branchId == null) { @@ -2294,122 +2392,122 @@ public class JGitUtils { if (srcId == null) { return MergeStatus.MISSING_SRC_BRANCH; } - RevCommit branchTip = revWalk.lookupCommit(branchId); - RevCommit srcTip = revWalk.lookupCommit(srcId); - if (revWalk.isMergedInto(srcTip, branchTip)) { - // already merged - return MergeStatus.ALREADY_MERGED; - } else if (revWalk.isMergedInto(branchTip, srcTip)) { - // fast-forward - return MergeStatus.MERGEABLE; - } - RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true); - boolean canMerge = merger.merge(branchTip, srcTip); - if (canMerge) { - return MergeStatus.MERGEABLE; - } + RevCommit branchTip = revWalk.lookupCommit(branchId); + RevCommit srcTip = revWalk.lookupCommit(srcId); + if (revWalk.isMergedInto(srcTip, branchTip)) { + // already merged + return MergeStatus.ALREADY_MERGED; + } else if (revWalk.isMergedInto(branchTip, srcTip)) { + // fast-forward + return MergeStatus.MERGEABLE; + } + RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true); + boolean canMerge = merger.merge(branchTip, srcTip); + if (canMerge) { + return MergeStatus.MERGEABLE; + } } catch (NullPointerException e) { LOGGER.error("Failed to determine canMerge", e); - } catch (IOException e) { - LOGGER.error("Failed to determine canMerge", e); + } catch (IOException e) { + LOGGER.error("Failed to determine canMerge", e); } finally { - if (revWalk != null) { + if (revWalk != null) { revWalk.release(); - } - } - return MergeStatus.NOT_MERGEABLE; - } - - - public static class MergeResult { - public final MergeStatus status; - public final String sha; - - MergeResult(MergeStatus status, String sha) { - this.status = status; - this.sha = sha; - } - } - - /** - * Tries to merge a commit into a branch. If there are conflicts, the merge - * will fail. - * - * @param repository - * @param src - * @param toBranch - * @param committer - * @param message - * @return the merge result - */ - public static MergeResult merge(Repository repository, String src, String toBranch, - PersonIdent committer, String message) { - - if (!toBranch.startsWith(Constants.R_REFS)) { - // branch ref doesn't start with ref, assume this is a branch head - toBranch = Constants.R_HEADS + toBranch; - } - - RevWalk revWalk = null; - try { - revWalk = new RevWalk(repository); - RevCommit branchTip = revWalk.lookupCommit(repository.resolve(toBranch)); - RevCommit srcTip = revWalk.lookupCommit(repository.resolve(src)); - if (revWalk.isMergedInto(srcTip, branchTip)) { - // already merged - return new MergeResult(MergeStatus.ALREADY_MERGED, null); - } - RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true); - boolean merged = merger.merge(branchTip, srcTip); - if (merged) { - // create a merge commit and a reference to track the merge commit - ObjectId treeId = merger.getResultTreeId(); - ObjectInserter odi = repository.newObjectInserter(); - try { - // Create a commit object - CommitBuilder commitBuilder = new CommitBuilder(); - commitBuilder.setCommitter(committer); - commitBuilder.setAuthor(committer); - commitBuilder.setEncoding(Constants.CHARSET); - if (StringUtils.isEmpty(message)) { - message = MessageFormat.format("merge {0} into {1}", srcTip.getName(), branchTip.getName()); - } - commitBuilder.setMessage(message); - commitBuilder.setParentIds(branchTip.getId(), srcTip.getId()); - commitBuilder.setTreeId(treeId); - - // Insert the merge commit into the repository - ObjectId mergeCommitId = odi.insert(commitBuilder); - odi.flush(); - - // set the merge ref to the merge commit - RevCommit mergeCommit = revWalk.parseCommit(mergeCommitId); - RefUpdate mergeRefUpdate = repository.updateRef(toBranch); - mergeRefUpdate.setNewObjectId(mergeCommitId); - mergeRefUpdate.setRefLogMessage("commit: " + mergeCommit.getShortMessage(), false); - RefUpdate.Result rc = mergeRefUpdate.update(); - switch (rc) { - case FAST_FORWARD: - // successful, clean merge + } + } + return MergeStatus.NOT_MERGEABLE; + } + + + public static class MergeResult { + public final MergeStatus status; + public final String sha; + + MergeResult(MergeStatus status, String sha) { + this.status = status; + this.sha = sha; + } + } + + /** + * Tries to merge a commit into a branch. If there are conflicts, the merge + * will fail. + * + * @param repository + * @param src + * @param toBranch + * @param committer + * @param message + * @return the merge result + */ + public static MergeResult merge(Repository repository, String src, String toBranch, + PersonIdent committer, String message) { + + if (!toBranch.startsWith(Constants.R_REFS)) { + // branch ref doesn't start with ref, assume this is a branch head + toBranch = Constants.R_HEADS + toBranch; + } + + RevWalk revWalk = null; + try { + revWalk = new RevWalk(repository); + RevCommit branchTip = revWalk.lookupCommit(repository.resolve(toBranch)); + RevCommit srcTip = revWalk.lookupCommit(repository.resolve(src)); + if (revWalk.isMergedInto(srcTip, branchTip)) { + // already merged + return new MergeResult(MergeStatus.ALREADY_MERGED, null); + } + RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true); + boolean merged = merger.merge(branchTip, srcTip); + if (merged) { + // create a merge commit and a reference to track the merge commit + ObjectId treeId = merger.getResultTreeId(); + ObjectInserter odi = repository.newObjectInserter(); + try { + // Create a commit object + CommitBuilder commitBuilder = new CommitBuilder(); + commitBuilder.setCommitter(committer); + commitBuilder.setAuthor(committer); + commitBuilder.setEncoding(Constants.CHARSET); + if (StringUtils.isEmpty(message)) { + message = MessageFormat.format("merge {0} into {1}", srcTip.getName(), branchTip.getName()); + } + commitBuilder.setMessage(message); + commitBuilder.setParentIds(branchTip.getId(), srcTip.getId()); + commitBuilder.setTreeId(treeId); + + // Insert the merge commit into the repository + ObjectId mergeCommitId = odi.insert(commitBuilder); + odi.flush(); + + // set the merge ref to the merge commit + RevCommit mergeCommit = revWalk.parseCommit(mergeCommitId); + RefUpdate mergeRefUpdate = repository.updateRef(toBranch); + mergeRefUpdate.setNewObjectId(mergeCommitId); + mergeRefUpdate.setRefLogMessage("commit: " + mergeCommit.getShortMessage(), false); + RefUpdate.Result rc = mergeRefUpdate.update(); + switch (rc) { + case FAST_FORWARD: + // successful, clean merge break; - default: - throw new GitBlitException(MessageFormat.format("Unexpected result \"{0}\" when merging commit {1} into {2} in {3}", - rc.name(), srcTip.getName(), branchTip.getName(), repository.getDirectory())); - } - - // return the merge commit id - return new MergeResult(MergeStatus.MERGED, mergeCommitId.getName()); - } finally { - odi.release(); - } - } - } catch (IOException e) { - LOGGER.error("Failed to merge", e); + default: + throw new GitBlitException(MessageFormat.format("Unexpected result \"{0}\" when merging commit {1} into {2} in {3}", + rc.name(), srcTip.getName(), branchTip.getName(), repository.getDirectory())); + } + + // return the merge commit id + return new MergeResult(MergeStatus.MERGED, mergeCommitId.getName()); + } finally { + odi.release(); + } + } + } catch (IOException e) { + LOGGER.error("Failed to merge", e); } finally { - if (revWalk != null) { + if (revWalk != null) { revWalk.release(); - } - } - return new MergeResult(MergeStatus.FAILED, null); - } + } + } + return new MergeResult(MergeStatus.FAILED, null); + } } diff --git a/src/main/java/com/gitblit/utils/PathUtils.java b/src/main/java/com/gitblit/utils/PathUtils.java new file mode 100644 index 00000000..a3c7d8d6 --- /dev/null +++ b/src/main/java/com/gitblit/utils/PathUtils.java @@ -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 compressPaths(final Iterable paths) { + + ArrayList pathList = new ArrayList<>(); + Map> 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 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 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 tmp = Arrays.asList(pathComponents); + return Joiner.on('/').join(tmp.subList(0,tmp.size()-1)) + '/'; + } + + + private static String longestCommonSequence(final List 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(); + } + +} diff --git a/src/main/java/com/gitblit/wicket/pages/TreePage.java b/src/main/java/com/gitblit/wicket/pages/TreePage.java index 9ddbecf6..d7899dcb 100644 --- a/src/main/java/com/gitblit/wicket/pages/TreePage.java +++ b/src/main/java/com/gitblit/wicket/pages/TreePage.java @@ -52,7 +52,7 @@ public class TreePage extends RepositoryPage { Repository r = getRepository(); RevCommit commit = getCommit(); - List paths = JGitUtils.getFilesInPath(r, path, commit); + List paths = JGitUtils.getFilesInPath2(r, path, commit); // tree page links add(new BookmarkablePageLink("historyLink", HistoryPage.class, diff --git a/src/test/java/com/gitblit/tests/GitBlitSuite.java b/src/test/java/com/gitblit/tests/GitBlitSuite.java index f2dfcc0c..bf6834d3 100644 --- a/src/test/java/com/gitblit/tests/GitBlitSuite.java +++ b/src/test/java/com/gitblit/tests/GitBlitSuite.java @@ -63,9 +63,9 @@ import com.gitblit.utils.JGitUtils; GitBlitTest.class, FederationTests.class, RpcTests.class, GitServletTest.class, GitDaemonTest.class, SshDaemonTest.class, GroovyScriptTest.class, LuceneExecutorTest.class, RepositoryModelTest.class, FanoutServiceTest.class, Issue0259Test.class, Issue0271Test.class, HtpasswdAuthenticationTest.class, - ModelUtilsTest.class, JnaUtilsTest.class, LdapSyncServiceTest.class, FileTicketServiceTest.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"); @@ -110,11 +110,11 @@ public class GitBlitSuite { return getRepository("test/gitective.git"); } - public static Repository getTicketsTestRepository() { - JGitUtils.createRepository(REPOSITORIES, "gb-tickets.git").close(); - return getRepository("gb-tickets.git"); - } - + public static Repository getTicketsTestRepository() { + JGitUtils.createRepository(REPOSITORIES, "gb-tickets.git").close(); + return getRepository("gb-tickets.git"); + } + private static Repository getRepository(String name) { try { File gitDir = FileKey.resolve(new File(REPOSITORIES, name), FS.DETECTED); diff --git a/src/test/java/com/gitblit/tests/JGitUtilsTest.java b/src/test/java/com/gitblit/tests/JGitUtilsTest.java index c2cfb331..2cf4a5a1 100644 --- a/src/test/java/com/gitblit/tests/JGitUtilsTest.java +++ b/src/test/java/com/gitblit/tests/JGitUtilsTest.java @@ -475,6 +475,15 @@ public class JGitUtilsTest extends GitblitUnitTest { assertTrue(files.size() > 10); } + @Test + public void testFilesInPath2() throws Exception { + assertEquals(0, JGitUtils.getFilesInPath2(null, null, null).size()); + Repository repository = GitBlitSuite.getHelloworldRepository(); + List files = JGitUtils.getFilesInPath2(repository, null, null); + repository.close(); + assertTrue(files.size() > 10); + } + @Test public void testDocuments() throws Exception { Repository repository = GitBlitSuite.getTicgitRepository(); diff --git a/src/test/java/com/gitblit/tests/PathUtilsTest.java b/src/test/java/com/gitblit/tests/PathUtilsTest.java new file mode 100644 index 00000000..209b8ee6 --- /dev/null +++ b/src/test/java/com/gitblit/tests/PathUtilsTest.java @@ -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("/"), "/"); + } + +} \ No newline at end of file