From e8506ff14de23b82898a9f1fa2d203d7e03dfd17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 7 Oct 2020 15:36:05 +0200 Subject: [PATCH] Fix recursive iteration Due to the api of TreeWalk we have no real change to iterate this recursively, so we get back to good old loops. --- CHANGELOG.md | 3 ++ .../scm/repository/spi/GitBrowseCommand.java | 45 +++++++++--------- .../repository/spi/GitBrowseCommandTest.java | 44 +++++++++++++++++ .../scm/repository/spi/scm-git-spi-test.zip | Bin 46664 -> 48326 bytes 4 files changed, 69 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5eb83ea16..8ecd0e849d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## unreleased +- Fix recursive browse command for git ([#1361](https://github.com/scm-manager/scm-manager/pull/1361)) + ## [2.6.1] - 2020-09-30 ### Fixed - Not found error when using browse command in empty hg repository ([#1355](https://github.com/scm-manager/scm-manager/pull/1355)) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java index 05792b9707..b2b77cf41c 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java @@ -24,8 +24,6 @@ package sonia.scm.repository.spi; -//~--- non-JDK imports -------------------------------------------------------- - import com.google.common.base.Stopwatch; import com.google.common.base.Strings; import com.google.common.collect.Lists; @@ -66,17 +64,15 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Stack; import java.util.function.Consumer; -import static java.util.Collections.emptyList; import static java.util.Optional.empty; import static java.util.Optional.of; import static sonia.scm.ContextEntry.ContextBuilder.entity; import static sonia.scm.NotFoundException.notFound; import static sonia.scm.repository.spi.SyncAsyncExecutor.ExecutionType.ASYNCHRONOUS; -//~--- JDK imports ------------------------------------------------------------ - /** * * @author Sebastian Sdorra @@ -251,7 +247,7 @@ public class GitBrowseCommand extends AbstractGitCommand private void findChildren(FileObject parent, TreeWalk treeWalk) throws IOException { TreeEntry entry = new TreeEntry(); - createTree(parent.getPath(), entry, treeWalk); + createTree(entry, treeWalk); convertToFileObject(parent, entry.getChildren()); } @@ -282,25 +278,22 @@ public class GitBrowseCommand extends AbstractGitCommand parent.setTruncated(hasNext); } - private Optional createTree(String path, TreeEntry parent, TreeWalk treeWalk) throws IOException { - List entries = new ArrayList<>(); + private void createTree(TreeEntry parent, TreeWalk treeWalk) throws IOException { + Stack parents = new Stack<>(); + parents.push(parent); while (treeWalk.next()) { - TreeEntry treeEntry = new TreeEntry(repo, treeWalk); - if (!treeEntry.getPathString().startsWith(path)) { - parent.setChildren(entries); - return of(treeEntry); + final String currentPath = treeWalk.getPathString(); + while (!currentPath.startsWith(parents.peek().pathString)) { + parents.pop(); } - - entries.add(treeEntry); - + TreeEntry currentParent = parents.peek(); + TreeEntry treeEntry = new TreeEntry(repo, treeWalk); + currentParent.addChild(treeEntry); if (request.isRecursive() && treeEntry.getType() == TreeType.DIRECTORY) { treeWalk.enterSubtree(); - Optional surplus = createTree(treeEntry.getNameString(), treeEntry, treeWalk); - surplus.ifPresent(entries::add); + parents.push(treeEntry); } } - parent.setChildren(entries); - return empty(); } private FileObject findFirstMatch(TreeWalk treeWalk) throws IOException { @@ -465,7 +458,9 @@ public class GitBrowseCommand extends AbstractGitCommand private final ObjectId objectId; private final TreeType type; private final SubRepository subRepository; - private List children = emptyList(); + private final List children = new ArrayList<>(); + + private boolean sorted = true; TreeEntry() { pathString = ""; @@ -513,12 +508,16 @@ public class GitBrowseCommand extends AbstractGitCommand } List getChildren() { + if (!sorted) { + sort(children, entry -> entry.type != TreeType.FILE, TreeEntry::getNameString); + sorted = true; + } return children; } - void setChildren(List children) { - sort(children, entry -> entry.type != TreeType.FILE, TreeEntry::getNameString); - this.children = children; + private void addChild(TreeEntry treeEntry) { + sorted = false; + children.add(treeEntry); } } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java index 0ba57cbada..43433c7652 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java @@ -346,6 +346,50 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase { .containsExactly("e.txt"); } + @Test + public void testRecursionWithDeepPaths() throws IOException { + BrowseCommandRequest request = new BrowseCommandRequest(); + request.setRevision("deep-folders"); + request.setRecursive(true); + + FileObject root = createCommand().getBrowserResult(request).getFile(); + + assertThat(root.getChildren()) + .extracting("name") + .containsExactly("c", "a.txt", "b.txt", "f.txt"); + FileObject c = findFile(root.getChildren(), "c"); + + assertThat(c.getChildren()) + .extracting("name") + .containsExactly("1", "4", "d.txt", "e.txt"); + + FileObject f_1 = findFile(c.getChildren(), "1"); + assertThat(f_1.getChildren()) + .extracting("name") + .containsExactly("2"); + FileObject f_12 = findFile(f_1.getChildren(), "2"); + assertThat(f_12.getChildren()) + .extracting("name") + .containsExactly("3"); + FileObject f_123 = findFile(f_12.getChildren(), "3"); + assertThat(f_123.getChildren()) + .extracting("name") + .containsExactly("123.txt"); + + FileObject f_4 = findFile(c.getChildren(), "4"); + assertThat(f_4.getChildren()) + .extracting("name") + .containsExactly("5", "6"); + FileObject f_45 = findFile(f_4.getChildren(), "5"); + assertThat(f_45.getChildren()) + .extracting("name") + .containsExactly("45-1.txt", "45-2.txt"); + FileObject f_46 = findFile(f_4.getChildren(), "6"); + assertThat(f_46.getChildren()) + .extracting("name") + .containsExactly("46-1.txt", "46-2.txt"); + } + private FileObject findFile(Collection foList, String name) { return foList.stream() .filter(f -> name.equals(f.getName())) diff --git a/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-test.zip b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-test.zip index 4d23fa02840bfe41556f4a5a93720240671e6cdd..7f143e9763f25ff36e5ae65fb951176b1937528f 100644 GIT binary patch delta 23086 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 delta 21992 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 R;Ywkp5YSH$?!gy=@P8$fus{F+