From a93380b867e9aad6632fdea63457672b44fa3a60 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Mon, 21 Oct 2019 14:10:50 +0200 Subject: [PATCH 01/31] Differentiate types for central and cloned repository --- .../repository/util/SimpleWorkdirFactory.java | 31 ++++++++++++------- .../scm/repository/util/WorkdirFactory.java | 4 +-- .../scm/repository/util/WorkingCopy.java | 18 ++++++----- .../util/SimpleWorkdirFactoryTest.java | 19 +++++++----- .../scm/repository/GitWorkdirFactory.java | 2 +- .../repository/spi/AbstractGitCommand.java | 2 +- .../scm/repository/spi/GitBranchCommand.java | 2 +- .../spi/SimpleGitWorkdirFactory.java | 11 +++++-- .../spi/SimpleGitWorkdirFactoryTest.java | 10 +++--- .../scm/repository/spi/HgBranchCommand.java | 4 +-- .../scm/repository/spi/HgModifyCommand.java | 4 +-- .../scm/repository/spi/HgWorkdirFactory.java | 2 +- .../spi/SimpleHgWorkdirFactory.java | 9 ++++-- 13 files changed, 73 insertions(+), 45 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java b/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java index a1073e24f2..f301022d30 100644 --- a/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java +++ b/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java @@ -8,7 +8,7 @@ import sonia.scm.repository.Repository; import java.io.File; import java.io.IOException; -public abstract class SimpleWorkdirFactory implements WorkdirFactory { +public abstract class SimpleWorkdirFactory implements WorkdirFactory { private static final Logger logger = LoggerFactory.getLogger(SimpleWorkdirFactory.class); @@ -19,11 +19,11 @@ public abstract class SimpleWorkdirFactory implements WorkdirFactory } @Override - public WorkingCopy createWorkingCopy(C context, String initialBranch) { + public WorkingCopy createWorkingCopy(C context, String initialBranch) { try { File directory = workdirProvider.createNewWorkdir(); - ParentAndClone parentAndClone = cloneRepository(context, directory, initialBranch); - return new WorkingCopy<>(parentAndClone.getClone(), parentAndClone.getParent(), this::close, directory); + ParentAndClone parentAndClone = cloneRepository(context, directory, initialBranch); + return new WorkingCopy<>(parentAndClone.getClone(), parentAndClone.getParent(), this::closeWorkdir, this::closeCentral, directory); } catch (IOException e) { throw new InternalRepositoryException(getScmRepository(context), "could not clone repository in temporary directory", e); } @@ -34,10 +34,11 @@ public abstract class SimpleWorkdirFactory implements WorkdirFactory @SuppressWarnings("squid:S00112") // We do allow implementations to throw arbitrary exceptions here, so that we can handle them in close protected abstract void closeRepository(R repository) throws Exception; + protected abstract void closeWorkdirInternal(W workdir) throws Exception; - protected abstract ParentAndClone cloneRepository(C context, File target, String initialBranch) throws IOException; + protected abstract ParentAndClone cloneRepository(C context, File target, String initialBranch) throws IOException; - private void close(R repository) { + private void closeCentral(R repository) { try { closeRepository(repository); } catch (Exception e) { @@ -45,11 +46,19 @@ public abstract class SimpleWorkdirFactory implements WorkdirFactory } } - protected static class ParentAndClone { - private final R parent; - private final R clone; + private void closeWorkdir(W repository) { + try { + closeWorkdirInternal(repository); + } catch (Exception e) { + logger.warn("could not close temporary repository clone", e); + } + } - public ParentAndClone(R parent, R clone) { + protected static class ParentAndClone { + private final R parent; + private final W clone; + + public ParentAndClone(R parent, W clone) { this.parent = parent; this.clone = clone; } @@ -58,7 +67,7 @@ public abstract class SimpleWorkdirFactory implements WorkdirFactory return parent; } - public R getClone() { + public W getClone() { return clone; } } diff --git a/scm-core/src/main/java/sonia/scm/repository/util/WorkdirFactory.java b/scm-core/src/main/java/sonia/scm/repository/util/WorkdirFactory.java index bddf03adaa..e1df5e99f3 100644 --- a/scm-core/src/main/java/sonia/scm/repository/util/WorkdirFactory.java +++ b/scm-core/src/main/java/sonia/scm/repository/util/WorkdirFactory.java @@ -1,5 +1,5 @@ package sonia.scm.repository.util; -public interface WorkdirFactory { - WorkingCopy createWorkingCopy(C context, String initialBranch); +public interface WorkdirFactory { + WorkingCopy createWorkingCopy(C context, String initialBranch); } diff --git a/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java b/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java index 3c96184142..12c859fb8c 100644 --- a/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java +++ b/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java @@ -8,23 +8,25 @@ import java.io.File; import java.io.IOException; import java.util.function.Consumer; -public class WorkingCopy implements AutoCloseable { +public class WorkingCopy implements AutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(WorkingCopy.class); private final File directory; - private final R workingRepository; + private final W workingRepository; private final R centralRepository; - private final Consumer cleanup; + private final Consumer cleanupWorkdir; + private final Consumer cleanupCentral; - public WorkingCopy(R workingRepository, R centralRepository, Consumer cleanup, File directory) { + public WorkingCopy(W workingRepository, R centralRepository, Consumer cleanupWorkdir, Consumer cleanupCentral, File directory) { this.directory = directory; this.workingRepository = workingRepository; this.centralRepository = centralRepository; - this.cleanup = cleanup; + this.cleanupCentral = cleanupCentral; + this.cleanupWorkdir = cleanupWorkdir; } - public R getWorkingRepository() { + public W getWorkingRepository() { return workingRepository; } @@ -39,8 +41,8 @@ public class WorkingCopy implements AutoCloseable { @Override public void close() { try { - cleanup.accept(workingRepository); - cleanup.accept(centralRepository); + cleanupWorkdir.accept(workingRepository); + cleanupCentral.accept(centralRepository); IOUtil.delete(directory); } catch (IOException e) { LOG.warn("could not delete temporary workdir '{}'", directory, e); diff --git a/scm-core/src/test/java/sonia/scm/repository/util/SimpleWorkdirFactoryTest.java b/scm-core/src/test/java/sonia/scm/repository/util/SimpleWorkdirFactoryTest.java index 4a1a1a4179..04e7b72202 100644 --- a/scm-core/src/test/java/sonia/scm/repository/util/SimpleWorkdirFactoryTest.java +++ b/scm-core/src/test/java/sonia/scm/repository/util/SimpleWorkdirFactoryTest.java @@ -24,14 +24,14 @@ public class SimpleWorkdirFactoryTest { @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); - private SimpleWorkdirFactory simpleWorkdirFactory; + private SimpleWorkdirFactory simpleWorkdirFactory; private String initialBranchForLastCloneCall; @Before public void initFactory() throws IOException { WorkdirProvider workdirProvider = new WorkdirProvider(temporaryFolder.newFolder()); - simpleWorkdirFactory = new SimpleWorkdirFactory(workdirProvider) { + simpleWorkdirFactory = new SimpleWorkdirFactory(workdirProvider) { @Override protected Repository getScmRepository(Context context) { return REPOSITORY; @@ -43,7 +43,12 @@ public class SimpleWorkdirFactoryTest { } @Override - protected ParentAndClone cloneRepository(Context context, File target, String initialBranch) { + protected void closeWorkdirInternal(Closeable workdir) throws Exception { + workdir.close(); + } + + @Override + protected ParentAndClone cloneRepository(Context context, File target, String initialBranch) { initialBranchForLastCloneCall = initialBranch; return new ParentAndClone<>(parent, clone); } @@ -53,7 +58,7 @@ public class SimpleWorkdirFactoryTest { @Test public void shouldCreateParentAndClone() { Context context = new Context(); - try (WorkingCopy workingCopy = simpleWorkdirFactory.createWorkingCopy(context, null)) { + try (WorkingCopy workingCopy = simpleWorkdirFactory.createWorkingCopy(context, null)) { assertThat(workingCopy.getCentralRepository()).isSameAs(parent); assertThat(workingCopy.getWorkingRepository()).isSameAs(clone); } @@ -62,7 +67,7 @@ public class SimpleWorkdirFactoryTest { @Test public void shouldCloseParent() throws IOException { Context context = new Context(); - try (WorkingCopy workingCopy = simpleWorkdirFactory.createWorkingCopy(context, null)) {} + try (WorkingCopy workingCopy = simpleWorkdirFactory.createWorkingCopy(context, null)) {} verify(parent).close(); } @@ -70,7 +75,7 @@ public class SimpleWorkdirFactoryTest { @Test public void shouldCloseClone() throws IOException { Context context = new Context(); - try (WorkingCopy workingCopy = simpleWorkdirFactory.createWorkingCopy(context, null)) {} + try (WorkingCopy workingCopy = simpleWorkdirFactory.createWorkingCopy(context, null)) {} verify(clone).close(); } @@ -78,7 +83,7 @@ public class SimpleWorkdirFactoryTest { @Test public void shouldPropagateInitialBranch() { Context context = new Context(); - try (WorkingCopy workingCopy = simpleWorkdirFactory.createWorkingCopy(context, "some")) { + try (WorkingCopy workingCopy = simpleWorkdirFactory.createWorkingCopy(context, "some")) { assertThat(initialBranchForLastCloneCall).isEqualTo("some"); } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitWorkdirFactory.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitWorkdirFactory.java index d3ed353677..9b2be467e8 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitWorkdirFactory.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitWorkdirFactory.java @@ -4,5 +4,5 @@ import org.eclipse.jgit.lib.Repository; import sonia.scm.repository.spi.GitContext; import sonia.scm.repository.util.WorkdirFactory; -public interface GitWorkdirFactory extends WorkdirFactory { +public interface GitWorkdirFactory extends WorkdirFactory { } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitCommand.java index 2531888a8b..73159da5c1 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitCommand.java @@ -142,7 +142,7 @@ class AbstractGitCommand } > R inClone(Function workerSupplier, GitWorkdirFactory workdirFactory, String initialBranch) { - try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context, initialBranch)) { + try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context, initialBranch)) { Repository repository = workingCopy.getWorkingRepository(); logger.debug("cloned repository to folder {}", repository.getWorkTree()); return workerSupplier.apply(new Git(repository)).run(); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java index e8675068b9..fe39006a66 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java @@ -58,7 +58,7 @@ public class GitBranchCommand extends AbstractGitCommand implements BranchComman @Override public Branch branch(BranchRequest request) { - try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context, request.getParentBranch())) { + try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context, request.getParentBranch())) { Git clone = new Git(workingCopy.getWorkingRepository()); Ref ref = clone.branchCreate().setName(request.getNewBranch()).call(); Iterable call = clone.push().add(request.getNewBranch()).call(); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java index a0fda7cd3b..f7b0c5567c 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java @@ -18,7 +18,7 @@ import java.io.IOException; import static sonia.scm.ContextEntry.ContextBuilder.entity; import static sonia.scm.NotFoundException.notFound; -public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory implements GitWorkdirFactory { +public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory implements GitWorkdirFactory { @Inject public SimpleGitWorkdirFactory(WorkdirProvider workdirProvider) { @@ -26,7 +26,7 @@ public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory cloneRepository(GitContext context, File target, String initialBranch) { + public ParentAndClone cloneRepository(GitContext context, File target, String initialBranch) { try { Repository clone = Git.cloneRepository() .setURI(createScmTransportProtocolUri(context.getDirectory())) @@ -60,6 +60,13 @@ public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory workingCopy = factory.createWorkingCopy(createContext(), null)) { + try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext(), null)) { assertThat(workingCopy.getDirectory()) .exists() @@ -62,7 +62,7 @@ public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase { public void shouldCheckoutInitialBranch() { SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(workdirProvider); - try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext(), "test-branch")) { + try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext(), "test-branch")) { assertThat(new File(workingCopy.getWorkingRepository().getWorkTree(), "a.txt")) .exists() .isFile() @@ -75,10 +75,10 @@ public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase { SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(workdirProvider); File firstDirectory; - try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext(), null)) { + try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext(), null)) { firstDirectory = workingCopy.getDirectory(); } - try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext(), null)) { + try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext(), null)) { File secondDirectory = workingCopy.getDirectory(); assertThat(secondDirectory).isNotEqualTo(firstDirectory); } @@ -89,7 +89,7 @@ public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase { SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(workdirProvider); File directory; - try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext(), null)) { + try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext(), null)) { directory = workingCopy.getWorkingRepository().getWorkTree(); } assertThat(directory).doesNotExist(); diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java index 5e9e01c9d8..e41dbf96da 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java @@ -59,7 +59,7 @@ public class HgBranchCommand extends AbstractCommand implements BranchCommand { @Override public Branch branch(BranchRequest request) { - try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(getContext(), request.getParentBranch())) { + try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(getContext(), request.getParentBranch())) { com.aragost.javahg.Repository repository = workingCopy.getWorkingRepository(); Changeset emptyChangeset = createNewBranchWithEmptyCommit(request, repository); @@ -83,7 +83,7 @@ public class HgBranchCommand extends AbstractCommand implements BranchCommand { .execute(); } - private void pullNewBranchIntoCentralRepository(BranchRequest request, WorkingCopy workingCopy) { + private void pullNewBranchIntoCentralRepository(BranchRequest request, WorkingCopy workingCopy) { try { PullCommand pullCommand = PullCommand.on(workingCopy.getCentralRepository()); workdirFactory.configure(pullCommand); diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java index 0294b6902b..419b7de161 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java @@ -29,7 +29,7 @@ public class HgModifyCommand implements ModifyCommand { @Override public String execute(ModifyCommandRequest request) { - try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context, request.getBranch())) { + try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context, request.getBranch())) { Repository workingRepository = workingCopy.getWorkingRepository(); request.getRequests().forEach( partialRequest -> { @@ -85,7 +85,7 @@ public class HgModifyCommand implements ModifyCommand { } } - private List pullModifyChangesToCentralRepository(ModifyCommandRequest request, WorkingCopy workingCopy) { + private List pullModifyChangesToCentralRepository(ModifyCommandRequest request, WorkingCopy workingCopy) { try { com.aragost.javahg.commands.PullCommand pullCommand = PullCommand.on(workingCopy.getCentralRepository()); workdirFactory.configure(pullCommand); diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgWorkdirFactory.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgWorkdirFactory.java index 515ee62c98..2920abc422 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgWorkdirFactory.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgWorkdirFactory.java @@ -4,6 +4,6 @@ import com.aragost.javahg.Repository; import com.aragost.javahg.commands.PullCommand; import sonia.scm.repository.util.WorkdirFactory; -public interface HgWorkdirFactory extends WorkdirFactory { +public interface HgWorkdirFactory extends WorkdirFactory { void configure(PullCommand pullCommand); } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkdirFactory.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkdirFactory.java index 619f8b0892..412e9b7bf6 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkdirFactory.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkdirFactory.java @@ -16,7 +16,7 @@ import java.io.IOException; import java.util.Map; import java.util.function.BiConsumer; -public class SimpleHgWorkdirFactory extends SimpleWorkdirFactory implements HgWorkdirFactory { +public class SimpleHgWorkdirFactory extends SimpleWorkdirFactory implements HgWorkdirFactory { private final Provider hgRepositoryEnvironmentBuilder; @@ -26,7 +26,7 @@ public class SimpleHgWorkdirFactory extends SimpleWorkdirFactory cloneRepository(HgCommandContext context, File target, String initialBranch) throws IOException { + public ParentAndClone cloneRepository(HgCommandContext context, File target, String initialBranch) throws IOException { BiConsumer> repositoryMapBiConsumer = (repository, environment) -> hgRepositoryEnvironmentBuilder.get().buildFor(repository, null, environment); Repository centralRepository = context.openWithSpecialEnvironment(repositoryMapBiConsumer); @@ -46,6 +46,11 @@ public class SimpleHgWorkdirFactory extends SimpleWorkdirFactory Date: Mon, 21 Oct 2019 14:27:57 +0200 Subject: [PATCH 02/31] Fix suppressions --- .../java/sonia/scm/repository/util/SimpleWorkdirFactory.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java b/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java index f301022d30..eaa9487f3c 100644 --- a/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java +++ b/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java @@ -32,8 +32,10 @@ public abstract class SimpleWorkdirFactory implements WorkdirFactory cloneRepository(C context, File target, String initialBranch) throws IOException; From 5c47a266cbed86bd429d4940673f05c85c977844 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 22 Oct 2019 09:33:14 +0200 Subject: [PATCH 03/31] implement SvnWorkDirFactory (grafted from 7c6f871d771d61c6835d052e2380af49368fe6f5) --- .../scm/repository/SvnWorkDirFactory.java | 9 +++ .../spi/SimpleSvnWorkDirFactory.java | 63 +++++++++++++++++ .../spi/SimpleSvnWorkDirFactoryTest.java | 69 +++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnWorkDirFactory.java create mode 100644 scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactory.java create mode 100644 scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactoryTest.java diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnWorkDirFactory.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnWorkDirFactory.java new file mode 100644 index 0000000000..6ae7715ded --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnWorkDirFactory.java @@ -0,0 +1,9 @@ +package sonia.scm.repository; + +import sonia.scm.repository.spi.SvnContext; +import sonia.scm.repository.util.WorkdirFactory; + +import java.io.File; + +public interface SvnWorkDirFactory extends WorkdirFactory { +} diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactory.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactory.java new file mode 100644 index 0000000000..2f30f086d9 --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactory.java @@ -0,0 +1,63 @@ +package sonia.scm.repository.spi; + +import org.apache.commons.lang.exception.CloneFailedException; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc2.SvnCheckout; +import org.tmatesoft.svn.core.wc2.SvnOperationFactory; +import org.tmatesoft.svn.core.wc2.SvnTarget; +import sonia.scm.repository.Repository; +import sonia.scm.repository.SvnWorkDirFactory; +import sonia.scm.repository.util.SimpleWorkdirFactory; +import sonia.scm.repository.util.WorkdirProvider; + +import javax.inject.Inject; +import java.io.File; +import java.io.IOException; + +public class SimpleSvnWorkDirFactory extends SimpleWorkdirFactory implements SvnWorkDirFactory { + + @Inject + public SimpleSvnWorkDirFactory(WorkdirProvider workdirProvider) { + super(workdirProvider); + } + + @Override + protected Repository getScmRepository(SvnContext context) { + return null; + } + + @Override + protected void closeRepository(File workingCopy) { + } + + @Override + protected void closeWorkdirInternal(File workdir) { + } + + @Override + protected ParentAndClone cloneRepository(SvnContext context, File workingCopy, String initialBranch) throws IOException { + + final SvnOperationFactory svnOperationFactory = new SvnOperationFactory(); + + SVNURL source; + try { + source = SVNURL.fromFile(context.getDirectory()); + } catch (SVNException ex) { + throw new CloneFailedException(ex.getMessage()); + } + + try { + final SvnCheckout checkout = svnOperationFactory.createCheckout(); + checkout.setSingleTarget(SvnTarget.fromFile(workingCopy)); + checkout.setSource(SvnTarget.fromURL(source)); + checkout.run(); + } catch (SVNException ex) { + throw new CloneFailedException(ex.getMessage()); + } finally { + svnOperationFactory.dispose(); + } + + return new ParentAndClone<>(workingCopy, workingCopy); + } +} diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactoryTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactoryTest.java new file mode 100644 index 0000000000..4cef2f9353 --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactoryTest.java @@ -0,0 +1,69 @@ +package sonia.scm.repository.spi; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.tmatesoft.svn.core.SVNException; +import sonia.scm.repository.util.WorkdirProvider; +import sonia.scm.repository.util.WorkingCopy; + +import java.io.File; +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SimpleSvnWorkDirFactoryTest extends AbstractSvnCommandTestBase { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + // keep this so that it will not be garbage collected (Transport keeps this in a week reference) + private WorkdirProvider workdirProvider; + + @Before + public void initWorkDirProvider() throws IOException { + workdirProvider = new WorkdirProvider(temporaryFolder.newFolder()); + } + + @Test + public void shouldCheckoutLatestRevision() throws SVNException, IOException { + SimpleSvnWorkDirFactory factory = new SimpleSvnWorkDirFactory(workdirProvider); + + try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext(), null)) { + assertThat(new File(workingCopy.getWorkingRepository(), "a.txt")) + .exists() + .isFile() + .hasContent("a and b\nline for blame test"); + } + } + + @Test + public void cloneFromPoolshouldNotBeReused() { + SimpleSvnWorkDirFactory factory = new SimpleSvnWorkDirFactory(workdirProvider); + + File firstDirectory; + try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext(), null)) { + firstDirectory = workingCopy.getDirectory(); + } + try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext(), null)) { + File secondDirectory = workingCopy.getDirectory(); + assertThat(secondDirectory).isNotEqualTo(firstDirectory); + } + } + + @Test + public void shouldDeleteCloneOnClose() { + SimpleSvnWorkDirFactory factory = new SimpleSvnWorkDirFactory(workdirProvider); + + File directory; + File workingRepository; + try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext(), null)) { + directory = workingCopy.getDirectory(); + workingRepository = workingCopy.getWorkingRepository(); + + } + assertThat(directory).doesNotExist(); + assertThat(workingRepository).doesNotExist(); + } +} From bedd2844f204287ce3f531f4fbbe03a8b813a58a Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 22 Oct 2019 09:33:14 +0200 Subject: [PATCH 04/31] implement SvnWorkDirFactory --- .../scm/repository/SvnWorkDirFactory.java | 9 +++ .../spi/SimpleSvnWorkDirFactory.java | 63 +++++++++++++++++ .../spi/SimpleSvnWorkDirFactoryTest.java | 69 +++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnWorkDirFactory.java create mode 100644 scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactory.java create mode 100644 scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactoryTest.java diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnWorkDirFactory.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnWorkDirFactory.java new file mode 100644 index 0000000000..6ae7715ded --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnWorkDirFactory.java @@ -0,0 +1,9 @@ +package sonia.scm.repository; + +import sonia.scm.repository.spi.SvnContext; +import sonia.scm.repository.util.WorkdirFactory; + +import java.io.File; + +public interface SvnWorkDirFactory extends WorkdirFactory { +} diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactory.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactory.java new file mode 100644 index 0000000000..2f30f086d9 --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactory.java @@ -0,0 +1,63 @@ +package sonia.scm.repository.spi; + +import org.apache.commons.lang.exception.CloneFailedException; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc2.SvnCheckout; +import org.tmatesoft.svn.core.wc2.SvnOperationFactory; +import org.tmatesoft.svn.core.wc2.SvnTarget; +import sonia.scm.repository.Repository; +import sonia.scm.repository.SvnWorkDirFactory; +import sonia.scm.repository.util.SimpleWorkdirFactory; +import sonia.scm.repository.util.WorkdirProvider; + +import javax.inject.Inject; +import java.io.File; +import java.io.IOException; + +public class SimpleSvnWorkDirFactory extends SimpleWorkdirFactory implements SvnWorkDirFactory { + + @Inject + public SimpleSvnWorkDirFactory(WorkdirProvider workdirProvider) { + super(workdirProvider); + } + + @Override + protected Repository getScmRepository(SvnContext context) { + return null; + } + + @Override + protected void closeRepository(File workingCopy) { + } + + @Override + protected void closeWorkdirInternal(File workdir) { + } + + @Override + protected ParentAndClone cloneRepository(SvnContext context, File workingCopy, String initialBranch) throws IOException { + + final SvnOperationFactory svnOperationFactory = new SvnOperationFactory(); + + SVNURL source; + try { + source = SVNURL.fromFile(context.getDirectory()); + } catch (SVNException ex) { + throw new CloneFailedException(ex.getMessage()); + } + + try { + final SvnCheckout checkout = svnOperationFactory.createCheckout(); + checkout.setSingleTarget(SvnTarget.fromFile(workingCopy)); + checkout.setSource(SvnTarget.fromURL(source)); + checkout.run(); + } catch (SVNException ex) { + throw new CloneFailedException(ex.getMessage()); + } finally { + svnOperationFactory.dispose(); + } + + return new ParentAndClone<>(workingCopy, workingCopy); + } +} diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactoryTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactoryTest.java new file mode 100644 index 0000000000..4cef2f9353 --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactoryTest.java @@ -0,0 +1,69 @@ +package sonia.scm.repository.spi; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.tmatesoft.svn.core.SVNException; +import sonia.scm.repository.util.WorkdirProvider; +import sonia.scm.repository.util.WorkingCopy; + +import java.io.File; +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SimpleSvnWorkDirFactoryTest extends AbstractSvnCommandTestBase { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + // keep this so that it will not be garbage collected (Transport keeps this in a week reference) + private WorkdirProvider workdirProvider; + + @Before + public void initWorkDirProvider() throws IOException { + workdirProvider = new WorkdirProvider(temporaryFolder.newFolder()); + } + + @Test + public void shouldCheckoutLatestRevision() throws SVNException, IOException { + SimpleSvnWorkDirFactory factory = new SimpleSvnWorkDirFactory(workdirProvider); + + try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext(), null)) { + assertThat(new File(workingCopy.getWorkingRepository(), "a.txt")) + .exists() + .isFile() + .hasContent("a and b\nline for blame test"); + } + } + + @Test + public void cloneFromPoolshouldNotBeReused() { + SimpleSvnWorkDirFactory factory = new SimpleSvnWorkDirFactory(workdirProvider); + + File firstDirectory; + try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext(), null)) { + firstDirectory = workingCopy.getDirectory(); + } + try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext(), null)) { + File secondDirectory = workingCopy.getDirectory(); + assertThat(secondDirectory).isNotEqualTo(firstDirectory); + } + } + + @Test + public void shouldDeleteCloneOnClose() { + SimpleSvnWorkDirFactory factory = new SimpleSvnWorkDirFactory(workdirProvider); + + File directory; + File workingRepository; + try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext(), null)) { + directory = workingCopy.getDirectory(); + workingRepository = workingCopy.getWorkingRepository(); + + } + assertThat(directory).doesNotExist(); + assertThat(workingRepository).doesNotExist(); + } +} From 1d7d854ea889950a861d26e90616fa18115d4af0 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Wed, 23 Oct 2019 14:42:19 +0200 Subject: [PATCH 05/31] implement svnModifyCommand --- .../spi/SimpleSvnWorkDirFactory.java | 2 +- .../scm/repository/spi/SvnModifyCommand.java | 84 +++++++++++++++++ .../spi/SvnRepositoryServiceProvider.java | 14 ++- .../spi/SvnRepositoryServiceResolver.java | 7 +- .../java/sonia/scm/web/SvnServletModule.java | 3 + .../repository/spi/SvnModifyCommandTest.java | 89 +++++++++++++++++++ 6 files changed, 194 insertions(+), 5 deletions(-) create mode 100644 scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModifyCommand.java create mode 100644 scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnModifyCommandTest.java diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactory.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactory.java index 2f30f086d9..713e79a2bb 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactory.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactory.java @@ -58,6 +58,6 @@ public class SimpleSvnWorkDirFactory extends SimpleWorkdirFactory(workingCopy, workingCopy); + return new ParentAndClone<>(context.getDirectory(), workingCopy); } } diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModifyCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModifyCommand.java new file mode 100644 index 0000000000..f624c40806 --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModifyCommand.java @@ -0,0 +1,84 @@ +package sonia.scm.repository.spi; + +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.wc.SVNClientManager; +import org.tmatesoft.svn.core.wc.SVNWCClient; +import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.repository.Repository; +import sonia.scm.repository.SvnWorkDirFactory; +import sonia.scm.repository.util.WorkingCopy; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; + +public class SvnModifyCommand implements ModifyCommand { + + private SvnContext context; + private SvnWorkDirFactory workDirFactory; + private Repository repository; + + public SvnModifyCommand(SvnContext context, Repository repository, SvnWorkDirFactory workDirFactory) { + this.context = context; + this.repository = repository; + this.workDirFactory = workDirFactory; + } + + @Override + public String execute(ModifyCommandRequest request) { + SVNClientManager clientManager = SVNClientManager.newInstance(); + try (WorkingCopy workingCopy = workDirFactory.createWorkingCopy(context, null)) { + File workingRepository = workingCopy.getWorkingRepository(); + for (ModifyCommandRequest.PartialRequest partialRequest : request.getRequests()) { + try { + SVNWCClient wcClient = clientManager.getWCClient(); + partialRequest.execute(new ModifyWorkerHelper() { + @Override + public void doScmDelete(String toBeDeleted){ + try { + wcClient.doDelete(new File(String.format("%s/%s", workingRepository, toBeDeleted)), true, true, false); + } catch (SVNException e) { + throw new InternalRepositoryException(repository, "could not delete file from repository"); + } + } + + @Override + public void addFileToScm(String name, Path file) { + try { + wcClient.doAdd(file.toFile(), true, false, true, SVNDepth.INFINITY, false, true); + } catch (SVNException e) { + throw new InternalRepositoryException(repository, "could not add file to repository"); + } + } + + @Override + public File getWorkDir() { + return workingRepository; + } + + @Override + public Repository getRepository() { + return repository; + } + + @Override + public String getBranch() { + return null; + } + }); + } catch (IOException e) { + throw new InternalRepositoryException(repository, "could not read files from repository"); + } + } + try { + clientManager.getCommitClient().doCommit(new File[] {workingRepository}, false, + request.getCommitMessage(), null, null, false, true, SVNDepth.INFINITY); + //I can't get the newest revision after commiting without creating a new working copy + return String.valueOf(clientManager.getStatusClient().doStatus(workingRepository, false).getCommittedRevision().getNumber() + 1); + } catch (SVNException e) { + throw new InternalRepositoryException(repository, "could not commit changes on repository"); + } + } + } +} diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnRepositoryServiceProvider.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnRepositoryServiceProvider.java index ff277947bd..e96dedd4a0 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnRepositoryServiceProvider.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnRepositoryServiceProvider.java @@ -37,8 +37,10 @@ import com.google.common.collect.ImmutableSet; import com.google.common.io.Closeables; import sonia.scm.repository.Repository; import sonia.scm.repository.SvnRepositoryHandler; +import sonia.scm.repository.SvnWorkDirFactory; import sonia.scm.repository.api.Command; +import javax.inject.Inject; import java.io.IOException; import java.util.Set; @@ -53,17 +55,19 @@ public class SvnRepositoryServiceProvider extends RepositoryServiceProvider //J- public static final Set COMMANDS = ImmutableSet.of( Command.BLAME, Command.BROWSE, Command.CAT, Command.DIFF, - Command.LOG, Command.BUNDLE, Command.UNBUNDLE + Command.LOG, Command.BUNDLE, Command.UNBUNDLE, Command.MODIFY ); //J+ //~--- constructors --------------------------------------------------------- + @Inject SvnRepositoryServiceProvider(SvnRepositoryHandler handler, - Repository repository) + Repository repository, SvnWorkDirFactory workdirFactory) { this.repository = repository; this.context = new SvnContext(handler.getDirectory(repository.getId())); + this.workDirFactory = workdirFactory; } //~--- methods -------------------------------------------------------------- @@ -158,6 +162,10 @@ public class SvnRepositoryServiceProvider extends RepositoryServiceProvider return new SvnModificationsCommand(context, repository); } + public ModifyCommand getModifyCommand() { + return new SvnModifyCommand(context, repository, workDirFactory); + } + /** * Method description * @@ -189,4 +197,6 @@ public class SvnRepositoryServiceProvider extends RepositoryServiceProvider /** Field description */ private final Repository repository; + + private final SvnWorkDirFactory workDirFactory; } diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnRepositoryServiceResolver.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnRepositoryServiceResolver.java index 763b5f445e..05097ff111 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnRepositoryServiceResolver.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnRepositoryServiceResolver.java @@ -36,15 +36,18 @@ import com.google.inject.Inject; import sonia.scm.plugin.Extension; import sonia.scm.repository.Repository; import sonia.scm.repository.SvnRepositoryHandler; +import sonia.scm.repository.SvnWorkDirFactory; @Extension public class SvnRepositoryServiceResolver implements RepositoryServiceResolver { private SvnRepositoryHandler handler; + private SvnWorkDirFactory workdirFactory; @Inject - public SvnRepositoryServiceResolver(SvnRepositoryHandler handler) { + public SvnRepositoryServiceResolver(SvnRepositoryHandler handler, SvnWorkDirFactory workdirFactory) { this.handler = handler; + this.workdirFactory = workdirFactory; } @Override @@ -52,7 +55,7 @@ public class SvnRepositoryServiceResolver implements RepositoryServiceResolver { SvnRepositoryServiceProvider provider = null; if (SvnRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) { - provider = new SvnRepositoryServiceProvider(handler, repository); + provider = new SvnRepositoryServiceProvider(handler, repository, workdirFactory); } return provider; diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnServletModule.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnServletModule.java index 8526d6380a..b4f0aaf920 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnServletModule.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnServletModule.java @@ -38,6 +38,8 @@ import org.mapstruct.factory.Mappers; import sonia.scm.api.v2.resources.SvnConfigDtoToSvnConfigMapper; import sonia.scm.api.v2.resources.SvnConfigToSvnConfigDtoMapper; import sonia.scm.plugin.Extension; +import sonia.scm.repository.SvnWorkDirFactory; +import sonia.scm.repository.spi.SimpleSvnWorkDirFactory; /** * @@ -50,5 +52,6 @@ public class SvnServletModule extends ServletModule { protected void configureServlets() { bind(SvnConfigDtoToSvnConfigMapper.class).to(Mappers.getMapper(SvnConfigDtoToSvnConfigMapper.class).getClass()); bind(SvnConfigToSvnConfigDtoMapper.class).to(Mappers.getMapper(SvnConfigToSvnConfigDtoMapper.class).getClass()); + bind(SvnWorkDirFactory.class).to(SimpleSvnWorkDirFactory.class); } } diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnModifyCommandTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnModifyCommandTest.java new file mode 100644 index 0000000000..456971d9a2 --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnModifyCommandTest.java @@ -0,0 +1,89 @@ +package sonia.scm.repository.spi; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import sonia.scm.AlreadyExistsException; +import sonia.scm.repository.Person; +import sonia.scm.repository.util.WorkdirProvider; +import sonia.scm.repository.util.WorkingCopy; + +import java.io.File; +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class SvnModifyCommandTest extends AbstractSvnCommandTestBase { + + private SvnModifyCommand svnModifyCommand; + private SvnContext context; + private SimpleSvnWorkDirFactory workDirFactory; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Before + public void initSvnModifyCommand() { + context = createContext(); + workDirFactory = new SimpleSvnWorkDirFactory(new WorkdirProvider(context.getDirectory())); + svnModifyCommand = new SvnModifyCommand(context, createRepository(), workDirFactory); + } + + @Test + public void shouldRemoveFiles() { + ModifyCommandRequest request = new ModifyCommandRequest(); + request.addRequest(new ModifyCommandRequest.DeleteFileRequest("a.txt")); + request.setCommitMessage("this is great"); + request.setAuthor(new Person("Arthur Dent", "dent@hitchhiker.com")); + + svnModifyCommand.execute(request); + WorkingCopy workingCopy = workDirFactory.createWorkingCopy(context, null); + assertThat(new File(workingCopy.getWorkingRepository().getAbsolutePath() + "/a.txt")).doesNotExist(); + assertThat(new File(workingCopy.getWorkingRepository().getAbsolutePath() + "/c")).exists(); + } + + @Test + public void shouldAddNewFile() throws IOException { + File testfile = temporaryFolder.newFile("Test123"); + + ModifyCommandRequest request = new ModifyCommandRequest(); + request.addRequest(new ModifyCommandRequest.CreateFileRequest("Test123", testfile, false)); + request.setCommitMessage("this is great"); + request.setAuthor(new Person("Arthur Dent", "dent@hitchhiker.com")); + + svnModifyCommand.execute(request); + + WorkingCopy workingCopy = workDirFactory.createWorkingCopy(context, null); + assertThat(new File(workingCopy.getWorkingRepository(), "Test123")).exists(); + } + + @Test + public void shouldThrowFileAlreadyExistsException() throws IOException { + File testfile = temporaryFolder.newFile("a.txt"); + + ModifyCommandRequest request = new ModifyCommandRequest(); + request.addRequest(new ModifyCommandRequest.CreateFileRequest("a.txt", testfile, false)); + request.setCommitMessage("this is great"); + request.setAuthor(new Person("Arthur Dent", "dent@hitchhiker.com")); + + assertThrows(AlreadyExistsException.class, () -> svnModifyCommand.execute(request)); + } + + @Test + public void shouldUpdateExistingFile() throws IOException { + File testfile = temporaryFolder.newFile("a.txt"); + + ModifyCommandRequest request = new ModifyCommandRequest(); + request.addRequest(new ModifyCommandRequest.CreateFileRequest("a.txt", testfile, true)); + request.setCommitMessage("this is great"); + request.setAuthor(new Person("Arthur Dent", "dent@hitchhiker.com")); + + svnModifyCommand.execute(request); + + WorkingCopy workingCopy = workDirFactory.createWorkingCopy(context, null); + assertThat(new File(workingCopy.getWorkingRepository(), "a.txt")).exists(); + assertThat(new File(workingCopy.getWorkingRepository(), "a.txt")).hasContent(""); + } +} From a62c4a1fc0b6edcfc700ac04e68c0c67ddb3303b Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Wed, 23 Oct 2019 17:25:52 +0200 Subject: [PATCH 06/31] always render breadcrumd --- scm-ui/ui-components/src/Breadcrumb.js | 26 ++++++---- .../src/repos/sources/containers/Sources.js | 49 ++++++++----------- 2 files changed, 37 insertions(+), 38 deletions(-) diff --git a/scm-ui/ui-components/src/Breadcrumb.js b/scm-ui/ui-components/src/Breadcrumb.js index 524230a0a4..f441c8e2d1 100644 --- a/scm-ui/ui-components/src/Breadcrumb.js +++ b/scm-ui/ui-components/src/Breadcrumb.js @@ -1,11 +1,11 @@ //@flow import React from "react"; -import { Link } from "react-router-dom"; -import { translate } from "react-i18next"; +import {Link} from "react-router-dom"; +import {translate} from "react-i18next"; import classNames from "classnames"; import styled from "styled-components"; -import { binder, ExtensionPoint } from "@scm-manager/ui-extensions"; -import type { Branch, Repository } from "@scm-manager/ui-types"; +import {binder, ExtensionPoint} from "@scm-manager/ui-extensions"; +import type {Branch, Repository} from "@scm-manager/ui-types"; import Icon from "./Icon"; type Props = { @@ -74,6 +74,11 @@ class Breadcrumb extends React.Component { t } = this.props; + let homeUrl = baseUrl + "/"; + if (revision) { + homeUrl += encodeURIComponent(revision) + "/"; + } + return ( <>
@@ -83,7 +88,7 @@ class Breadcrumb extends React.Component { >
  • - + { props={{ baseUrl, branch: branch ? branch : defaultBranch, + revision: branches ? undefined : revision, path, - isBranchUrl: - branches && - branches.filter( - b => b.name.replace("/", "%2F") === revision - ).length > 0, + isBranchUrl: branches + ? branches.filter( + b => b.name.replace("/", "%2F") === revision + ).length > 0 + : true, repository }} renderAll={true} diff --git a/scm-ui/ui-webapp/src/repos/sources/containers/Sources.js b/scm-ui/ui-webapp/src/repos/sources/containers/Sources.js index 98f173efb5..18b65e3f2f 100644 --- a/scm-ui/ui-webapp/src/repos/sources/containers/Sources.js +++ b/scm-ui/ui-webapp/src/repos/sources/containers/Sources.js @@ -1,25 +1,20 @@ // @flow import React from "react"; -import { connect } from "react-redux"; -import { withRouter } from "react-router-dom"; -import type { Branch, Repository } from "@scm-manager/ui-types"; +import {connect} from "react-redux"; +import {withRouter} from "react-router-dom"; +import type {Branch, Repository} from "@scm-manager/ui-types"; import FileTree from "../components/FileTree"; -import { - BranchSelector, - Breadcrumb, - ErrorNotification, - Loading -} from "@scm-manager/ui-components"; -import { translate } from "react-i18next"; +import {BranchSelector, Breadcrumb, ErrorNotification, Loading} from "@scm-manager/ui-components"; +import {translate} from "react-i18next"; import { fetchBranches, getBranches, getFetchBranchesFailure, isFetchBranchesPending } from "../../branches/modules/branches"; -import { compose } from "redux"; +import {compose} from "redux"; import Content from "./Content"; -import { fetchSources, isDirectory } from "../modules/sources"; +import {fetchSources, isDirectory} from "../modules/sources"; type Props = { repository: Repository, @@ -175,22 +170,20 @@ class Sources extends React.Component { const { revision, path, baseUrl, branches, repository } = this.props; const { selectedBranch } = this.state; - if (revision) { - return ( - b.defaultBranch === true)[0] - } - branches={branches} - repository={repository} - /> - ); - } - return null; + //TODO refactor + return ( + b.defaultBranch === true)[0] + } + branches={branches} + repository={repository} + /> + ); }; } From 8a9549cf48ef0239e43b8d8755f511c56ff5ac41 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Wed, 23 Oct 2019 17:26:58 +0200 Subject: [PATCH 07/31] fix linting --- scm-ui/ui-components/src/Breadcrumb.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scm-ui/ui-components/src/Breadcrumb.js b/scm-ui/ui-components/src/Breadcrumb.js index f441c8e2d1..c063c3beba 100644 --- a/scm-ui/ui-components/src/Breadcrumb.js +++ b/scm-ui/ui-components/src/Breadcrumb.js @@ -1,11 +1,11 @@ //@flow import React from "react"; -import {Link} from "react-router-dom"; -import {translate} from "react-i18next"; +import { Link } from "react-router-dom"; +import { translate } from "react-i18next"; import classNames from "classnames"; import styled from "styled-components"; -import {binder, ExtensionPoint} from "@scm-manager/ui-extensions"; -import type {Branch, Repository} from "@scm-manager/ui-types"; +import { binder, ExtensionPoint } from "@scm-manager/ui-extensions"; +import type { Branch, Repository } from "@scm-manager/ui-types"; import Icon from "./Icon"; type Props = { From fb0eb329e5241f1a13ae7269819714057f7a5cc2 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Thu, 24 Oct 2019 09:11:17 +0200 Subject: [PATCH 08/31] return branch for git & hg for correct redirect --- .../main/java/sonia/scm/repository/spi/GitModifyCommand.java | 5 ++++- .../main/java/sonia/scm/repository/spi/HgModifyCommand.java | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java index a68be0a4da..ea0d207a48 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java @@ -64,7 +64,10 @@ public class GitModifyCommand extends AbstractGitCommand implements ModifyComman failIfNotChanged(() -> new NoChangesMadeException(repository, ModifyWorker.this.request.getBranch())); Optional revCommit = doCommit(request.getCommitMessage(), request.getAuthor()); push(); - return revCommit.orElseThrow(() -> new NoChangesMadeException(repository, ModifyWorker.this.request.getBranch())).name(); + if (!revCommit.isPresent()) { + throw new NoChangesMadeException(repository, ModifyWorker.this.request.getBranch()); + } + return ModifyWorker.this.request.getBranch(); } @Override diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java index 419b7de161..55383d69fe 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java @@ -78,7 +78,7 @@ public class HgModifyCommand implements ModifyCommand { } CommitCommand.on(workingRepository).user(String.format("%s <%s>", request.getAuthor().getName(), request.getAuthor().getMail())).message(request.getCommitMessage()).execute(); List execute = pullModifyChangesToCentralRepository(request, workingCopy); - return execute.get(0).getNode(); + return execute.get(0).getBranch(); } catch (ExecutionException e) { throwInternalRepositoryException("could not execute command on repository", e); return null; From 5c7bd90f7aa5e65a4cdf76371396554c9fc72e90 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Thu, 24 Oct 2019 11:57:16 +0200 Subject: [PATCH 09/31] return changesetId for git & hg --- .../main/java/sonia/scm/repository/spi/GitModifyCommand.java | 5 +---- .../main/java/sonia/scm/repository/spi/HgModifyCommand.java | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java index ea0d207a48..a68be0a4da 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java @@ -64,10 +64,7 @@ public class GitModifyCommand extends AbstractGitCommand implements ModifyComman failIfNotChanged(() -> new NoChangesMadeException(repository, ModifyWorker.this.request.getBranch())); Optional revCommit = doCommit(request.getCommitMessage(), request.getAuthor()); push(); - if (!revCommit.isPresent()) { - throw new NoChangesMadeException(repository, ModifyWorker.this.request.getBranch()); - } - return ModifyWorker.this.request.getBranch(); + return revCommit.orElseThrow(() -> new NoChangesMadeException(repository, ModifyWorker.this.request.getBranch())).name(); } @Override diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java index 55383d69fe..419b7de161 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java @@ -78,7 +78,7 @@ public class HgModifyCommand implements ModifyCommand { } CommitCommand.on(workingRepository).user(String.format("%s <%s>", request.getAuthor().getName(), request.getAuthor().getMail())).message(request.getCommitMessage()).execute(); List execute = pullModifyChangesToCentralRepository(request, workingCopy); - return execute.get(0).getBranch(); + return execute.get(0).getNode(); } catch (ExecutionException e) { throwInternalRepositoryException("could not execute command on repository", e); return null; From a938fd86825e155401937cdc8c269c6380cce6c0 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Fri, 25 Oct 2019 12:31:18 +0200 Subject: [PATCH 10/31] enhance logcommand --- .../scm/repository/api/LogCommandBuilder.java | 4 +-- .../sonia/scm/repository/spi/LogCommand.java | 2 +- .../scm/repository/spi/GitLogCommand.java | 10 +++++-- .../scm/repository/spi/GitLogCommandTest.java | 28 ++++++++++++++++++- .../scm/repository/spi/HgLogCommand.java | 2 +- .../scm/repository/spi/HgLogCommandTest.java | 2 +- .../scm/repository/spi/SvnLogCommand.java | 2 +- .../scm/repository/spi/SvnLogCommandTest.java | 2 +- 8 files changed, 42 insertions(+), 10 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/api/LogCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/LogCommandBuilder.java index 917b81391f..2836fc537d 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/LogCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/LogCommandBuilder.java @@ -178,7 +178,7 @@ public final class LogCommandBuilder logger.debug("get changeset for {} with disabled cache", id); } - changeset = logCommand.getChangeset(id); + changeset = logCommand.getChangeset(id, request); } else { @@ -192,7 +192,7 @@ public final class LogCommandBuilder logger.debug("get changeset for {}", id); } - changeset = logCommand.getChangeset(id); + changeset = logCommand.getChangeset(id, request); if (changeset != null) { diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/LogCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/LogCommand.java index f4babcee72..21d9ece4de 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/LogCommand.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/LogCommand.java @@ -49,7 +49,7 @@ import java.io.IOException; */ public interface LogCommand { - Changeset getChangeset(String id) throws IOException; + Changeset getChangeset(String id, LogCommandRequest request) throws IOException; ChangesetPagingResult getChangesets(LogCommandRequest request) throws IOException; } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java index a42abca5f0..ffed8c5ad4 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java @@ -106,7 +106,7 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand * @return */ @Override - public Changeset getChangeset(String revision) + public Changeset getChangeset(String revision, LogCommandRequest request) { if (logger.isDebugEnabled()) { @@ -131,7 +131,13 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand if (commit != null) { converter = new GitChangesetConverter(gr, revWalk); - changeset = converter.createChangeset(commit); + + if (request != null && !Strings.isNullOrEmpty(request.getBranch())) { + changeset = converter.createChangeset(commit, request.getBranch()); + } else { + changeset = converter.createChangeset(commit); + + } } else if (logger.isWarnEnabled()) { diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java index 06e9b17fe7..58485024d5 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java @@ -171,7 +171,33 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase public void testGetCommit() { GitLogCommand command = createCommand(); - Changeset c = command.getChangeset("435df2f061add3589cb3"); + Changeset c = command.getChangeset("435df2f061add3589cb3", null); + + assertNotNull(c); + String revision = "435df2f061add3589cb326cc64be9b9c3897ceca"; + assertEquals(revision, c.getId()); + assertEquals("added a and b files", c.getDescription()); + checkDate(c.getDate()); + assertEquals("Douglas Adams", c.getAuthor().getName()); + assertEquals("douglas.adams@hitchhiker.com", c.getAuthor().getMail()); + assertEquals("added a and b files", c.getDescription()); + + GitModificationsCommand gitModificationsCommand = new GitModificationsCommand(createContext(), repository); + Modifications modifications = gitModificationsCommand.getModifications(revision); + + assertNotNull(modifications); + assertTrue("modified list should be empty", modifications.getModified().isEmpty()); + assertTrue("removed list should be empty", modifications.getRemoved().isEmpty()); + assertFalse("added list should not be empty", modifications.getAdded().isEmpty()); + assertEquals(2, modifications.getAdded().size()); + assertThat(modifications.getAdded(), contains("a.txt", "b.txt")); + } + + @Test + public void testGetCommitWithBranch() + { + GitLogCommand command = createCommand(); + Changeset c = command.getChangeset("435df2f061add3589cb3", null); assertNotNull(c); String revision = "435df2f061add3589cb326cc64be9b9c3897ceca"; diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgLogCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgLogCommand.java index e9de7f7471..8a9a1c8e84 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgLogCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgLogCommand.java @@ -68,7 +68,7 @@ public class HgLogCommand extends AbstractCommand implements LogCommand //~--- get methods ---------------------------------------------------------- @Override - public Changeset getChangeset(String id) { + public Changeset getChangeset(String id, LogCommandRequest request) { com.aragost.javahg.Repository repository = open(); HgLogChangesetCommand cmd = on(repository); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgLogCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgLogCommandTest.java index 1daa395e07..07e9a0dec3 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgLogCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgLogCommandTest.java @@ -154,7 +154,7 @@ public class HgLogCommandTest extends AbstractHgCommandTestBase HgLogCommand command = createComamnd(); String revision = "a9bacaf1b7fa0cebfca71fed4e59ed69a6319427"; Changeset c = - command.getChangeset(revision); + command.getChangeset(revision, null); assertNotNull(c); assertEquals(revision, c.getId()); diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnLogCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnLogCommand.java index be102be1bd..0cc8687154 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnLogCommand.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnLogCommand.java @@ -75,7 +75,7 @@ public class SvnLogCommand extends AbstractSvnCommand implements LogCommand @Override @SuppressWarnings("unchecked") - public Changeset getChangeset(String revision) { + public Changeset getChangeset(String revision, LogCommandRequest request) { Changeset changeset = null; if (logger.isDebugEnabled()) diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnLogCommandTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnLogCommandTest.java index 0cfeaa3a1c..15198a19ed 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnLogCommandTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnLogCommandTest.java @@ -128,7 +128,7 @@ public class SvnLogCommandTest extends AbstractSvnCommandTestBase @Test public void testGetCommit() { - Changeset c = createCommand().getChangeset("3"); + Changeset c = createCommand().getChangeset("3", null); assertNotNull(c); assertEquals("3", c.getId()); From 6e0c788c98ac43fead4ab0c3c2fe2b2078448a02 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Fri, 25 Oct 2019 12:38:48 +0200 Subject: [PATCH 11/31] transfer frontend changes to typescript files --- scm-ui/ui-components/src/Breadcrumb.js | 129 ---------- scm-ui/ui-components/src/Breadcrumb.tsx | 7 +- .../src/repos/sources/containers/Sources.js | 230 ------------------ .../src/repos/sources/containers/Sources.tsx | 27 +- 4 files changed, 19 insertions(+), 374 deletions(-) delete mode 100644 scm-ui/ui-components/src/Breadcrumb.js delete mode 100644 scm-ui/ui-webapp/src/repos/sources/containers/Sources.js diff --git a/scm-ui/ui-components/src/Breadcrumb.js b/scm-ui/ui-components/src/Breadcrumb.js deleted file mode 100644 index c063c3beba..0000000000 --- a/scm-ui/ui-components/src/Breadcrumb.js +++ /dev/null @@ -1,129 +0,0 @@ -//@flow -import React from "react"; -import { Link } from "react-router-dom"; -import { translate } from "react-i18next"; -import classNames from "classnames"; -import styled from "styled-components"; -import { binder, ExtensionPoint } from "@scm-manager/ui-extensions"; -import type { Branch, Repository } from "@scm-manager/ui-types"; -import Icon from "./Icon"; - -type Props = { - repository: Repository, - branch: Branch, - defaultBranch: Branch, - branches: Branch[], - revision: string, - path: string, - baseUrl: string, - - // Context props - t: string => string -}; - -const FlexStartNav = styled.nav` - flex: 1; -`; - -const HomeIcon = styled(Icon)` - line-height: 1.5rem; -`; - -const ActionWrapper = styled.div` - align-self: center; - padding-right: 1rem; -`; - -class Breadcrumb extends React.Component { - renderPath() { - const { revision, path, baseUrl } = this.props; - - if (path) { - const paths = path.split("/"); - const map = paths.map((path, index) => { - const currPath = paths.slice(0, index + 1).join("/"); - if (paths.length - 1 === index) { - return ( -
  • - - {path} - -
  • - ); - } - return ( -
  • - {path} -
  • - ); - }); - return map; - } - return null; - } - - render() { - const { - baseUrl, - branch, - defaultBranch, - branches, - revision, - path, - repository, - t - } = this.props; - - let homeUrl = baseUrl + "/"; - if (revision) { - homeUrl += encodeURIComponent(revision) + "/"; - } - - return ( - <> -
    - -
      -
    • - - - -
    • - {this.renderPath()} -
    -
    - {binder.hasExtension("repos.sources.actionbar") && ( - - b.name.replace("/", "%2F") === revision - ).length > 0 - : true, - repository - }} - renderAll={true} - /> - - )} -
    -
    - - ); - } -} - -export default translate("commons")(Breadcrumb); diff --git a/scm-ui/ui-components/src/Breadcrumb.tsx b/scm-ui/ui-components/src/Breadcrumb.tsx index 8402bc25eb..6e35f537bc 100644 --- a/scm-ui/ui-components/src/Breadcrumb.tsx +++ b/scm-ui/ui-components/src/Breadcrumb.tsx @@ -61,13 +61,18 @@ class Breadcrumb extends React.Component { render() { const { baseUrl, branch, defaultBranch, branches, revision, path, repository, t } = this.props; + let homeUrl = baseUrl + "/"; + if (revision) { + homeUrl += encodeURIComponent(revision) + "/"; + } + return ( <>
    • - +
    • diff --git a/scm-ui/ui-webapp/src/repos/sources/containers/Sources.js b/scm-ui/ui-webapp/src/repos/sources/containers/Sources.js deleted file mode 100644 index 18b65e3f2f..0000000000 --- a/scm-ui/ui-webapp/src/repos/sources/containers/Sources.js +++ /dev/null @@ -1,230 +0,0 @@ -// @flow -import React from "react"; -import {connect} from "react-redux"; -import {withRouter} from "react-router-dom"; -import type {Branch, Repository} from "@scm-manager/ui-types"; -import FileTree from "../components/FileTree"; -import {BranchSelector, Breadcrumb, ErrorNotification, Loading} from "@scm-manager/ui-components"; -import {translate} from "react-i18next"; -import { - fetchBranches, - getBranches, - getFetchBranchesFailure, - isFetchBranchesPending -} from "../../branches/modules/branches"; -import {compose} from "redux"; -import Content from "./Content"; -import {fetchSources, isDirectory} from "../modules/sources"; - -type Props = { - repository: Repository, - loading: boolean, - error: Error, - baseUrl: string, - branches: Branch[], - revision: string, - path: string, - currentFileIsDirectory: boolean, - - // dispatch props - fetchBranches: Repository => void, - fetchSources: (Repository, string, string) => void, - - // Context props - history: any, - match: any, - location: any, - t: string => string -}; - -type State = { - selectedBranch: any -}; - -class Sources extends React.Component { - constructor(props: Props) { - super(props); - - this.state = { - selectedBranch: null - }; - } - - componentDidMount() { - const { - fetchBranches, - repository, - revision, - path, - fetchSources - } = this.props; - - fetchBranches(repository); - fetchSources(repository, revision, path); - - this.redirectToDefaultBranch(); - } - - componentDidUpdate(prevProps) { - const { fetchSources, repository, revision, path } = this.props; - if (prevProps.revision !== revision || prevProps.path !== path) { - fetchSources(repository, revision, path); - } - - this.redirectToDefaultBranch(); - } - - redirectToDefaultBranch = () => { - const { branches } = this.props; - if (this.shouldRedirectToDefaultBranch()) { - const defaultBranches = branches.filter(b => b.defaultBranch); - - if (defaultBranches.length > 0) { - this.branchSelected(defaultBranches[0]); - } - } - }; - - shouldRedirectToDefaultBranch = () => { - const { branches, revision } = this.props; - return branches && !revision; - }; - - branchSelected = (branch?: Branch) => { - const { baseUrl, history, path } = this.props; - let url; - if (branch) { - this.setState({ selectedBranch: branch }); - if (path) { - url = `${baseUrl}/${encodeURIComponent(branch.name)}/${path}`; - } else { - url = `${baseUrl}/${encodeURIComponent(branch.name)}/`; - } - } else { - this.setState({ selectedBranch: null }); - url = `${baseUrl}/`; - } - history.push(url); - }; - - render() { - const { - repository, - baseUrl, - loading, - error, - revision, - path, - currentFileIsDirectory - } = this.props; - - if (error) { - return ; - } - - if (loading) { - return ; - } - - if (currentFileIsDirectory) { - return ( -
      - {this.renderBranchSelector()} - {this.renderBreadcrumb()} - -
      - ); - } else { - return ( - - ); - } - } - - renderBranchSelector = () => { - const { branches, revision, t } = this.props; - - if (branches) { - return ( -
      - { - this.branchSelected(b); - }} - /> -
      - ); - } - return null; - }; - - renderBreadcrumb = () => { - const { revision, path, baseUrl, branches, repository } = this.props; - const { selectedBranch } = this.state; - - //TODO refactor - return ( - b.defaultBranch === true)[0] - } - branches={branches} - repository={repository} - /> - ); - }; -} - -const mapStateToProps = (state, ownProps) => { - const { repository, match } = ownProps; - const { revision, path } = match.params; - const decodedRevision = revision ? decodeURIComponent(revision) : undefined; - const loading = isFetchBranchesPending(state, repository); - const error = getFetchBranchesFailure(state, repository); - const branches = getBranches(state, repository); - const currentFileIsDirectory = decodedRevision - ? isDirectory(state, repository, decodedRevision, path) - : isDirectory(state, repository, revision, path); - - return { - repository, - revision: decodedRevision, - path, - loading, - error, - branches, - currentFileIsDirectory - }; -}; - -const mapDispatchToProps = dispatch => { - return { - fetchBranches: (repository: Repository) => { - dispatch(fetchBranches(repository)); - }, - fetchSources: (repository: Repository, revision: string, path: string) => { - dispatch(fetchSources(repository, revision, path)); - } - }; -}; - -export default compose( - translate("repos"), - withRouter, - connect( - mapStateToProps, - mapDispatchToProps - ) -)(Sources); diff --git a/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx b/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx index 33626ddcc4..037f93e166 100644 --- a/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx +++ b/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx @@ -151,20 +151,19 @@ class Sources extends React.Component { const { revision, path, baseUrl, branches, repository } = this.props; const { selectedBranch } = this.state; - if (revision) { - return ( - b.defaultBranch === true)[0]} - branches={branches} - repository={repository} - /> - ); - } - return null; + return ( + b.defaultBranch === true)[0] + } + branches={branches} + repository={repository} + /> + ); }; } From a3d5e9669ee88ee222272a70a00c91d38d84a391 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Sat, 26 Oct 2019 17:59:37 +0200 Subject: [PATCH 12/31] add test for gitLogCommand --- .../scm/repository/spi/GitLogCommandTest.java | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java index 58485024d5..b0b2f6adb7 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java @@ -35,7 +35,11 @@ package sonia.scm.repository.spi; import com.google.common.io.Files; +import org.assertj.core.api.Assertions; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.GitRepositoryConfig; @@ -51,14 +55,18 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; /** * Unit tests for {@link GitLogCommand}. * * @author Sebastian Sdorra */ +@RunWith(MockitoJUnitRunner.class) public class GitLogCommandTest extends AbstractGitCommandTestBase { + @Mock + LogCommandRequest request; /** * Tests log command with the usage of a default branch. @@ -194,29 +202,15 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase } @Test - public void testGetCommitWithBranch() + public void commitShouldContainBranchIfLogCommandRequestHasBranch() { + when(request.getBranch()).thenReturn("master"); GitLogCommand command = createCommand(); - Changeset c = command.getChangeset("435df2f061add3589cb3", null); + Changeset c = command.getChangeset("435df2f061add3589cb3", request); - assertNotNull(c); - String revision = "435df2f061add3589cb326cc64be9b9c3897ceca"; - assertEquals(revision, c.getId()); - assertEquals("added a and b files", c.getDescription()); - checkDate(c.getDate()); - assertEquals("Douglas Adams", c.getAuthor().getName()); - assertEquals("douglas.adams@hitchhiker.com", c.getAuthor().getMail()); - assertEquals("added a and b files", c.getDescription()); - - GitModificationsCommand gitModificationsCommand = new GitModificationsCommand(createContext(), repository); - Modifications modifications = gitModificationsCommand.getModifications(revision); - - assertNotNull(modifications); - assertTrue("modified list should be empty", modifications.getModified().isEmpty()); - assertTrue("removed list should be empty", modifications.getRemoved().isEmpty()); - assertFalse("added list should not be empty", modifications.getAdded().isEmpty()); - assertEquals(2, modifications.getAdded().size()); - assertThat(modifications.getAdded(), contains("a.txt", "b.txt")); + Assertions.assertThat(c.getBranches().isEmpty()).isFalse(); + Assertions.assertThat(c.getBranches().contains("master")).isTrue(); + Assertions.assertThat(c.getBranches().size()).isEqualTo(1); } @Test From 6d888f68c8dcc1f899ba19a6c8a4f3d3aa782c15 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Sat, 26 Oct 2019 18:55:26 +0200 Subject: [PATCH 13/31] fix after merge --- scm-ui/ui-components/src/Breadcrumb.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scm-ui/ui-components/src/Breadcrumb.tsx b/scm-ui/ui-components/src/Breadcrumb.tsx index 6e35f537bc..06423f8e1c 100644 --- a/scm-ui/ui-components/src/Breadcrumb.tsx +++ b/scm-ui/ui-components/src/Breadcrumb.tsx @@ -87,7 +87,9 @@ class Breadcrumb extends React.Component { baseUrl, branch: branch ? branch : defaultBranch, path, - isBranchUrl: branches && branches.filter(b => b.name.replace("/", "%2F") === revision).length > 0, + isBranchUrl: branches + ? branches.filter(b => b.name.replace("/", "%2F") === revision).length > 0 + : true, repository }} renderAll={true} From 71902598131ce7de425f19af1bca275787868ec3 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Sat, 26 Oct 2019 19:29:17 +0200 Subject: [PATCH 14/31] cleanup --- .../pkg/ourPackage/scm-source.properties | 4 ---- scm-webapp/config/config.xml | 13 ------------- 2 files changed, 17 deletions(-) delete mode 100644 scm-plugins/scm-hg-plugin/pkg/ourPackage/scm-source.properties delete mode 100644 scm-webapp/config/config.xml diff --git a/scm-plugins/scm-hg-plugin/pkg/ourPackage/scm-source.properties b/scm-plugins/scm-hg-plugin/pkg/ourPackage/scm-source.properties deleted file mode 100644 index 08549be9d4..0000000000 --- a/scm-plugins/scm-hg-plugin/pkg/ourPackage/scm-source.properties +++ /dev/null @@ -1,4 +0,0 @@ -#sonia.scm.io.AbstractUnArchiver properties -#Mon Oct 14 09:37:22 CEST 2019 -scm.unarchiver.source=/home/eheimbuch/IdeaProjects/scm-manager/scm-plugins/scm-hg-plugin/pkg/ourPackage -scm.unarchiver.checksum=ee0db11c27cf73ebdbc4ee9e0942a39b84ccbedc diff --git a/scm-webapp/config/config.xml b/scm-webapp/config/config.xml deleted file mode 100644 index f6e18efb74..0000000000 --- a/scm-webapp/config/config.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - false - 0 - Dl55tA0pSXIF_FWYY05nh0zw63ncJupsbnc4 - 0 - false - 0 - false - false - false - false - From 531259bec09d4f0989b376d8152e8b7ae3aed341 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Mon, 28 Oct 2019 11:45:42 +0100 Subject: [PATCH 15/31] check if request branch contains revision --- .../main/java/sonia/scm/repository/spi/GitLogCommand.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java index ffed8c5ad4..7f10948ad8 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java @@ -132,7 +132,9 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand { converter = new GitChangesetConverter(gr, revWalk); - if (request != null && !Strings.isNullOrEmpty(request.getBranch())) { + if (request != null && + !Strings.isNullOrEmpty(request.getBranch()) && + revWalk.isMergedInto(commit, findTipCommitForRequestBranch(request, gr, revWalk))) { changeset = converter.createChangeset(commit, request.getBranch()); } else { changeset = converter.createChangeset(commit); @@ -163,6 +165,10 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand return changeset; } + private RevCommit findTipCommitForRequestBranch(LogCommandRequest request, Repository gr, RevWalk revWalk) throws IOException { + return revWalk.parseCommit(GitUtil.getCommit(gr, revWalk, gr.findRef(request.getBranch()))); + } + /** * Method description * From 52c3c1d7f841f533c2d4405c2162957b20fe9f4c Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Mon, 28 Oct 2019 15:21:24 +0100 Subject: [PATCH 16/31] add enricher for browserResult mapping --- .../v2/resources/BaseFileObjectDtoMapper.java | 53 +++++++++++++++++++ .../BrowserResultToFileObjectDtoMapper.java | 28 +++++----- .../FileObjectToFileObjectDtoMapper.java | 39 ++------------ .../scm/api/v2/resources/MapperModule.java | 1 + ...rowserResultToFileObjectDtoMapperTest.java | 2 +- .../v2/resources/SourceRootResourceTest.java | 2 +- 6 files changed, 74 insertions(+), 51 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/BaseFileObjectDtoMapper.java diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BaseFileObjectDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BaseFileObjectDtoMapper.java new file mode 100644 index 0000000000..d470ccd5ad --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BaseFileObjectDtoMapper.java @@ -0,0 +1,53 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.Embedded; +import de.otto.edison.hal.Links; +import org.mapstruct.Context; +import org.mapstruct.MapperConfig; +import org.mapstruct.Mapping; +import org.mapstruct.ObjectFactory; +import sonia.scm.repository.BrowserResult; +import sonia.scm.repository.FileObject; +import sonia.scm.repository.NamespaceAndName; +import sonia.scm.repository.SubRepository; + +import javax.inject.Inject; + +import static de.otto.edison.hal.Embedded.embeddedBuilder; +import static de.otto.edison.hal.Link.link; + +@MapperConfig +abstract class BaseFileObjectDtoMapper extends HalAppenderMapper implements InstantAttributeMapper { + + @Inject + private ResourceLinks resourceLinks; + + @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes + protected abstract FileObjectDto map(FileObject fileObject, @Context NamespaceAndName namespaceAndName, @Context BrowserResult browserResult); + + abstract SubRepositoryDto mapSubrepository(SubRepository subRepository); + + @ObjectFactory + FileObjectDto createDto(@Context NamespaceAndName namespaceAndName, @Context BrowserResult browserResult, FileObject fileObject) { + String path = removeFirstSlash(fileObject.getPath()); + Links.Builder links = Links.linkingTo(); + if (fileObject.isDirectory()) { + links.self(resourceLinks.source().sourceWithPath(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision(), path)); + } else { + links.self(resourceLinks.source().content(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision(), path)); + links.single(link("history", resourceLinks.fileHistory().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision(), path))); + } + + Embedded.Builder embeddedBuilder = embeddedBuilder(); + applyEnrichers(links, embeddedBuilder, namespaceAndName, browserResult, fileObject); + + return new FileObjectDto(links.build(), embeddedBuilder.build()); + } + + abstract void applyEnrichers(Links.Builder links, Embedded.Builder embeddedBuilder, NamespaceAndName namespaceAndName, BrowserResult browserResult, FileObject fileObject); + + private String removeFirstSlash(String source) { + return source.startsWith("/") ? source.substring(1) : source; + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java index 588a8b3b2f..6f190bade6 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java @@ -1,22 +1,24 @@ package sonia.scm.api.v2.resources; +import de.otto.edison.hal.Embedded; +import de.otto.edison.hal.Links; +import org.mapstruct.Context; +import org.mapstruct.Mapper; import sonia.scm.repository.BrowserResult; +import sonia.scm.repository.FileObject; import sonia.scm.repository.NamespaceAndName; -import javax.inject.Inject; +@Mapper +public abstract class BrowserResultToFileObjectDtoMapper extends BaseFileObjectDtoMapper { -public class BrowserResultToFileObjectDtoMapper { - - private final FileObjectToFileObjectDtoMapper fileObjectToFileObjectDtoMapper; - - @Inject - public BrowserResultToFileObjectDtoMapper(FileObjectToFileObjectDtoMapper fileObjectToFileObjectDtoMapper) { - this.fileObjectToFileObjectDtoMapper = fileObjectToFileObjectDtoMapper; - } - - public FileObjectDto map(BrowserResult browserResult, NamespaceAndName namespaceAndName) { - FileObjectDto fileObjectDto = fileObjectToFileObjectDtoMapper.map(browserResult.getFile(), namespaceAndName, browserResult); - fileObjectDto.setRevision( browserResult.getRevision() ); + FileObjectDto map(BrowserResult browserResult, @Context NamespaceAndName namespaceAndName) { + FileObjectDto fileObjectDto = map(browserResult.getFile(), namespaceAndName, browserResult); + fileObjectDto.setRevision(browserResult.getRevision()); return fileObjectDto; } + + @Override + void applyEnrichers(Links.Builder links, Embedded.Builder embeddedBuilder, NamespaceAndName namespaceAndName, BrowserResult browserResult, FileObject fileObject) { + applyEnrichers(new EdisonHalAppender(links, embeddedBuilder), browserResult, namespaceAndName); + } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java index 42da3e90c8..d9ecbcfd4e 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java @@ -2,49 +2,16 @@ package sonia.scm.api.v2.resources; import de.otto.edison.hal.Embedded; import de.otto.edison.hal.Links; -import org.mapstruct.Context; import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.ObjectFactory; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.FileObject; import sonia.scm.repository.NamespaceAndName; -import sonia.scm.repository.SubRepository; - -import javax.inject.Inject; - -import static de.otto.edison.hal.Embedded.embeddedBuilder; -import static de.otto.edison.hal.Link.link; @Mapper -public abstract class FileObjectToFileObjectDtoMapper extends HalAppenderMapper implements InstantAttributeMapper { +public abstract class FileObjectToFileObjectDtoMapper extends BaseFileObjectDtoMapper { - @Inject - private ResourceLinks resourceLinks; - - @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes - protected abstract FileObjectDto map(FileObject fileObject, @Context NamespaceAndName namespaceAndName, @Context BrowserResult browserResult); - - abstract SubRepositoryDto mapSubrepository(SubRepository subRepository); - - @ObjectFactory - FileObjectDto createDto(@Context NamespaceAndName namespaceAndName, @Context BrowserResult browserResult, FileObject fileObject) { - String path = removeFirstSlash(fileObject.getPath()); - Links.Builder links = Links.linkingTo(); - if (fileObject.isDirectory()) { - links.self(resourceLinks.source().sourceWithPath(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision(), path)); - } else { - links.self(resourceLinks.source().content(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision(), path)); - links.single(link("history", resourceLinks.fileHistory().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision(), path))); - } - - Embedded.Builder embeddedBuilder = embeddedBuilder(); + @Override + void applyEnrichers(Links.Builder links, Embedded.Builder embeddedBuilder, NamespaceAndName namespaceAndName, BrowserResult browserResult, FileObject fileObject) { applyEnrichers(new EdisonHalAppender(links, embeddedBuilder), fileObject, namespaceAndName, browserResult, browserResult.getRevision()); - - return new FileObjectDto(links.build(), embeddedBuilder.build()); - } - - private String removeFirstSlash(String source) { - return source.startsWith("/") ? source.substring(1) : source; } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java index 0b419cf542..6b6846f039 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java @@ -38,6 +38,7 @@ public class MapperModule extends AbstractModule { bind(TagToTagDtoMapper.class).to(Mappers.getMapper(TagToTagDtoMapper.class).getClass()); bind(FileObjectToFileObjectDtoMapper.class).to(Mappers.getMapper(FileObjectToFileObjectDtoMapper.class).getClass()); + bind(BrowserResultToFileObjectDtoMapper.class).to(Mappers.getMapper(BrowserResultToFileObjectDtoMapper.class).getClass()); bind(ModificationsToDtoMapper.class).to(Mappers.getMapper(ModificationsToDtoMapper.class).getClass()); bind(ReducedObjectModelToDtoMapper.class).to(Mappers.getMapper(ReducedObjectModelToDtoMapper.class).getClass()); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapperTest.java index b0f0d00708..eae634d854 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapperTest.java @@ -39,7 +39,7 @@ public class BrowserResultToFileObjectDtoMapperTest { @Before public void init() { initMocks(this); - mapper = new BrowserResultToFileObjectDtoMapper(fileObjectToFileObjectDtoMapper); + mapper = null;//new BrowserResultToFileObjectDtoMapper(fileObjectToFileObjectDtoMapper); subjectThreadState.bind(); ThreadContext.bind(subject); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java index 542c0d017f..6c0266fc76 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java @@ -49,7 +49,7 @@ public class SourceRootResourceTest extends RepositoryTestBase { @Before public void prepareEnvironment() throws Exception { - browserResultToFileObjectDtoMapper = new BrowserResultToFileObjectDtoMapper(fileObjectToFileObjectDtoMapper); + browserResultToFileObjectDtoMapper = null;//new BrowserResultToFileObjectDtoMapper(fileObjectToFileObjectDtoMapper); when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service); when(service.getBrowseCommand()).thenReturn(browseCommandBuilder); From ca5642357299b1b12dfc8771cb0c12d9c6e28d13 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Mon, 28 Oct 2019 15:52:59 +0100 Subject: [PATCH 17/31] expose sources to extensionPoint --- scm-ui/ui-components/src/Breadcrumb.tsx | 11 ++++------- .../src/repos/sources/containers/Sources.tsx | 12 +++++++----- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/scm-ui/ui-components/src/Breadcrumb.tsx b/scm-ui/ui-components/src/Breadcrumb.tsx index 06423f8e1c..63c14bbf15 100644 --- a/scm-ui/ui-components/src/Breadcrumb.tsx +++ b/scm-ui/ui-components/src/Breadcrumb.tsx @@ -8,13 +8,12 @@ import { Branch, Repository } from "@scm-manager/ui-types"; import Icon from "./Icon"; type Props = WithTranslation & { - repository: Repository; branch: Branch; defaultBranch: Branch; - branches: Branch[]; revision: string; path: string; baseUrl: string; + sources: File; }; const FlexStartNav = styled.nav` @@ -59,7 +58,7 @@ class Breadcrumb extends React.Component { } render() { - const { baseUrl, branch, defaultBranch, branches, revision, path, repository, t } = this.props; + const { baseUrl, branch, defaultBranch, sources, revision, path, t } = this.props; let homeUrl = baseUrl + "/"; if (revision) { @@ -81,16 +80,14 @@ class Breadcrumb extends React.Component { {binder.hasExtension("repos.sources.actionbar") && ( + {console.log(sources)} b.name.replace("/", "%2F") === revision).length > 0 - : true, - repository + sources }} renderAll={true} /> diff --git a/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx b/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx index 037f93e166..fc5c08e8d6 100644 --- a/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx +++ b/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx @@ -13,7 +13,7 @@ import { } from "../../branches/modules/branches"; import { compose } from "redux"; import Content from "./Content"; -import { fetchSources, isDirectory } from "../modules/sources"; +import {fetchSources, getSources, isDirectory} from "../modules/sources"; type Props = WithTranslation & { repository: Repository; @@ -24,6 +24,7 @@ type Props = WithTranslation & { revision: string; path: string; currentFileIsDirectory: boolean; + sources: File; // dispatch props fetchBranches: (p: Repository) => void; @@ -148,7 +149,7 @@ class Sources extends React.Component { }; renderBreadcrumb = () => { - const { revision, path, baseUrl, branches, repository } = this.props; + const { revision, path, baseUrl, branches, sources } = this.props; const { selectedBranch } = this.state; return ( @@ -160,8 +161,7 @@ class Sources extends React.Component { defaultBranch={ branches && branches.filter(b => b.defaultBranch === true)[0] } - branches={branches} - repository={repository} + sources={sources} /> ); }; @@ -177,6 +177,7 @@ const mapStateToProps = (state, ownProps) => { const currentFileIsDirectory = decodedRevision ? isDirectory(state, repository, decodedRevision, path) : isDirectory(state, repository, revision, path); + const sources = getSources(state, repository, revision, path); return { repository, @@ -185,7 +186,8 @@ const mapStateToProps = (state, ownProps) => { loading, error, branches, - currentFileIsDirectory + currentFileIsDirectory, + sources }; }; From f4b1bf289a368255906f40f915c90d1a7262426b Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 29 Oct 2019 08:37:50 +0100 Subject: [PATCH 18/31] change fileObjectMapping --- .../v2/resources/BaseFileObjectDtoMapper.java | 3 -- .../BrowserResultToFileObjectDtoMapper.java | 34 +++++++++++++++++-- .../FileObjectToFileObjectDtoMapper.java | 5 +++ 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BaseFileObjectDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BaseFileObjectDtoMapper.java index d470ccd5ad..a96333b1ae 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BaseFileObjectDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BaseFileObjectDtoMapper.java @@ -22,9 +22,6 @@ abstract class BaseFileObjectDtoMapper extends HalAppenderMapper implements Inst @Inject private ResourceLinks resourceLinks; - @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes - protected abstract FileObjectDto map(FileObject fileObject, @Context NamespaceAndName namespaceAndName, @Context BrowserResult browserResult); - abstract SubRepositoryDto mapSubrepository(SubRepository subRepository); @ObjectFactory diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java index 6f190bade6..007b714c7f 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java @@ -4,21 +4,51 @@ import de.otto.edison.hal.Embedded; import de.otto.edison.hal.Links; import org.mapstruct.Context; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Qualifier; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.FileObject; import sonia.scm.repository.NamespaceAndName; +import javax.inject.Inject; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + @Mapper public abstract class BrowserResultToFileObjectDtoMapper extends BaseFileObjectDtoMapper { + @Inject + private FileObjectToFileObjectDtoMapper childrenMapper; + FileObjectDto map(BrowserResult browserResult, @Context NamespaceAndName namespaceAndName) { - FileObjectDto fileObjectDto = map(browserResult.getFile(), namespaceAndName, browserResult); + FileObjectDto fileObjectDto = fileObjectToDto(browserResult.getFile(), namespaceAndName, browserResult); fileObjectDto.setRevision(browserResult.getRevision()); return fileObjectDto; } + @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes + @Mapping(target = "children", qualifiedBy = Children.class) + protected abstract FileObjectDto fileObjectToDto(FileObject fileObject, @Context NamespaceAndName namespaceAndName, @Context BrowserResult browserResult); + + @Children + protected FileObjectDto childrenToDto(FileObject fileObject, @Context NamespaceAndName namespaceAndName, @Context BrowserResult browserResult) { + return childrenMapper.map(fileObject, namespaceAndName, browserResult); + } + @Override void applyEnrichers(Links.Builder links, Embedded.Builder embeddedBuilder, NamespaceAndName namespaceAndName, BrowserResult browserResult, FileObject fileObject) { - applyEnrichers(new EdisonHalAppender(links, embeddedBuilder), browserResult, namespaceAndName); + EdisonHalAppender appender = new EdisonHalAppender(links, embeddedBuilder); + // we call enrichers, which are only responsible for top level browseresults + applyEnrichers(appender, browserResult, namespaceAndName); + // we call enrichers, which are responsible for all file object top level browse result and its children + applyEnrichers(appender, fileObject, namespaceAndName, browserResult, browserResult.getRevision()); + } + + @Qualifier + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.CLASS) + @interface Children { } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java index d9ecbcfd4e..c2884a460f 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java @@ -2,7 +2,9 @@ package sonia.scm.api.v2.resources; import de.otto.edison.hal.Embedded; import de.otto.edison.hal.Links; +import org.mapstruct.Context; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.FileObject; import sonia.scm.repository.NamespaceAndName; @@ -10,6 +12,9 @@ import sonia.scm.repository.NamespaceAndName; @Mapper public abstract class FileObjectToFileObjectDtoMapper extends BaseFileObjectDtoMapper { + @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes + protected abstract FileObjectDto map(FileObject fileObject, @Context NamespaceAndName namespaceAndName, @Context BrowserResult browserResult); + @Override void applyEnrichers(Links.Builder links, Embedded.Builder embeddedBuilder, NamespaceAndName namespaceAndName, BrowserResult browserResult, FileObject fileObject) { applyEnrichers(new EdisonHalAppender(links, embeddedBuilder), fileObject, namespaceAndName, browserResult, browserResult.getRevision()); From 730646f4a85da3a20d1bb6af2cd3c1279c7c245e Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 29 Oct 2019 10:40:10 +0100 Subject: [PATCH 19/31] hgBrowseCommand resolves Branches as Revision for BrowseResult --- .../sonia/scm/repository/spi/HgBrowseCommand.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java index 19a8724b69..d1e28b5026 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java @@ -35,6 +35,8 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- +import com.aragost.javahg.Changeset; +import com.aragost.javahg.commands.LogCommand; import com.google.common.base.MoreObjects; import com.google.common.base.Strings; import sonia.scm.repository.BrowserResult; @@ -72,10 +74,10 @@ public class HgBrowseCommand extends AbstractCommand implements BrowseCommand public BrowserResult getBrowserResult(BrowseCommandRequest request) throws IOException { HgFileviewCommand cmd = HgFileviewCommand.on(open()); - if (!Strings.isNullOrEmpty(request.getRevision())) - { - cmd.rev(request.getRevision()); - } + String revision = MoreObjects.firstNonNull(request.getRevision(), "tip"); + Changeset c = LogCommand.on(getContext().open()).rev(revision).limit(1).single(); + + cmd.rev(c.getNode()); if (!Strings.isNullOrEmpty(request.getPath())) { @@ -98,6 +100,6 @@ public class HgBrowseCommand extends AbstractCommand implements BrowseCommand } FileObject file = cmd.execute(); - return new BrowserResult(MoreObjects.firstNonNull(request.getRevision(), "tip"), file); + return new BrowserResult(c.getNode(), file); } } From a13d129eff71a8d4ca2f2852c17d88d612ee3670 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 29 Oct 2019 10:49:38 +0100 Subject: [PATCH 20/31] add Test / HgBrowseCommand shouldResolveBranchForRevision --- .../scm/repository/spi/HgBrowseCommandTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBrowseCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBrowseCommandTest.java index 32b536e69d..2116d06a7a 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBrowseCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBrowseCommandTest.java @@ -33,6 +33,7 @@ package sonia.scm.repository.spi; +import com.aragost.javahg.commands.LogCommand; import org.junit.Test; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.FileObject; @@ -40,6 +41,7 @@ import sonia.scm.repository.FileObject; import java.io.IOException; import java.util.Collection; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -79,6 +81,19 @@ public class HgBrowseCommandTest extends AbstractHgCommandTestBase { assertEquals("c", c.getPath()); } + @Test + public void testBrowseShouldResolveBranchForRevision() throws IOException { + String defaultBranchRevision = new LogCommand(cmdContext.open()).rev("default").single().getNode(); + + BrowseCommandRequest browseCommandRequest = new BrowseCommandRequest(); + browseCommandRequest.setRevision("default"); + + BrowserResult result = new HgBrowseCommand(cmdContext, + repository).getBrowserResult(browseCommandRequest); + + assertThat(result.getRevision()).isEqualTo(defaultBranchRevision); + } + @Test public void testBrowseSubDirectory() throws IOException { BrowseCommandRequest request = new BrowseCommandRequest(); From 7fd3d085409ab73d39a37aef80833af0f5b15217 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 29 Oct 2019 13:04:29 +0100 Subject: [PATCH 21/31] add requestedRevision to BrowseCommandResult --- .../src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java index d1e28b5026..48772ad5e5 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java @@ -100,6 +100,6 @@ public class HgBrowseCommand extends AbstractCommand implements BrowseCommand } FileObject file = cmd.execute(); - return new BrowserResult(c.getNode(), file); + return new BrowserResult(c.getNode(), revision, file); } } From 48154cce0ff2c3f48f1ba416b2ca47046e122670 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 29 Oct 2019 13:49:04 +0100 Subject: [PATCH 22/31] fix unit tests --- .../scm/api/v2/resources/BaseFileObjectDtoMapper.java | 7 ++++++- .../v2/resources/BrowserResultToFileObjectDtoMapper.java | 6 ++++++ .../resources/BrowserResultToFileObjectDtoMapperTest.java | 7 +++++-- .../sonia/scm/api/v2/resources/SourceRootResourceTest.java | 5 ++++- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BaseFileObjectDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BaseFileObjectDtoMapper.java index a96333b1ae..bb74bc5045 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BaseFileObjectDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BaseFileObjectDtoMapper.java @@ -1,10 +1,10 @@ package sonia.scm.api.v2.resources; +import com.google.common.annotations.VisibleForTesting; import de.otto.edison.hal.Embedded; import de.otto.edison.hal.Links; import org.mapstruct.Context; import org.mapstruct.MapperConfig; -import org.mapstruct.Mapping; import org.mapstruct.ObjectFactory; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.FileObject; @@ -22,6 +22,11 @@ abstract class BaseFileObjectDtoMapper extends HalAppenderMapper implements Inst @Inject private ResourceLinks resourceLinks; + @VisibleForTesting + void setResourceLinks(ResourceLinks resourceLinks) { + this.resourceLinks = resourceLinks; + } + abstract SubRepositoryDto mapSubrepository(SubRepository subRepository); @ObjectFactory diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java index 007b714c7f..b8e34f8101 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java @@ -1,5 +1,6 @@ package sonia.scm.api.v2.resources; +import com.google.common.annotations.VisibleForTesting; import de.otto.edison.hal.Embedded; import de.otto.edison.hal.Links; import org.mapstruct.Context; @@ -22,6 +23,11 @@ public abstract class BrowserResultToFileObjectDtoMapper extends BaseFileObjectD @Inject private FileObjectToFileObjectDtoMapper childrenMapper; + @VisibleForTesting + void setChildrenMapper(FileObjectToFileObjectDtoMapper childrenMapper) { + this.childrenMapper = childrenMapper; + } + FileObjectDto map(BrowserResult browserResult, @Context NamespaceAndName namespaceAndName) { FileObjectDto fileObjectDto = fileObjectToDto(browserResult.getFile(), namespaceAndName, browserResult); fileObjectDto.setRevision(browserResult.getRevision()); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapperTest.java index eae634d854..162a90d45a 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapperTest.java @@ -7,6 +7,7 @@ import org.apache.shiro.util.ThreadState; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.mapstruct.factory.Mappers; import org.mockito.InjectMocks; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.FileObject; @@ -21,7 +22,6 @@ import static org.mockito.MockitoAnnotations.initMocks; public class BrowserResultToFileObjectDtoMapperTest { private final URI baseUri = URI.create("http://example.com/base/"); - @SuppressWarnings("unused") // Is injected private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); @InjectMocks @@ -39,7 +39,10 @@ public class BrowserResultToFileObjectDtoMapperTest { @Before public void init() { initMocks(this); - mapper = null;//new BrowserResultToFileObjectDtoMapper(fileObjectToFileObjectDtoMapper); + mapper = Mappers.getMapper(BrowserResultToFileObjectDtoMapper.class); + mapper.setChildrenMapper(fileObjectToFileObjectDtoMapper); + mapper.setResourceLinks(resourceLinks); + subjectThreadState.bind(); ThreadContext.bind(subject); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java index 6c0266fc76..1a45d3b233 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java @@ -7,6 +7,7 @@ import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mapstruct.factory.Mappers; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; @@ -49,7 +50,9 @@ public class SourceRootResourceTest extends RepositoryTestBase { @Before public void prepareEnvironment() throws Exception { - browserResultToFileObjectDtoMapper = null;//new BrowserResultToFileObjectDtoMapper(fileObjectToFileObjectDtoMapper); + browserResultToFileObjectDtoMapper = Mappers.getMapper(BrowserResultToFileObjectDtoMapper.class); + browserResultToFileObjectDtoMapper.setChildrenMapper(fileObjectToFileObjectDtoMapper); + browserResultToFileObjectDtoMapper.setResourceLinks(resourceLinks); when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service); when(service.getBrowseCommand()).thenReturn(browseCommandBuilder); From 4f17bc6b1cbe27341e7777b9b99a7935ea7fa07e Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Wed, 30 Oct 2019 12:59:54 +0100 Subject: [PATCH 23/31] refactor backend for modifyCommand / add sourceExtension for plugins --- .../scm/repository/spi/GitLogCommandTest.java | 1 - .../scm/repository/spi/SvnModifyCommand.java | 8 +- scm-ui/ui-components/src/Breadcrumb.tsx | 8 +- .../src/repos/containers/RepositoryRoot.tsx | 10 ++ .../src/repos/sources/containers/Content.tsx | 3 +- .../sources/containers/SourceExtensions.tsx | 98 +++++++++++++++++++ .../src/repos/sources/containers/Sources.tsx | 3 +- 7 files changed, 121 insertions(+), 10 deletions(-) create mode 100644 scm-ui/ui-webapp/src/repos/sources/containers/SourceExtensions.tsx diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java index b0b2f6adb7..095090b94a 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java @@ -208,7 +208,6 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase GitLogCommand command = createCommand(); Changeset c = command.getChangeset("435df2f061add3589cb3", request); - Assertions.assertThat(c.getBranches().isEmpty()).isFalse(); Assertions.assertThat(c.getBranches().contains("master")).isTrue(); Assertions.assertThat(c.getBranches().size()).isEqualTo(1); } diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModifyCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModifyCommand.java index f624c40806..671ab604be 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModifyCommand.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModifyCommand.java @@ -1,5 +1,6 @@ package sonia.scm.repository.spi; +import org.tmatesoft.svn.core.SVNCommitInfo; import org.tmatesoft.svn.core.SVNDepth; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.wc.SVNClientManager; @@ -72,10 +73,9 @@ public class SvnModifyCommand implements ModifyCommand { } } try { - clientManager.getCommitClient().doCommit(new File[] {workingRepository}, false, - request.getCommitMessage(), null, null, false, true, SVNDepth.INFINITY); - //I can't get the newest revision after commiting without creating a new working copy - return String.valueOf(clientManager.getStatusClient().doStatus(workingRepository, false).getCommittedRevision().getNumber() + 1); + SVNCommitInfo svnCommitInfo = clientManager.getCommitClient().doCommit(new File[]{workingRepository}, false, + request.getCommitMessage(), null, null, false, true, SVNDepth.INFINITY); + return String.valueOf(svnCommitInfo.getNewRevision()); } catch (SVNException e) { throw new InternalRepositoryException(repository, "could not commit changes on repository"); } diff --git a/scm-ui/ui-components/src/Breadcrumb.tsx b/scm-ui/ui-components/src/Breadcrumb.tsx index 63c14bbf15..5fb34b79da 100644 --- a/scm-ui/ui-components/src/Breadcrumb.tsx +++ b/scm-ui/ui-components/src/Breadcrumb.tsx @@ -8,6 +8,7 @@ import { Branch, Repository } from "@scm-manager/ui-types"; import Icon from "./Icon"; type Props = WithTranslation & { + repository: Repository; branch: Branch; defaultBranch: Branch; revision: string; @@ -58,7 +59,7 @@ class Breadcrumb extends React.Component { } render() { - const { baseUrl, branch, defaultBranch, sources, revision, path, t } = this.props; + const { repository, baseUrl, branch, defaultBranch, sources, revision, path, t } = this.props; let homeUrl = baseUrl + "/"; if (revision) { @@ -80,14 +81,15 @@ class Breadcrumb extends React.Component { {binder.hasExtension("repos.sources.actionbar") && ( - {console.log(sources)} diff --git a/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx b/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx index ba974b3dfa..8e8c9867d5 100644 --- a/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx +++ b/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx @@ -20,6 +20,7 @@ import PermissionsNavLink from "../components/PermissionsNavLink"; import Sources from "../sources/containers/Sources"; import RepositoryNavLink from "../components/RepositoryNavLink"; import { getLinks, getRepositoriesLink } from "../../modules/indexResource"; +import SourceExtensions from "../sources/containers/SourceExtensions"; type Props = WithTranslation & { namespace: string; @@ -120,6 +121,15 @@ class RepositoryRoot extends React.Component { path={`${url}/sources/:revision/:path*`} render={() => } /> + } + /> + } + /> ( diff --git a/scm-ui/ui-webapp/src/repos/sources/containers/Content.tsx b/scm-ui/ui-webapp/src/repos/sources/containers/Content.tsx index 59f93e8453..adb56ecd97 100644 --- a/scm-ui/ui-webapp/src/repos/sources/containers/Content.tsx +++ b/scm-ui/ui-webapp/src/repos/sources/containers/Content.tsx @@ -75,7 +75,7 @@ class Content extends React.Component { }; showHeader() { - const { file, revision } = this.props; + const { repository, file, revision } = this.props; const { showHistory, collapsed } = this.state; const icon = collapsed ? "angle-right" : "angle-down"; @@ -99,6 +99,7 @@ class Content extends React.Component { void; +}; + +const extensionPointName = "repos.sources.extensions"; + +class SourceExtensions extends React.Component { + componentDidMount() { + const { fetchSources, repository, revision, path } = this.props; + // TODO get typing right + fetchSources(repository, revision || "", path || ""); + } + + render() { + const { loading, error, repository, extension, revision, path, sources } = this.props; + if (error) { + return ; + } + if (loading) { + return ; + } + + const extprops = { extension, repository, revision, path, sources }; + if (!binder.hasExtension(extensionPointName, extprops)) { + // TODO i18n + return No extension bound; + } + + return ; + } +} + +const mapStateToProps = (state: any, ownProps: Props): Partial => { + const { repository, match } = ownProps; + // TODO add query-string v6 + // see : https://www.pluralsight.com/guides/react-router-typescript + // @ts-ignore + const revision: string = match.params.revision; + // @ts-ignore + const path: string = match.params.path; + // @ts-ignore + const extension: string = match.params.extension; + const decodedRevision = revision ? decodeURIComponent(revision) : undefined; + const loading = isFetchSourcesPending(state, repository, revision, path); + const error = getFetchSourcesFailure(state, repository, revision, path); + const sources = getSources(state, repository, revision, path); + + return { + repository, + extension, + revision: decodedRevision, + path, + loading, + error, + sources + }; +}; + +const mapDispatchToProps = (dispatch: any) => { + return { + fetchSources: (repository: Repository, revision: string, path: string) => { + dispatch(fetchSources(repository, revision, path)); + } + }; +}; + +export default withRouter(connect( + mapStateToProps, + mapDispatchToProps +)(SourceExtensions)); diff --git a/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx b/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx index fc5c08e8d6..7a28178b51 100644 --- a/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx +++ b/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx @@ -149,11 +149,12 @@ class Sources extends React.Component { }; renderBreadcrumb = () => { - const { revision, path, baseUrl, branches, sources } = this.props; + const { revision, path, baseUrl, branches, sources, repository } = this.props; const { selectedBranch } = this.state; return ( Date: Fri, 1 Nov 2019 09:29:57 +0100 Subject: [PATCH 24/31] use i18n for missing extension warning / fix encoding for branches containing slashes --- scm-ui/ui-webapp/public/locales/de/repos.json | 5 ++- scm-ui/ui-webapp/public/locales/en/repos.json | 5 ++- scm-ui/ui-webapp/public/locales/es/repos.json | 5 ++- .../src/repos/containers/RepositoryRoot.tsx | 7 ++++ .../sources/containers/SourceExtensions.tsx | 36 ++++++++----------- .../src/repos/sources/containers/Sources.tsx | 12 +++---- .../src/repos/sources/modules/sources.ts | 10 +++--- 7 files changed, 45 insertions(+), 35 deletions(-) diff --git a/scm-ui/ui-webapp/public/locales/de/repos.json b/scm-ui/ui-webapp/public/locales/de/repos.json index 8ae5c405af..e156cc903e 100644 --- a/scm-ui/ui-webapp/public/locales/de/repos.json +++ b/scm-ui/ui-webapp/public/locales/de/repos.json @@ -113,7 +113,10 @@ "description": "Beschreibung", "size": "Größe" }, - "noSources": "Keine Sources in diesem Branch gefunden." + "noSources": "Keine Sources in diesem Branch gefunden.", + "extension" : { + "notBound": "Keine Erweiterung angebunden." + } }, "permission": { "title": "Berechtigungen bearbeiten", diff --git a/scm-ui/ui-webapp/public/locales/en/repos.json b/scm-ui/ui-webapp/public/locales/en/repos.json index d621bad7bf..0466bab77f 100644 --- a/scm-ui/ui-webapp/public/locales/en/repos.json +++ b/scm-ui/ui-webapp/public/locales/en/repos.json @@ -113,7 +113,10 @@ "description": "Description", "size": "Size" }, - "noSources": "No sources found for this branch." + "noSources": "No sources found for this branch.", + "extension" : { + "notBound": "No extension bound." + } }, "permission": { "title": "Edit Permissions", diff --git a/scm-ui/ui-webapp/public/locales/es/repos.json b/scm-ui/ui-webapp/public/locales/es/repos.json index 8372c7e592..ef9bd2badc 100644 --- a/scm-ui/ui-webapp/public/locales/es/repos.json +++ b/scm-ui/ui-webapp/public/locales/es/repos.json @@ -113,7 +113,10 @@ "description": "Discripción", "size": "tamaño" }, - "noSources": "No se han encontrado fuentes para esta rama." + "noSources": "No se han encontrado fuentes para esta rama.", + "extension" : { + "notBound": "Sin extensión conectada." + } }, "permission": { "title": "Editar permisos", diff --git a/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx b/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx index 8e8c9867d5..1e2b94f828 100644 --- a/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx +++ b/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx @@ -68,6 +68,12 @@ class RepositoryRoot extends React.Component { return route.location.pathname.match(regex); }; + matchesSources = (route: any) => { + const url = this.matchedUrl(); + const regex = new RegExp(`${url}/(sources|sourceext)/.*`); + return route.location.pathname.match(regex); + }; + render() { const { loading, error, indexLinks, repository, t } = this.props; @@ -196,6 +202,7 @@ class RepositoryRoot extends React.Component { to={`${url}/sources`} icon="fas fa-code" label={t("repositoryRoot.menu.sourcesNavLink")} + activeWhenMatch={this.matchesSources} activeOnlyWhenExact={false} /> diff --git a/scm-ui/ui-webapp/src/repos/sources/containers/SourceExtensions.tsx b/scm-ui/ui-webapp/src/repos/sources/containers/SourceExtensions.tsx index a340d6ef26..c75b0106a9 100644 --- a/scm-ui/ui-webapp/src/repos/sources/containers/SourceExtensions.tsx +++ b/scm-ui/ui-webapp/src/repos/sources/containers/SourceExtensions.tsx @@ -3,17 +3,13 @@ import { Repository, File } from "@scm-manager/ui-types"; import { withRouter, RouteComponentProps } from "react-router-dom"; import { ExtensionPoint, binder } from "@scm-manager/ui-extensions"; -import { - fetchSources, - getFetchSourcesFailure, - getSources, - isFetchSourcesPending -} from "../modules/sources"; -import { connect} from "react-redux"; +import { fetchSources, getFetchSourcesFailure, getSources, isFetchSourcesPending } from "../modules/sources"; +import { connect } from "react-redux"; import { Loading, ErrorNotification } from "@scm-manager/ui-components"; import Notification from "@scm-manager/ui-components/src/Notification"; +import {WithTranslation, withTranslation} from "react-i18next"; -type Props = RouteComponentProps & { +type Props = WithTranslation & RouteComponentProps & { repository: Repository; // url params @@ -36,11 +32,11 @@ class SourceExtensions extends React.Component { componentDidMount() { const { fetchSources, repository, revision, path } = this.props; // TODO get typing right - fetchSources(repository, revision || "", path || ""); + fetchSources(repository,revision || "", path || ""); } render() { - const { loading, error, repository, extension, revision, path, sources } = this.props; + const { loading, error, repository, extension, revision, path, sources, t } = this.props; if (error) { return ; } @@ -50,8 +46,7 @@ class SourceExtensions extends React.Component { const extprops = { extension, repository, revision, path, sources }; if (!binder.hasExtension(extensionPointName, extprops)) { - // TODO i18n - return No extension bound; + return {t("sources.extension.notBound")}; } return ; @@ -60,15 +55,12 @@ class SourceExtensions extends React.Component { const mapStateToProps = (state: any, ownProps: Props): Partial => { const { repository, match } = ownProps; - // TODO add query-string v6 - // see : https://www.pluralsight.com/guides/react-router-typescript // @ts-ignore const revision: string = match.params.revision; // @ts-ignore const path: string = match.params.path; // @ts-ignore const extension: string = match.params.extension; - const decodedRevision = revision ? decodeURIComponent(revision) : undefined; const loading = isFetchSourcesPending(state, repository, revision, path); const error = getFetchSourcesFailure(state, repository, revision, path); const sources = getSources(state, repository, revision, path); @@ -76,7 +68,7 @@ const mapStateToProps = (state: any, ownProps: Props): Partial => { return { repository, extension, - revision: decodedRevision, + revision, path, loading, error, @@ -87,12 +79,14 @@ const mapStateToProps = (state: any, ownProps: Props): Partial => { const mapDispatchToProps = (dispatch: any) => { return { fetchSources: (repository: Repository, revision: string, path: string) => { - dispatch(fetchSources(repository, revision, path)); + dispatch(fetchSources(repository, decodeURIComponent(revision), path)); } }; }; -export default withRouter(connect( - mapStateToProps, - mapDispatchToProps -)(SourceExtensions)); +export default withRouter( + connect( + mapStateToProps, + mapDispatchToProps + )(withTranslation("repos")(SourceExtensions)) +); diff --git a/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx b/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx index 7a28178b51..b5c34b7a21 100644 --- a/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx +++ b/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx @@ -171,18 +171,18 @@ class Sources extends React.Component { const mapStateToProps = (state, ownProps) => { const { repository, match } = ownProps; const { revision, path } = match.params; - const decodedRevision = revision ? decodeURIComponent(revision) : undefined; + const decodedRevision = revision ? decodeURIComponent(revision) : revision; const loading = isFetchBranchesPending(state, repository); const error = getFetchBranchesFailure(state, repository); const branches = getBranches(state, repository); - const currentFileIsDirectory = decodedRevision + const currentFileIsDirectory = revision ? isDirectory(state, repository, decodedRevision, path) - : isDirectory(state, repository, revision, path); - const sources = getSources(state, repository, revision, path); + : isDirectory(state, repository, decodedRevision, path); + const sources = getSources(state, repository, decodedRevision, path); return { repository, - revision: decodedRevision, + revision, path, loading, error, @@ -198,7 +198,7 @@ const mapDispatchToProps = dispatch => { dispatch(fetchBranches(repository)); }, fetchSources: (repository: Repository, revision: string, path: string) => { - dispatch(fetchSources(repository, revision, path)); + dispatch(fetchSources(repository, decodeURIComponent(revision), path)); } }; }; diff --git a/scm-ui/ui-webapp/src/repos/sources/modules/sources.ts b/scm-ui/ui-webapp/src/repos/sources/modules/sources.ts index 8fbd45f864..d3d0380a69 100644 --- a/scm-ui/ui-webapp/src/repos/sources/modules/sources.ts +++ b/scm-ui/ui-webapp/src/repos/sources/modules/sources.ts @@ -1,5 +1,5 @@ import * as types from "../../../modules/types"; -import { Repository, File, Action } from "@scm-manager/ui-types"; +import { Repository, File, Action, Link } from "@scm-manager/ui-types"; import { apiClient } from "@scm-manager/ui-components"; import { isPending } from "../../../modules/pending"; import { getFailure } from "../../../modules/failure"; @@ -16,16 +16,16 @@ export function fetchSources(repository: Repository, revision: string, path: str .get(createUrl(repository, revision, path)) .then(response => response.json()) .then(sources => { - dispatch(fetchSourcesSuccess(repository, revision, path, sources)); + dispatch(fetchSourcesSuccess(repository, decodeURIComponent(revision), path, sources)); }) .catch(err => { - dispatch(fetchSourcesFailure(repository, revision, path, err)); + dispatch(fetchSourcesFailure(repository, decodeURIComponent(revision), path, err)); }); }; } function createUrl(repository: Repository, revision: string, path: string) { - const base = repository._links.sources.href; + const base = (repository._links.sources as Link).href; if (!revision && !path) { return base; } @@ -61,7 +61,7 @@ export function fetchSourcesFailure(repository: Repository, revision: string, pa function createItemId(repository: Repository, revision: string, path: string) { const revPart = revision ? revision : "_"; const pathPart = path ? path : ""; - return `${repository.namespace}/${repository.name}/${revPart}/${pathPart}`; + return `${repository.namespace}/${repository.name}/${decodeURIComponent(revPart)}/${pathPart}`; } // reducer From 33cce15e33f0a2e342782ce13206f9f5512adc76 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Fri, 1 Nov 2019 09:40:20 +0100 Subject: [PATCH 25/31] fix navLink Highlighting for sourceext --- scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx b/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx index 1e2b94f828..d65f2c9892 100644 --- a/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx +++ b/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx @@ -70,7 +70,7 @@ class RepositoryRoot extends React.Component { matchesSources = (route: any) => { const url = this.matchedUrl(); - const regex = new RegExp(`${url}/(sources|sourceext)/.*`); + const regex = new RegExp(`${url}(/sources|/sourceext)/.*`); return route.location.pathname.match(regex); }; From 557856185ae14dff1664503f22e5cf5d76bffdbc Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Fri, 1 Nov 2019 10:18:13 +0100 Subject: [PATCH 26/31] small fix --- scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx b/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx index b5c34b7a21..0a5608e6e5 100644 --- a/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx +++ b/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx @@ -175,9 +175,9 @@ const mapStateToProps = (state, ownProps) => { const loading = isFetchBranchesPending(state, repository); const error = getFetchBranchesFailure(state, repository); const branches = getBranches(state, repository); - const currentFileIsDirectory = revision + const currentFileIsDirectory = decodedRevision ? isDirectory(state, repository, decodedRevision, path) - : isDirectory(state, repository, decodedRevision, path); + : isDirectory(state, repository, revision, path); const sources = getSources(state, repository, decodedRevision, path); return { From d5bd83fafba14cf78ddb4b21a0d04002ec9d8d7b Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Fri, 1 Nov 2019 13:52:53 +0100 Subject: [PATCH 27/31] fix encoding and decoding for branches including slashes --- .../src/repos/sources/containers/Sources.tsx | 12 ++++++++---- .../ui-webapp/src/repos/sources/modules/sources.ts | 4 ++-- .../scm/api/v2/resources/SourceRootResource.java | 3 ++- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx b/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx index 0a5608e6e5..bdf654b08a 100644 --- a/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx +++ b/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx @@ -53,7 +53,7 @@ class Sources extends React.Component { const { fetchBranches, repository, revision, path, fetchSources } = this.props; fetchBranches(repository); - fetchSources(repository, revision, path); + fetchSources(repository, this.decodeRevision(revision), path); this.redirectToDefaultBranch(); } @@ -61,12 +61,16 @@ class Sources extends React.Component { componentDidUpdate(prevProps) { const { fetchSources, repository, revision, path } = this.props; if (prevProps.revision !== revision || prevProps.path !== path) { - fetchSources(repository, revision, path); + fetchSources(repository, this.decodeRevision(revision), path); } this.redirectToDefaultBranch(); } + decodeRevision = (revision: string) => { + return revision ? decodeURIComponent(revision) : revision; + }; + redirectToDefaultBranch = () => { const { branches } = this.props; if (this.shouldRedirectToDefaultBranch()) { @@ -171,7 +175,7 @@ class Sources extends React.Component { const mapStateToProps = (state, ownProps) => { const { repository, match } = ownProps; const { revision, path } = match.params; - const decodedRevision = revision ? decodeURIComponent(revision) : revision; + const decodedRevision = revision ? decodeURIComponent(revision) : undefined; const loading = isFetchBranchesPending(state, repository); const error = getFetchBranchesFailure(state, repository); const branches = getBranches(state, repository); @@ -198,7 +202,7 @@ const mapDispatchToProps = dispatch => { dispatch(fetchBranches(repository)); }, fetchSources: (repository: Repository, revision: string, path: string) => { - dispatch(fetchSources(repository, decodeURIComponent(revision), path)); + dispatch(fetchSources(repository, revision, path)); } }; }; diff --git a/scm-ui/ui-webapp/src/repos/sources/modules/sources.ts b/scm-ui/ui-webapp/src/repos/sources/modules/sources.ts index d3d0380a69..826b32f1f6 100644 --- a/scm-ui/ui-webapp/src/repos/sources/modules/sources.ts +++ b/scm-ui/ui-webapp/src/repos/sources/modules/sources.ts @@ -16,10 +16,10 @@ export function fetchSources(repository: Repository, revision: string, path: str .get(createUrl(repository, revision, path)) .then(response => response.json()) .then(sources => { - dispatch(fetchSourcesSuccess(repository, decodeURIComponent(revision), path, sources)); + dispatch(fetchSourcesSuccess(repository, revision, path, sources)); }) .catch(err => { - dispatch(fetchSourcesFailure(repository, decodeURIComponent(revision), path, err)); + dispatch(fetchSourcesFailure(repository, revision, path, err)); }); }; } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java index b5c7e205be..b431997462 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java @@ -14,6 +14,7 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Response; import java.io.IOException; +import java.net.URLDecoder; import static sonia.scm.ContextEntry.ContextBuilder.entity; import static sonia.scm.NotFoundException.notFound; @@ -57,7 +58,7 @@ public class SourceRootResource { BrowseCommandBuilder browseCommand = repositoryService.getBrowseCommand(); browseCommand.setPath(path); if (revision != null && !revision.isEmpty()) { - browseCommand.setRevision(revision); + browseCommand.setRevision(URLDecoder.decode(revision, "UTF-8")); } browseCommand.setDisableCache(true); BrowserResult browserResult = browseCommand.getBrowserResult(); From e4c8783c614938a6653e8d45cfdf8fe0ccc2d219 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 4 Nov 2019 11:46:27 +0100 Subject: [PATCH 28/31] return null if commit was not merged into requested branch --- .../scm/repository/spi/GitLogCommand.java | 25 +++++++++++++------ .../scm/repository/spi/GitLogCommandTest.java | 10 ++++++-- .../main/java/sonia/scm/util/MockUtil.java | 4 +-- yarn.lock | 7 +++++- 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java index 7f10948ad8..8c44f33f7f 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java @@ -132,13 +132,16 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand { converter = new GitChangesetConverter(gr, revWalk); - if (request != null && - !Strings.isNullOrEmpty(request.getBranch()) && - revWalk.isMergedInto(commit, findTipCommitForRequestBranch(request, gr, revWalk))) { - changeset = converter.createChangeset(commit, request.getBranch()); + if (isBranchRequested(request)) { + String branch = request.getBranch(); + if (isMergedIntoBranch(gr, revWalk, commit, branch)) { + logger.trace("returning commit {} with branch {}", commit.getId(), branch); + changeset = converter.createChangeset(commit, branch); + } else { + logger.debug("returning null, because commit {} was not merged into branch {}", commit.getId(), branch); + } } else { changeset = converter.createChangeset(commit); - } } else if (logger.isWarnEnabled()) @@ -165,8 +168,16 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand return changeset; } - private RevCommit findTipCommitForRequestBranch(LogCommandRequest request, Repository gr, RevWalk revWalk) throws IOException { - return revWalk.parseCommit(GitUtil.getCommit(gr, revWalk, gr.findRef(request.getBranch()))); + private boolean isMergedIntoBranch(Repository repository, RevWalk revWalk, RevCommit commit, String branchName) throws IOException { + return revWalk.isMergedInto(commit, findHeadCommitOfBranch(repository, revWalk, branchName)); + } + + private boolean isBranchRequested(LogCommandRequest request) { + return request != null && !Strings.isNullOrEmpty(request.getBranch()); + } + + private RevCommit findHeadCommitOfBranch(Repository repository, RevWalk revWalk, String branchName) throws IOException { + return revWalk.parseCommit(GitUtil.getCommit(repository, revWalk, repository.findRef(branchName))); } /** diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java index 095090b94a..71dac42f72 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java @@ -208,8 +208,14 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase GitLogCommand command = createCommand(); Changeset c = command.getChangeset("435df2f061add3589cb3", request); - Assertions.assertThat(c.getBranches().contains("master")).isTrue(); - Assertions.assertThat(c.getBranches().size()).isEqualTo(1); + Assertions.assertThat(c.getBranches()).containsOnly("master"); + } + + @Test + public void shouldNotReturnCommitFromDifferentBranch() { + when(request.getBranch()).thenReturn("master"); + Changeset changeset = createCommand().getChangeset("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4", request); + Assertions.assertThat(changeset).isNull(); } @Test diff --git a/scm-test/src/main/java/sonia/scm/util/MockUtil.java b/scm-test/src/main/java/sonia/scm/util/MockUtil.java index 415fefd620..4345b4d225 100644 --- a/scm-test/src/main/java/sonia/scm/util/MockUtil.java +++ b/scm-test/src/main/java/sonia/scm/util/MockUtil.java @@ -212,8 +212,8 @@ public final class MockUtil { SCMContextProvider provider = mock(SCMContextProvider.class); - when(provider.getBaseDirectory()).thenReturn(directory); - when(provider.resolve(any(Path.class))).then(ic -> { + lenient().when(provider.getBaseDirectory()).thenReturn(directory); + lenient().when(provider.resolve(any(Path.class))).then(ic -> { Path p = ic.getArgument(0); return directory.toPath().resolve(p); }); diff --git a/yarn.lock b/yarn.lock index 761ad121f3..cca4e09c7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3595,6 +3595,11 @@ babel-code-frame@^6.22.0: esutils "^2.0.2" js-tokens "^3.0.2" +babel-core@7.0.0-bridge.0: + version "7.0.0-bridge.0" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece" + integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg== + babel-eslint@^10.0.3: version "10.0.3" resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.3.tgz#81a2c669be0f205e19462fed2482d33e4687a88a" @@ -7235,7 +7240,7 @@ gitconfiglocal@^1.0.0: dependencies: ini "^1.3.2" -gitdiff-parser@^0.1.2: +gitdiff-parser@^0.1.2, "gitdiff-parser@https://github.com/scm-manager/gitdiff-parser#6baa7278824ecd17a199d842ca720d0453f68982": version "0.1.2" resolved "https://github.com/scm-manager/gitdiff-parser#6baa7278824ecd17a199d842ca720d0453f68982" From 3aea20370a9630206ec57f3b304692ecbdcb3fa8 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 4 Nov 2019 12:03:19 +0100 Subject: [PATCH 29/31] fixed returning null for getScmRepository from SimpleSvnWorkDirFactory --- .../spi/SimpleSvnWorkDirFactory.java | 20 ++-- .../sonia/scm/repository/spi/SvnContext.java | 112 +++++------------- .../spi/SvnRepositoryServiceProvider.java | 2 +- .../spi/AbstractSvnCommandTestBase.java | 2 +- .../spi/SimpleSvnWorkDirFactoryTest.java | 8 ++ .../spi/SvnUnbundleCommandTest.java | 2 +- 6 files changed, 49 insertions(+), 97 deletions(-) diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactory.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactory.java index 713e79a2bb..bb9976b4a9 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactory.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactory.java @@ -24,19 +24,11 @@ public class SimpleSvnWorkDirFactory extends SimpleWorkdirFactory cloneRepository(SvnContext context, File workingCopy, String initialBranch) throws IOException { + protected ParentAndClone cloneRepository(SvnContext context, File workingCopy, String initialBranch) { final SvnOperationFactory svnOperationFactory = new SvnOperationFactory(); @@ -60,4 +52,12 @@ public class SimpleSvnWorkDirFactory extends SimpleWorkdirFactory(context.getDirectory(), workingCopy); } + + @Override + protected void closeRepository(File workingCopy) { + } + + @Override + protected void closeWorkdirInternal(File workdir) { + } } diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnContext.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnContext.java index 3a04ec1a2d..f8f6986c59 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnContext.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnContext.java @@ -42,113 +42,57 @@ import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.io.SVNRepositoryFactory; +import sonia.scm.repository.Repository; import sonia.scm.repository.SvnUtil; //~--- JDK imports ------------------------------------------------------------ import java.io.Closeable; import java.io.File; -import java.io.IOException; /** * * @author Sebastian Sdorra */ -public class SvnContext implements Closeable -{ +public class SvnContext implements Closeable { - /** - * the logger for SvnContext - */ - private static final Logger logger = - LoggerFactory.getLogger(SvnContext.class); + private static final Logger LOG = LoggerFactory.getLogger(SvnContext.class); - //~--- constructors --------------------------------------------------------- + private final Repository repository; + private final File directory; - /** - * Constructs ... - * - * - * @param directory - */ - public SvnContext(File directory) - { + private SVNRepository svnRepository; + + public SvnContext(Repository repository, File directory) { + this.repository = repository; this.directory = directory; } - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @throws IOException - */ - @Override - public void close() throws IOException - { - if (logger.isTraceEnabled()) - { - logger.trace("close svn repository {}", directory); - } - - SvnUtil.closeSession(repository); - } - - /** - * Method description - * - * - * @return - * - * @throws SVNException - */ - public SVNURL createUrl() throws SVNException - { - return SVNURL.fromFile(directory); - } - - /** - * Method description - * - * - * @return - * - * @throws SVNException - */ - public SVNRepository open() throws SVNException - { - if (repository == null) - { - if (logger.isTraceEnabled()) - { - logger.trace("open svn repository {}", directory); - } - - repository = SVNRepositoryFactory.create(createUrl()); - } - + public Repository getRepository() { return repository; } - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - public File getDirectory() - { + public File getDirectory() { return directory; } - //~--- fields --------------------------------------------------------------- + public SVNURL createUrl() throws SVNException { + return SVNURL.fromFile(directory); + } - /** Field description */ - private File directory; + public SVNRepository open() throws SVNException { + if (svnRepository == null) { + LOG.trace("open svn repository {}", directory); + svnRepository = SVNRepositoryFactory.create(createUrl()); + } + + return svnRepository; + } + + @Override + public void close() { + LOG.trace("close svn repository {}", directory); + SvnUtil.closeSession(svnRepository); + } - /** Field description */ - private SVNRepository repository; } diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnRepositoryServiceProvider.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnRepositoryServiceProvider.java index e96dedd4a0..b12b787122 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnRepositoryServiceProvider.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnRepositoryServiceProvider.java @@ -66,7 +66,7 @@ public class SvnRepositoryServiceProvider extends RepositoryServiceProvider Repository repository, SvnWorkDirFactory workdirFactory) { this.repository = repository; - this.context = new SvnContext(handler.getDirectory(repository.getId())); + this.context = new SvnContext(repository, handler.getDirectory(repository.getId())); this.workDirFactory = workdirFactory; } diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/AbstractSvnCommandTestBase.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/AbstractSvnCommandTestBase.java index 81b55e5db3..a0a9e9c77d 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/AbstractSvnCommandTestBase.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/AbstractSvnCommandTestBase.java @@ -72,7 +72,7 @@ public class AbstractSvnCommandTestBase extends ZippedRepositoryTestBase { if (context == null) { - context = new SvnContext(repositoryDirectory); + context = new SvnContext(repository, repositoryDirectory); } return context; diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactoryTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactoryTest.java index 4cef2f9353..8beae9e0ed 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactoryTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SimpleSvnWorkDirFactoryTest.java @@ -5,6 +5,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.tmatesoft.svn.core.SVNException; +import sonia.scm.repository.Repository; import sonia.scm.repository.util.WorkdirProvider; import sonia.scm.repository.util.WorkingCopy; @@ -66,4 +67,11 @@ public class SimpleSvnWorkDirFactoryTest extends AbstractSvnCommandTestBase { assertThat(directory).doesNotExist(); assertThat(workingRepository).doesNotExist(); } + + @Test + public void shouldReturnRepository() { + SimpleSvnWorkDirFactory factory = new SimpleSvnWorkDirFactory(workdirProvider); + Repository scmRepository = factory.getScmRepository(createContext()); + assertThat(scmRepository).isSameAs(repository); + } } diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnUnbundleCommandTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnUnbundleCommandTest.java index 133518e8db..283a65449b 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnUnbundleCommandTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnUnbundleCommandTest.java @@ -116,6 +116,6 @@ public class SvnUnbundleCommandTest extends AbstractSvnCommandTestBase SVNRepositoryFactory.createLocalRepository(folder, true, true); - return new SvnContext(folder); + return new SvnContext(repository, folder); } } From a5324269583cfbe1a3bebd57855128a335b87460 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 4 Nov 2019 12:16:22 +0100 Subject: [PATCH 30/31] refactor SvnModifyCommand to be more readable --- .../scm/repository/spi/SvnModifyCommand.java | 126 +++++++++++------- 1 file changed, 80 insertions(+), 46 deletions(-) diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModifyCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModifyCommand.java index 671ab604be..8475cf7c48 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModifyCommand.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModifyCommand.java @@ -20,7 +20,7 @@ public class SvnModifyCommand implements ModifyCommand { private SvnWorkDirFactory workDirFactory; private Repository repository; - public SvnModifyCommand(SvnContext context, Repository repository, SvnWorkDirFactory workDirFactory) { + SvnModifyCommand(SvnContext context, Repository repository, SvnWorkDirFactory workDirFactory) { this.context = context; this.repository = repository; this.workDirFactory = workDirFactory; @@ -30,55 +30,89 @@ public class SvnModifyCommand implements ModifyCommand { public String execute(ModifyCommandRequest request) { SVNClientManager clientManager = SVNClientManager.newInstance(); try (WorkingCopy workingCopy = workDirFactory.createWorkingCopy(context, null)) { - File workingRepository = workingCopy.getWorkingRepository(); - for (ModifyCommandRequest.PartialRequest partialRequest : request.getRequests()) { - try { - SVNWCClient wcClient = clientManager.getWCClient(); - partialRequest.execute(new ModifyWorkerHelper() { - @Override - public void doScmDelete(String toBeDeleted){ - try { - wcClient.doDelete(new File(String.format("%s/%s", workingRepository, toBeDeleted)), true, true, false); - } catch (SVNException e) { - throw new InternalRepositoryException(repository, "could not delete file from repository"); - } - } + File workingDirectory = workingCopy.getDirectory(); + modifyWorkingDirectory(request, clientManager, workingDirectory); + return commitChanges(clientManager, workingDirectory, request.getCommitMessage()); + } + } - @Override - public void addFileToScm(String name, Path file) { - try { - wcClient.doAdd(file.toFile(), true, false, true, SVNDepth.INFINITY, false, true); - } catch (SVNException e) { - throw new InternalRepositoryException(repository, "could not add file to repository"); - } - } + private String commitChanges(SVNClientManager clientManager, File workingDirectory, String commitMessage) { + try { + SVNCommitInfo svnCommitInfo = clientManager.getCommitClient().doCommit( + new File[]{workingDirectory}, + false, + commitMessage, + null, + null, + false, + true, + SVNDepth.INFINITY + ); + return String.valueOf(svnCommitInfo.getNewRevision()); + } catch (SVNException e) { + throw new InternalRepositoryException(repository, "could not commit changes on repository"); + } + } - @Override - public File getWorkDir() { - return workingRepository; - } - - @Override - public Repository getRepository() { - return repository; - } - - @Override - public String getBranch() { - return null; - } - }); - } catch (IOException e) { - throw new InternalRepositoryException(repository, "could not read files from repository"); - } - } + private void modifyWorkingDirectory(ModifyCommandRequest request, SVNClientManager clientManager, File workingDirectory) { + for (ModifyCommandRequest.PartialRequest partialRequest : request.getRequests()) { try { - SVNCommitInfo svnCommitInfo = clientManager.getCommitClient().doCommit(new File[]{workingRepository}, false, - request.getCommitMessage(), null, null, false, true, SVNDepth.INFINITY); - return String.valueOf(svnCommitInfo.getNewRevision()); - } catch (SVNException e) { - throw new InternalRepositoryException(repository, "could not commit changes on repository"); + SVNWCClient wcClient = clientManager.getWCClient(); + partialRequest.execute(new ModifyWorker(wcClient, workingDirectory)); + } catch (IOException e) { + throw new InternalRepositoryException(repository, "could not read files from repository"); } } } + + private class ModifyWorker implements ModifyWorkerHelper { + private final SVNWCClient wcClient; + private final File workingDirectory; + + private ModifyWorker(SVNWCClient wcClient, File workingDirectory) { + this.wcClient = wcClient; + this.workingDirectory = workingDirectory; + } + + @Override + public void doScmDelete(String toBeDeleted) { + try { + wcClient.doDelete(new File(workingDirectory, toBeDeleted), true, true, false); + } catch (SVNException e) { + throw new InternalRepositoryException(repository, "could not delete file from repository"); + } + } + + @Override + public void addFileToScm(String name, Path file) { + try { + wcClient.doAdd( + file.toFile(), + true, + false, + true, + SVNDepth.INFINITY, + false, + true + ); + } catch (SVNException e) { + throw new InternalRepositoryException(repository, "could not add file to repository"); + } + } + + @Override + public File getWorkDir() { + return workingDirectory; + } + + @Override + public Repository getRepository() { + return repository; + } + + @Override + public String getBranch() { + return null; + } + } } From c18c64c15a86e161de5d0ef7ad06c1b0386d0443 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 5 Nov 2019 13:30:25 +0000 Subject: [PATCH 31/31] Close branch feature/modify_for_svn