From a93380b867e9aad6632fdea63457672b44fa3a60 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Mon, 21 Oct 2019 14:10:50 +0200 Subject: [PATCH 01/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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 4b024c650c2070e0caf85feb822c023ac6af388f Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Mon, 4 Nov 2019 15:16:37 +0100 Subject: [PATCH 31/60] implement squash to mergeCommand --- .../repository/api/MergeCommandBuilder.java | 12 +++++ .../scm/repository/api/ScmMergeStrategy.java | 5 ++ .../repository/spi/MergeCommandRequest.java | 16 +++++- .../scm/repository/spi/GitMergeCommand.java | 18 +++++-- .../repository/spi/GitMergeCommandTest.java | 51 ++++++++++++++++++ .../scm/repository/spi/scm-git-spi-test.zip | Bin 28443 -> 36171 bytes 6 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/repository/api/ScmMergeStrategy.java diff --git a/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java index 0a2267e888..df9f6bdfbe 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java @@ -92,6 +92,18 @@ public class MergeCommandBuilder { return this; } + /** + * Use this to set the strategy of the merge commit manually. + * + * This is optional and for {@link #executeMerge()} only. + * + * @return This builder instance. + */ + public MergeCommandBuilder setMergeStrategy(ScmMergeStrategy strategy) { + request.setScmMergeStrategy(strategy); + return this; + } + /** * Use this to set a template for the commit message. If no message is set, a default message will be used. * diff --git a/scm-core/src/main/java/sonia/scm/repository/api/ScmMergeStrategy.java b/scm-core/src/main/java/sonia/scm/repository/api/ScmMergeStrategy.java new file mode 100644 index 0000000000..7ff1b6bc51 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/api/ScmMergeStrategy.java @@ -0,0 +1,5 @@ +package sonia.scm.repository.api; + +public enum ScmMergeStrategy { + SQUASH +} diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommandRequest.java b/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommandRequest.java index 223cf8c49e..f7094a5e7f 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommandRequest.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommandRequest.java @@ -5,6 +5,7 @@ import com.google.common.base.Objects; import com.google.common.base.Strings; import sonia.scm.Validateable; import sonia.scm.repository.Person; +import sonia.scm.repository.api.ScmMergeStrategy; import sonia.scm.repository.util.AuthorUtil.CommandWithAuthor; import java.io.Serializable; @@ -17,6 +18,7 @@ public class MergeCommandRequest implements Validateable, Resetable, Serializabl private String targetBranch; private Person author; private String messageTemplate; + private ScmMergeStrategy scmMergeStrategy; public String getBranchToMerge() { return branchToMerge; @@ -50,6 +52,14 @@ public class MergeCommandRequest implements Validateable, Resetable, Serializabl this.messageTemplate = messageTemplate; } + public ScmMergeStrategy getScmMergeStrategy() { + return scmMergeStrategy; + } + + public void setScmMergeStrategy(ScmMergeStrategy scmMergeStrategy) { + this.scmMergeStrategy = scmMergeStrategy; + } + public boolean isValid() { return !Strings.isNullOrEmpty(getBranchToMerge()) && !Strings.isNullOrEmpty(getTargetBranch()); @@ -74,12 +84,13 @@ public class MergeCommandRequest implements Validateable, Resetable, Serializabl return Objects.equal(branchToMerge, other.branchToMerge) && Objects.equal(targetBranch, other.targetBranch) - && Objects.equal(author, other.author); + && Objects.equal(author, other.author) + && Objects.equal(scmMergeStrategy, other.scmMergeStrategy); } @Override public int hashCode() { - return Objects.hashCode(branchToMerge, targetBranch, author); + return Objects.hashCode(branchToMerge, targetBranch, author, scmMergeStrategy); } @Override @@ -88,6 +99,7 @@ public class MergeCommandRequest implements Validateable, Resetable, Serializabl .add("branchToMerge", branchToMerge) .add("targetBranch", targetBranch) .add("author", author) + .add("mergeStrategy", scmMergeStrategy) .toString(); } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java index 5643c858b5..bafe6b8376 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java @@ -16,6 +16,7 @@ import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Person; import sonia.scm.repository.api.MergeCommandResult; import sonia.scm.repository.api.MergeDryRunCommandResult; +import sonia.scm.repository.api.ScmMergeStrategy; import java.io.IOException; import java.text.MessageFormat; @@ -61,6 +62,7 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand private final String toMerge; private final Person author; private final String messageTemplate; + private final ScmMergeStrategy scmMergeStrategy; private MergeWorker(Git clone, MergeCommandRequest request) { super(clone); @@ -68,6 +70,7 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand this.toMerge = request.getBranchToMerge(); this.author = request.getAuthor(); this.messageTemplate = request.getMessageTemplate(); + this.scmMergeStrategy = request.getScmMergeStrategy(); } @Override @@ -86,11 +89,18 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand MergeResult result; try { ObjectId sourceRevision = resolveRevision(toMerge); - result = getClone().merge() - .setFastForward(FastForwardMode.NO_FF) + org.eclipse.jgit.api.MergeCommand mergeCommand = getClone().merge(); + mergeCommand .setCommit(false) // we want to set the author manually - .include(toMerge, sourceRevision) - .call(); + .include(toMerge, sourceRevision); + + if (scmMergeStrategy == ScmMergeStrategy.SQUASH) { + mergeCommand.setSquash(true); + } else { + mergeCommand.setFastForward(FastForwardMode.NO_FF); + } + + result = mergeCommand.call(); } catch (GitAPIException e) { throw new InternalRepositoryException(context.getRepository(), "could not merge branch " + toMerge + " into " + target, e); } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java index 5586a2f710..873dd23498 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java @@ -15,10 +15,12 @@ import org.junit.Test; import sonia.scm.NotFoundException; import sonia.scm.repository.Person; import sonia.scm.repository.api.MergeCommandResult; +import sonia.scm.repository.api.ScmMergeStrategy; import sonia.scm.repository.util.WorkdirProvider; import sonia.scm.user.User; import java.io.IOException; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -211,6 +213,55 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase { assertThat(new String(contentOfFileB)).isEqualTo("b\ncontent from branch\n"); } + @Test + public void shouldSquashCommitsIfSquashIsEnabled() throws IOException, GitAPIException { + GitMergeCommand command = createCommand(); + MergeCommandRequest request = new MergeCommandRequest(); + request.setAuthor(new Person("Dirk Gently", "dirk@holistic.det")); + request.setBranchToMerge("squash"); + request.setTargetBranch("master"); + request.setMessageTemplate("this is a squash"); + request.setScmMergeStrategy(ScmMergeStrategy.SQUASH); + + MergeCommandResult mergeCommandResult = command.merge(request); + + Repository repository = createContext().open(); + assertThat(mergeCommandResult.isSuccess()).isTrue(); + + Iterable commits = new Git(repository).log().add(repository.resolve("master")).setMaxCount(1).call(); + RevCommit mergeCommit = commits.iterator().next(); + PersonIdent mergeAuthor = mergeCommit.getAuthorIdent(); + String message = mergeCommit.getFullMessage(); + assertThat(mergeAuthor.getName()).isEqualTo("Dirk Gently"); + assertThat(message).isEqualTo("this is a squash"); + } + + @Test + public void shouldSquashThreeCommitsIntoOne() throws IOException, GitAPIException { + GitMergeCommand command = createCommand(); + MergeCommandRequest request = new MergeCommandRequest(); + request.setAuthor(new Person("Dirk Gently", "dirk@holistic.det")); + request.setBranchToMerge("squash"); + request.setTargetBranch("master"); + request.setMessageTemplate("squash three commits"); + request.setScmMergeStrategy(ScmMergeStrategy.SQUASH); + Repository gitRepository = createContext().open(); + MergeCommandResult mergeCommandResult = command.merge(request); + + assertThat(mergeCommandResult.isSuccess()).isTrue(); + + Iterable commits = new Git(gitRepository).log().add(gitRepository.resolve("master")).setMaxCount(1).call(); + RevCommit mergeCommit = commits.iterator().next(); + PersonIdent mergeAuthor = mergeCommit.getAuthorIdent(); + String message = mergeCommit.getFullMessage(); + assertThat(mergeAuthor.getName()).isEqualTo("Dirk Gently"); + assertThat(message).isEqualTo("squash three commits"); + + GitModificationsCommand modificationsCommand = new GitModificationsCommand(createContext(), repository); + List changes = modificationsCommand.getModifications("master").getAdded(); + assertThat(changes.size()).isEqualTo(3); + } + @Test(expected = NotFoundException.class) public void shouldHandleNotExistingSourceBranchInMerge() { GitMergeCommand command = createCommand(); diff --git a/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-test.zip b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-test.zip index 4310e637335c09568da9d3afa63ce57949cec5ab..8e43da1d828d94bc3f0bbe423d6488c88c52e5bf 100644 GIT binary patch delta 18069 zcmb_j34BfW@;^7hy=_R4h&|Sdh~@5!S`tY_Hc_$0mb*r535h1{K9<3_nw!K=5VNh0H>tO|a3eeW|532;NLEYwx z!Ae!e!{x(Xgsryp7c4EC%0B>d8hX)pcEXvDk9vbY9Tt;f; z$n^`ntpp{Vf9U$&MJ2=FSLKylfC#h#A5%ixR`PL1Xt5tCY3#Rm-+O)xgI|?bvXl>0 z&!&|FJn6Sgl{srp;bQ*E}v_fdvJ zz@E-*)D$gF$j;<*{=eTza$fJYXdaoF0z%xQ4q%uU1etzu(Xs9%-G6n2U~Wg3mty_E z-s`*W9MwVWQNm*{>_tI#iql1m@)^!=G_-VQyd%|Z(*1XRi0{2t!GW;RTEZqn0r*LF z=E`&tWwe4}nu5T~ty^Gf%-M5wgs_KLTkC_csDTS+%=-v5<2S`c9h4)Mgk1Opw@|M( zTxMikK9}mzk=f=%^Y7XFK?i*=-u&Z+lC5_QkC;yVX*>~qPVDdc2Bb>B1^$6ZcT3K*fEKXFfJXoE#%^Y73)~L zY2c-a+kxQR^n`1MaWFrAdjkqU*FfEcIir%h3Z zm;7kB&1MAz_RQ}%HD5V--ENodQQOv|%Z|R?;fw!%X`j-qUW{RnV}7reTa%8jdnM`C zn$b7LeW2U>_^(|hA8&}SP;4r1++*3YnWvskEIjhY8;J)NZ5;mAS26Qe-|hO#_}#sq zgtp)HkGCI99lEezN}c8-Uz=#|v^LuMcJrMf6AC#(5#vmfwYsK=3_PMyk<}tHFefV5t&ye#rRqmx zbYXXpL}?KbZB*a`rXI7KA_P1?_NJx{tX0F$N=hU<&ns7Td}QQvH8xv3mNWUa`hFz0Rw&>LPA2ny1*?hE zuob#S>{D$!!0}i=FJ=O0KrQZ^Ih{#!#-(?M1)HhLVX04W2z%yD9ft!2eBt2sOqmY% zOw;#^no0MJbk7d?Dg$a*Ppc@?gKlcO;pC0GQ4s()c?) zy!19VqW0C+HDMP$9rFw)Ya7@_p=o~HE($ufTDpr6OQp`AmS^c303R?oTiK%U22i2b zWzcN1@I3O2bScS-HoW~Gtz0C33UoKV2--)Fa-jY9*N$&yGxx;w?Ei~$`s({VuKl%QVVg&_ z{~FGPwlL=JvF$-CYIbKLzFpHk`4hKIPa_6sEe4mG&9t@0E(AgQioqvLLIqpU{ojyZ zs}9Auc7rC=CT4t9`Q_mEV9oQZm7S~7HS63WAio0B+HyfKuN|H&tcKA|{O1te#{nzmcTD|`ldHa8Jdi?j592c1m73`P2 zE`j|=A4DiGgCt7J`LC_9|2p1&Z&$Vc>zFSp*z_m^BpM!jgDyRT_56v0M{4CWke*Ouj6NgGR21$%aV3s}^f{p?{x&pp$8yhn# ze!4~=y?3v@(a6u z`*uy{PXC^zsf9rg&fM>QZRW3v;6tVAvb$Wiz8l_YUj(Q|_2Q~l)!Df04cwTqwW*wF zR1{k##C)bN7ey5Cm1w6Kbp;z1|0P6m$J%8Nz6ME@79n|Gj?pMO_=W&?5e5Z*;_l9O z-{nzBKJ`6^BS<=2PP@(JFxd1~t;=L`IrVC%#^!KpEl#6e?J~J6dcDnk#fbXj-1@!G zZw2m@X^IVHr^dy!sULOaUo#h7%vb)W_*$zKZ>Ov+(cFIjwRNV@t#>~-^GChyPebRY zJ)V3g_^ln;xeKEDU9A6FUaV&BorrnYJI9wAF05ST`eD0i{l4kPX7-6}wBgp+o%PpW z=`-Y;i(jjPhbO(%{!06}dn-q@%GtI!es1c;WqVH4xqCn0=kiuvf7)8|ZT2rCAKz1a zTRYhQu{A2E&ywObkDnCYpOLV0=9A<<1GXqXyF0KGmZ_{8Z#XJ%XBsOAza&>p`~hEm zl-GeIth*=5AZgR;twyy$uQyvvHlyBbvKVw$vs&k{IUF{Z#%grgjN0m>yp9?6+cz(QChz zU;KeNfCy`rTNb`%RPS{oFRww;U~y{oHnYy6)0iw)gTtiJI-Gi!#iG`j%np~qq|-Yr z7Vf@~==aVo&%5fyYo$xyy&Th_jwLiXv>vyA7jDZtjO7lEiw^ls5brAC1IaRK`J)gM z88-|3B%6gw9t26cj38;%TTKR?R%cUd&3cE~Wz;$>X1mn^J{mMGm(AqTsVy$fA)?>g zA;m$amuDHZYKO3l`PD4r0K?~-k3X6ok->)b`_+Ez(S);)ZqAza&fG_B1NQGOS?0$q z+cXHKT}su3MT;*T0rcoW&7$JOTNk_zCa`^kCDbb{AgZz0oNBev?$YTX!JS$Mtgp$c zGwV!NtIvh@$-B4XvJnKB_(CLTYO}{%oxo%=g`0#Pu4ea&<_jWe{%V@o3F1h%I zpn8usczFbpMzz&!(wUu56fHWP&SW;)4Q34#L-5FM(P+&Yy+LR6c!W0aKd-kM#Revw ztBTPt{zqLu>Xi$Y&+p{VeLJ}!J8MDngX7!(qiN(feaB77Up&MA)9ibXHkAFgS25$$ z@sYZW#ICiozjQ76@|6|-XPYFgZ?VnP*0SK-KZ`Tdr)NdYo0AlJ|Kdw4>dz^3u?r_( zpZ!%sM_$(*iF;F~&JB1_+HmBfUO$iSV7{-McT4&6{o=yg+k!Tfg{ymI)a|}@ZpL0? z`qq?Be>hU{-q(whMydA2J*d;WRoX+$n{{K`g>QjiU_itmdtYD@vMuQpB#cXpqT^6&!u7!=IHE}^k{lb6VLEgi}K#-@F z^&ID~furZ^?w|jjuV|O`dq9VOFMMJ1l-kqvs~UayVchZqyJo)ocVqd`9$`DvPclnx zWaYU&Dq3yol~khne#$2+);ohI4lCLj(6D9yrH9va|3TULrsKsh|5Zip+CKjASe>8V zyUA`W&Kq!KeQ?&&om2lDc5=w8)8>z_nD^Vzt>69n`jz-Ce;o-Q_n`KQ2i>R5&98H1 zc&CWZ*B!m~V_?bjjccwfN!pu|e&vh(Iq4NY_8GBi*UYl~MB9fOGVl2xzm_l+W50jv zyQ^@&Vw`FgdjnsZN$iCLWH*P`m05$UUTXjvAntmTLv7OPtZ)<=>{g4xska!+F15kn z(%J3s|J#lBYS-W@(^9NxpJe<$RkS{X-*GrXH{2Y2q!FCB2U}t*)D1Z!y|5 zcB@NkHyO=Zn^~{dxGXla(P%Znp$ApnV$z$di<_t94Zn8%g_b2y@uu|X+xaYbpj36$ zzTBen(dK>0kJDUnuZ;K9~k%~Y3%x6hICGP z<$T=ix898VYhb`U_Hv)er+Ye4LyqSron z9K6DKUQ&ggFo!uS8YH(U9<=$JS*QCgW(pT?I+q$Z^3vU5OF6e*D;`oPbqnk=x4d4= zk%hpym1h;G53j;ZLhwnX`%pnt+YplR?@8RWoeGpBVGtCp11D7u0^K$BKt&+fj!8{P zNfUTDBbH^+0OXY~$dp`^UWvI}-b-H>J zi?ZP(q%_&HC%LjRIU8i!w|wq6c)N67Z1=xm@ICylxC3cbNq|rAh<7-1?NhRH^0Klg zc~oJ;j9i1ZidXt7Ri?$tR=wfdC?V=vNJez;`!NhgJ~JzCd{?{GKGyw;qb0P34T>ue z$fF(4EX5aI6cANW8VZ)eTA)h8K=H9HA`??~4D6Vg5_$e~zm(*N^QTifcTMPW)g8#l ztlUkKTN0)V29nx3q!5TX4(Bwt^CI14$E_A-1wg*`C+qA0U&JpH$mF&I(+8O|sRI+@ z$9Qm`+AUI3yjq#s2WwhL(kfLFgt;VCbO2X>YlM3wY1)*BIJumOifPzZ>^USQYu zZzY3B;t}PkA(=*a$_tji$SP7>R3}d_+YZZD)yyOpu9BX zvI$R7!7)LH4`v%Cgg^X2B3roB{ORV54_X_@#0chbms6S=ZcpJUDrne(*WuZ$OT>Lo zZFk0^COqZQtHXFXu@e&}yI3s&0JjJz;C;gcBsW11QyzUijFF?ffr4jYd`UH$+vNQ_6mv)~FFxa=tCEM2_kv1QeaY$pU4pNu62Wj*%m}(STyhLit@@ z&aj$6!J%G#3l==+T@ZdBgpUvHeDHT9g9%T0^sJZQ$n`}H12u7i8cOFtaXg8Ir#$)? zQ6onc15KWKhf^KO@xrSt;b~vcpaWYl*no>nMm*?{2MHb7pqv!RKjFq)a7cz1hMGJ` zP!pwfNs-*?z$OggkdR5xGazXU4VZvLjtb_DgwnaBNHRLI4TgG<;3C7o;~9{+;J_D< z$Wg!miKiH2Oel>@iliihVSy5c%Y#87P~wF|p)DIO57G&Qr#yNryz2;13@1w9lG-il!p=$O>=G`RB!8;j-4DBp zNaRRg6bWjgY%M90sBUcX2o4G1fccET*>0j;IffV5_2ex^fYP$0cAKji78qZ+i(zPhzN{S@Kz_38x!VnPhmIR6%Z$_+DX+{x>97~G^1uG7vS4p9)HnLB~ za6$1p)M{8YyP8BKvXm@OM!{g6WYp8>=|obFpGCuo9USFYNqzW_i9x;=h7F$)ZPT1TX2Q393J@KE-$)re*ZO$+^%6kW=Tl8QgJutRKI?6K$)*q(-> z>MYQ|EIJ~`rJ{~t7EltD6v<4srJZxcD+#zA2}~ifaOKfKJw&r|yeYbHcuz^WPg1kP zdN9bN!Uzg|b5#o$(Ni=mN0_4i;_V|EN(Fg=gDW0eKk(B zE5~J`cCpA(K9bb#{y6rW+ph4A?A_^8?vZx{oIhv0*a!R9-BL7XQ>G^0s&w#v1vWQ%c zs^g8E@@1sRtx`?TN8OPir(7EEEmR);kl2&s(`$fJOVEacsDb99~o`P}}Z zJ&rd6JBE@>ypdDtiWGU7RMYd(Xr2MNGgU+`N00GFPWd2G(3Oz~bq7vDKy&1?eSpogr+SiQH(0@F6fBNdf{q z+9|W30YOkyuwsbB7xHh>39AA1d9Hczjf3(9==iRvpv6#z9i148tpXt@paM@wu=Ax9 z0jWb9heAlK?VF$mY<`6?Jaq||htBq;E@@$iEz%Tx5DO4dEQSalI@rhGr6Zir1by=S z02&)qLnB`CvtU^>)C z0)X%o6+8fC@^k=H!fgd%+DoVTwD=YktV?IAWCMVv+8iAAN^+x4qWRJS%Lw>KlfGyF zAlOUr11EEIG>@}>;VCW17|F;^{?QArD4-Xpf==b>fUvaSB&f)mBs0)Q9a$b2ur_of g(E(v;LF{Oz=F|KENww>s2f6TPGFf*%&^B;+PG@6z*jS4We+ubOf@+*tto3-HS_=boPEyT_Z;ppw6@EZ`}@ED zwg0`(rvrPf`z~2F&8`g!4kh@r_x9$xj4zHq84kb46+se6RefDu&Ag@4s%z%e-7=$L z-bfO(<)y0&M?bY~@#>y1VhR2%h!FNmo6p_iU6<69-QCk2ibkWr#0YXr`#^=~V#?3+ z;s1QFhJ#U;uie|y%#7VP{JjhHtv4K?8Mm*<7coWmhSkKuFjT@?iOFW z0tDRJL)+-Px6jGrxNT*{^{3L$?>GmTJyt?O0oUBpy)xU?=vwY!u)IUt79(uc!Vfp` zxJwo_*JZVI9T*2NT)_ZyPOGd|cwWA`5d8Cxw$tM+O@d##a+Z4<-Ca3tja}WIP8PS4 zOpF_BX+IeEa-VNyR}ObSxc=_&12nlICR)kTHCxBUhasB>hoc(_yBg!dAlvsroS#C* zk8D32G?@pA#-r!HbZAX5A@LD}jM0p*?r=ADd$PM4+d5j6h`E8jZNa|^ju$N~yS{6F zsD+SMVa1aKI%#C0$k?}hWV}TJ?7iXoAGbq9$6>_);Hl7f5kTJw-6!pza5;PT#SlVn z8^a=^4~I!1^_>d)B18g=vmQUO8w&R}#AO45^2VD)g1+#mgH{QU@XLiIKY=$p)DW(W zl|u+07h$OoWB&a6{>baF`7TH_Bv~zq%h%S{(mk=QtJySjzL*i#iQEcy%SYc+Pbye> z1=cXI($ens^l0t!%#+*teqK~;0f>1oC=jntYX#m3IZ7w2EjyEZ2%`EL)(y-&?Dh1x zT35UEnjU}g>OvY6e+E~Gg-}#3xPvwIv_Ao+5LP=Pvr6GY)_svYGw%U2w{qs2jaK5G$ z9#^+oC7xBNQm#^h7yPCMPCgDP%vJiO7HYc&Tm+O_xaAPf;=)mRH$p79av`4VRL3$1 ze?Jrsm*24NvDEgzQ9sw-j15aL484e0?%PZq?`FVkCxL~OSQBEC{s z`A`9n;zHun83Bn?GK&g$wElNPxKQPeM%PM(76&)pjLI3l(Kjwu=!GI>&+HG(df|7+ zXjd(`g$rGa&0?-$AD9)QBdd-Lks~WbihMPCJV0{0;b~ice#@G(U>+Az>$DbJ;&GM4h_#oc&e6;bJMD$Vk=A2GW%5*EG^V2dhZ! zYU9B|RNvOQ1LDED(#8%JZeDNfYW-k+3l zxInSTXHJi!qw;d;H}+&!Te*Gm)rgJlQ|%k5__t$gRWqa*7p8fqkI~`gZ{X7LukEGW z7Dn-CVsyM%ORqzT2`?JCfg*t!ftyW8dMsHPGwT}c`7T;EJq;p@ z4kera8Fz(cm&(Bvlniz@FgVxA>C^flFkBd1#VReh9%DIarsK5MLwGF#O8XImEy`C& zbKriGgIy29xxqT_OR<0}3K(fW5aROB=*faDETgMB#RKVRQYglop7~L)|5|F(uR9%0 zLz({{LYuCzF4NmI&~R#L7auKZobbTdSR^`HqFz+^7Lap+4~N6Ej%<1za)=8HaJh)( zLfvf0MPYHj$#BZ^D!DKYr)Qx^4^{1~P6qS1kmAQ~$9%}eClRbR{uKPkI+~py_rGzv zw!t}&GuPQ)bGq&mD{*}maz@V$PQHDwRC%8R!?@7+9jlrDswe)&X?y7lCbe{{J!)Rd z;zt(D5=YM7NkhBK!QY5hYoqQjKD@w9?MLibk9vjR3rwyOq8>Wg+Fu7aTo`5MS{5aL zuK9QD>B;pbRVr2|fU!!L$jASZ8hIy}$AuL4muZPKyFxxK+o+wyKyl@(tzjP555LI> zxd?=~ko4UiEiOocp?W#x@0rAv7cj0ZpFOZ4AI#%Iij5TkO^o!pE0WKu@a6Kcz&1Qr6a@9dCP0<0`I@hgChR+RC5>fr_n|%B;A4z|# zb`(OIa5KK&{n}cc3{Nj}&U2owor7=X@ec$Svt;?rJcmo6HJ^EBU;Scfr_%ENMUDF)k@ki0ci*7EuqTWu8a{94=PL8IDa5FK!+@UV9#V z7R)?*F01BD)*NS6|G5Td=FHQzXRlbvgSU>nuJ=ly3(>14M7aN z|F@{u;6e{APL>)2Oc9yreCL~0{25&W0si*m-E|oN#f9Hx^>b@xEY-ft&cJg8)E=E& zvEp^naA&GLfLeoJ==!I!@Df+Q)65R4pa!uIY&JX zR1L|~+3aa-Qgs__3=`q0B$wuNr`iPHaTxBl-8I0i!q+mPtw}KnP-q;2CPwnT^wXZy zM8OD_Ld@ljP>LhBMxhNXg^3L%iJ~Jo)UQ9a?q==?s#5f%CmN056<}f{&r7$iPOW5) zD0(4gc2VK$h3RThF&ZJWp0iVN^wR6GwYjK{TR93Kl>k zrAORRMLZ8ieN;5C#Y$AQ6v;s?RijsO=>M7=4%&?J6G^UBUIZ)`MFrP0xa)|PWl^2^ zT5oI+0g*IB08STyvOp>VM}%-%Ke<#eCHWvLrJ|{UFoL?N=!ArnB~uZZ8BS0@9TX{x zry_EA1VJfPM5c>@%POjfw2!4z8dF4|m_Ctk~*!=IDNGZ0z+6aoZ z!vP5w51wDGv_`pBkLze}ViG~|7E6m)4!Wpy6J9N{aH}Kt_a-OPYi=6?@tQ%PMv2#n ztl)~kf}05HxrjtPSE9sAM3!{jz$;ZX3R4O{A=pAk1US=w!2&8z zYO)m*M){YUu#oY+-=z^0gE4jx05NA}oZG*n!X(!_;R@!iGvQbFUg}9%_7$n0PbVk> z3uzxB^&9Yn;M7e19fVpIe?{tuOoCGI|4;p6rcTWS;6bQm`B$W_$Ra2P3)B@n;r_Q_ zxRmfKSvs{U0|PmK60o@eO4oSKh5eB>L5CFL2R#5#r!(}2ED3WH*dMJE2nxhPe=sHx zgQW>|(?p$#NyEUToPz)yc354OJA7WW27ReXb>E*TpMRh3zz}EF)*_wS__Si)f;cRA`%~o^0m7tW@Rq+k zwDKF3V$s{t#RPR@EI&swD*vH1twi^ZNyY$BXHObGyGFrSWZF)D=q^Ma6py8R;;@so zU=cUAl&)LN@~QHf7OrTL?jMtcfruAQa}WzBE5ahtopkn^LZNP2a~d^L4#VdBUSMWQ zFfj91oW_hMWFeTFz~(HY>1$bPIL@>L-%i#;WO}ek2{4ti8Z7dZKG4Gg^uII$2berX z=Vg-cU;)aCut@Bs@AUld03R>ciI^~~2dJGr3{aMZMV_z9>5+9}uMF{(bYF$e%+z6E z*5Cg6+fdeqMdm+O5DH4M&_3D$#J8cW1B<|$D+#K?2viwa@j(IhoAvIgx{pi`1_Ynj zT6%ES%W|;D_1IK;Wj!0f9H{lUQ3&Qh?D1n&IyaMrfg6=xX#g8lVQvC@{6AF$HDVk9 z9LWOppJ~0-Iu(Aaw&orHzslmXF z0bCax&Z~ic%LJ+~wkjXU+F`~5$m*^LJySza>qR6l zfJTawOV)HnWaUgC1%(&qfI6O?Tus3NA`7`9@_9fGChLMRaj1ZtD}9ONz&{%h#a-Br v8ZMT77$c{>+oMN`Wblt+Q0<+d?)3ZXmf5w#hC@4%rSM;33L$?3`-J=#eWdxs From 9df12bc35ed06f0d02af5b9314264d2947383007 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Mon, 4 Nov 2019 17:34:44 +0100 Subject: [PATCH 32/60] check if mergeStrategy is supported for mergeCommand --- .../repository/api/MergeCommandBuilder.java | 13 ++++++++++-- ...mMergeStrategy.java => MergeStrategy.java} | 2 +- .../scm/repository/spi/MergeCommand.java | 3 +++ .../repository/spi/MergeCommandRequest.java | 18 ++++++++--------- .../scm/repository/spi/GitMergeCommand.java | 20 +++++++++++++------ .../repository/spi/GitMergeCommandTest.java | 6 +++--- 6 files changed, 41 insertions(+), 21 deletions(-) rename scm-core/src/main/java/sonia/scm/repository/api/{ScmMergeStrategy.java => MergeStrategy.java} (59%) diff --git a/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java index df9f6bdfbe..6910dc02fa 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java @@ -55,6 +55,15 @@ public class MergeCommandBuilder { this.mergeCommand = mergeCommand; } + /** + * Use this to check if merge-strategy is supported by mergeCommand + * + * @return boolean. + */ + public boolean isSupported(MergeStrategy strategy) { + return mergeCommand.isSupported(strategy); + } + /** * Use this to set the branch that should be merged into the target branch. * @@ -99,8 +108,8 @@ public class MergeCommandBuilder { * * @return This builder instance. */ - public MergeCommandBuilder setMergeStrategy(ScmMergeStrategy strategy) { - request.setScmMergeStrategy(strategy); + public MergeCommandBuilder setMergeStrategy(MergeStrategy strategy) { + request.setMergeStrategy(strategy); return this; } diff --git a/scm-core/src/main/java/sonia/scm/repository/api/ScmMergeStrategy.java b/scm-core/src/main/java/sonia/scm/repository/api/MergeStrategy.java similarity index 59% rename from scm-core/src/main/java/sonia/scm/repository/api/ScmMergeStrategy.java rename to scm-core/src/main/java/sonia/scm/repository/api/MergeStrategy.java index 7ff1b6bc51..9968dd61e2 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/ScmMergeStrategy.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/MergeStrategy.java @@ -1,5 +1,5 @@ package sonia.scm.repository.api; -public enum ScmMergeStrategy { +public enum MergeStrategy { SQUASH } diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommand.java index 0a3680f6b3..cc20db4c5d 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommand.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommand.java @@ -2,9 +2,12 @@ package sonia.scm.repository.spi; import sonia.scm.repository.api.MergeCommandResult; import sonia.scm.repository.api.MergeDryRunCommandResult; +import sonia.scm.repository.api.MergeStrategy; public interface MergeCommand { MergeCommandResult merge(MergeCommandRequest request); MergeDryRunCommandResult dryRun(MergeCommandRequest request); + + boolean isSupported(MergeStrategy strategy); } diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommandRequest.java b/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommandRequest.java index f7094a5e7f..0751920d01 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommandRequest.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommandRequest.java @@ -5,7 +5,7 @@ import com.google.common.base.Objects; import com.google.common.base.Strings; import sonia.scm.Validateable; import sonia.scm.repository.Person; -import sonia.scm.repository.api.ScmMergeStrategy; +import sonia.scm.repository.api.MergeStrategy; import sonia.scm.repository.util.AuthorUtil.CommandWithAuthor; import java.io.Serializable; @@ -18,7 +18,7 @@ public class MergeCommandRequest implements Validateable, Resetable, Serializabl private String targetBranch; private Person author; private String messageTemplate; - private ScmMergeStrategy scmMergeStrategy; + private MergeStrategy mergeStrategy; public String getBranchToMerge() { return branchToMerge; @@ -52,12 +52,12 @@ public class MergeCommandRequest implements Validateable, Resetable, Serializabl this.messageTemplate = messageTemplate; } - public ScmMergeStrategy getScmMergeStrategy() { - return scmMergeStrategy; + public MergeStrategy getMergeStrategy() { + return mergeStrategy; } - public void setScmMergeStrategy(ScmMergeStrategy scmMergeStrategy) { - this.scmMergeStrategy = scmMergeStrategy; + public void setMergeStrategy(MergeStrategy mergeStrategy) { + this.mergeStrategy = mergeStrategy; } public boolean isValid() { @@ -85,12 +85,12 @@ public class MergeCommandRequest implements Validateable, Resetable, Serializabl return Objects.equal(branchToMerge, other.branchToMerge) && Objects.equal(targetBranch, other.targetBranch) && Objects.equal(author, other.author) - && Objects.equal(scmMergeStrategy, other.scmMergeStrategy); + && Objects.equal(mergeStrategy, other.mergeStrategy); } @Override public int hashCode() { - return Objects.hashCode(branchToMerge, targetBranch, author, scmMergeStrategy); + return Objects.hashCode(branchToMerge, targetBranch, author, mergeStrategy); } @Override @@ -99,7 +99,7 @@ public class MergeCommandRequest implements Validateable, Resetable, Serializabl .add("branchToMerge", branchToMerge) .add("targetBranch", targetBranch) .add("author", author) - .add("mergeStrategy", scmMergeStrategy) + .add("mergeStrategy", mergeStrategy) .toString(); } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java index bafe6b8376..35e2f1e144 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java @@ -1,13 +1,13 @@ package sonia.scm.repository.spi; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableSet; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.MergeCommand.FastForwardMode; import org.eclipse.jgit.api.MergeResult; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.merge.ResolveMerger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,10 +16,11 @@ import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Person; import sonia.scm.repository.api.MergeCommandResult; import sonia.scm.repository.api.MergeDryRunCommandResult; -import sonia.scm.repository.api.ScmMergeStrategy; +import sonia.scm.repository.api.MergeStrategy; import java.io.IOException; import java.text.MessageFormat; +import java.util.Set; public class GitMergeCommand extends AbstractGitCommand implements MergeCommand { @@ -32,6 +33,8 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand private final GitWorkdirFactory workdirFactory; + private static final Set STRATEGIES = ImmutableSet.of(MergeStrategy.SQUASH); + GitMergeCommand(GitContext context, sonia.scm.repository.Repository repository, GitWorkdirFactory workdirFactory) { super(context, repository); this.workdirFactory = workdirFactory; @@ -46,7 +49,7 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand public MergeDryRunCommandResult dryRun(MergeCommandRequest request) { try { Repository repository = context.open(); - ResolveMerger merger = (ResolveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true); + ResolveMerger merger = (ResolveMerger) org.eclipse.jgit.merge.MergeStrategy.RECURSIVE.newMerger(repository, true); return new MergeDryRunCommandResult( merger.merge( resolveRevisionOrThrowNotFound(repository, request.getBranchToMerge()), @@ -56,13 +59,18 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand } } + @Override + public boolean isSupported(MergeStrategy strategy) { + return STRATEGIES.contains(strategy); + } + private class MergeWorker extends GitCloneWorker { private final String target; private final String toMerge; private final Person author; private final String messageTemplate; - private final ScmMergeStrategy scmMergeStrategy; + private final MergeStrategy mergeStrategy; private MergeWorker(Git clone, MergeCommandRequest request) { super(clone); @@ -70,7 +78,7 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand this.toMerge = request.getBranchToMerge(); this.author = request.getAuthor(); this.messageTemplate = request.getMessageTemplate(); - this.scmMergeStrategy = request.getScmMergeStrategy(); + this.mergeStrategy = request.getMergeStrategy(); } @Override @@ -94,7 +102,7 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand .setCommit(false) // we want to set the author manually .include(toMerge, sourceRevision); - if (scmMergeStrategy == ScmMergeStrategy.SQUASH) { + if (mergeStrategy == MergeStrategy.SQUASH) { mergeCommand.setSquash(true); } else { mergeCommand.setFastForward(FastForwardMode.NO_FF); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java index 873dd23498..f27b1e3760 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java @@ -15,7 +15,7 @@ import org.junit.Test; import sonia.scm.NotFoundException; import sonia.scm.repository.Person; import sonia.scm.repository.api.MergeCommandResult; -import sonia.scm.repository.api.ScmMergeStrategy; +import sonia.scm.repository.api.MergeStrategy; import sonia.scm.repository.util.WorkdirProvider; import sonia.scm.user.User; @@ -221,7 +221,7 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase { request.setBranchToMerge("squash"); request.setTargetBranch("master"); request.setMessageTemplate("this is a squash"); - request.setScmMergeStrategy(ScmMergeStrategy.SQUASH); + request.setMergeStrategy(MergeStrategy.SQUASH); MergeCommandResult mergeCommandResult = command.merge(request); @@ -244,7 +244,7 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase { request.setBranchToMerge("squash"); request.setTargetBranch("master"); request.setMessageTemplate("squash three commits"); - request.setScmMergeStrategy(ScmMergeStrategy.SQUASH); + request.setMergeStrategy(MergeStrategy.SQUASH); Repository gitRepository = createContext().open(); MergeCommandResult mergeCommandResult = command.merge(request); From db46441adf9707715c08f96c32b1856823c8efbf Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 5 Nov 2019 09:51:58 +0100 Subject: [PATCH 33/60] move mergeResultDto + mapper to scm-core --- scm-core/pom.xml | 6 ++++++ .../java/sonia/scm/api/v2/resources/MergeResultDto.java | 0 .../sonia/scm/api/v2/resources/MergeResultToDtoMapper.java | 0 3 files changed, 6 insertions(+) rename {scm-webapp => scm-core}/src/main/java/sonia/scm/api/v2/resources/MergeResultDto.java (100%) rename {scm-webapp => scm-core}/src/main/java/sonia/scm/api/v2/resources/MergeResultToDtoMapper.java (100%) diff --git a/scm-core/pom.xml b/scm-core/pom.xml index f19d50064d..c7bbc664c9 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -131,6 +131,12 @@ mapstruct-jdk8 + + org.mapstruct + mapstruct-processor + provided + + com.webcohesion.enunciate diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MergeResultDto.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/MergeResultDto.java similarity index 100% rename from scm-webapp/src/main/java/sonia/scm/api/v2/resources/MergeResultDto.java rename to scm-core/src/main/java/sonia/scm/api/v2/resources/MergeResultDto.java diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MergeResultToDtoMapper.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/MergeResultToDtoMapper.java similarity index 100% rename from scm-webapp/src/main/java/sonia/scm/api/v2/resources/MergeResultToDtoMapper.java rename to scm-core/src/main/java/sonia/scm/api/v2/resources/MergeResultToDtoMapper.java From c18c64c15a86e161de5d0ef7ad06c1b0386d0443 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 5 Nov 2019 13:30:25 +0000 Subject: [PATCH 34/60] Close branch feature/modify_for_svn From bc5948f8230f8365dd7ff07a00cea6fd2751ecb9 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Wed, 6 Nov 2019 07:47:23 +0100 Subject: [PATCH 35/60] add new MergeStrategies --- .../sonia/scm/repository/api/MergeCommandBuilder.java | 6 ++++++ .../java/sonia/scm/repository/api/MergeStrategy.java | 4 +++- .../java/sonia/scm/repository/spi/MergeCommand.java | 4 ++++ .../sonia/scm/repository/spi/GitMergeCommand.java | 11 ++++++++++- 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java index 6910dc02fa..267dfa9862 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java @@ -6,6 +6,8 @@ import sonia.scm.repository.spi.MergeCommand; import sonia.scm.repository.spi.MergeCommandRequest; import sonia.scm.repository.util.AuthorUtil; +import java.util.Set; + /** * Use this {@link MergeCommandBuilder} to merge two branches of a repository ({@link #executeMerge()}) or to check if * the branches could be merged without conflicts ({@link #dryRun()}). To do so, you have to specify the name of @@ -64,6 +66,10 @@ public class MergeCommandBuilder { return mergeCommand.isSupported(strategy); } + public Set getSupportedMergeStrategies() { + return mergeCommand.getSupportedMergeStrategies(); + } + /** * Use this to set the branch that should be merged into the target branch. * diff --git a/scm-core/src/main/java/sonia/scm/repository/api/MergeStrategy.java b/scm-core/src/main/java/sonia/scm/repository/api/MergeStrategy.java index 9968dd61e2..8fa6b1c49c 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/MergeStrategy.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/MergeStrategy.java @@ -1,5 +1,7 @@ package sonia.scm.repository.api; public enum MergeStrategy { - SQUASH + SQUASH, + MERGE_COMMIT, + FAST_FORWARD_IF_POSSIBLE } diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommand.java index cc20db4c5d..a62e373dca 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommand.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommand.java @@ -4,10 +4,14 @@ import sonia.scm.repository.api.MergeCommandResult; import sonia.scm.repository.api.MergeDryRunCommandResult; import sonia.scm.repository.api.MergeStrategy; +import java.util.Set; + public interface MergeCommand { MergeCommandResult merge(MergeCommandRequest request); MergeDryRunCommandResult dryRun(MergeCommandRequest request); boolean isSupported(MergeStrategy strategy); + + Set getSupportedMergeStrategies(); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java index 35e2f1e144..378ec3afbd 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java @@ -33,7 +33,11 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand private final GitWorkdirFactory workdirFactory; - private static final Set STRATEGIES = ImmutableSet.of(MergeStrategy.SQUASH); + private static final Set STRATEGIES = ImmutableSet.of( + MergeStrategy.SQUASH, + MergeStrategy.MERGE_COMMIT, + MergeStrategy.FAST_FORWARD_IF_POSSIBLE + ); GitMergeCommand(GitContext context, sonia.scm.repository.Repository repository, GitWorkdirFactory workdirFactory) { super(context, repository); @@ -64,6 +68,11 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand return STRATEGIES.contains(strategy); } + @Override + public Set getSupportedMergeStrategies() { + return STRATEGIES; + } + private class MergeWorker extends GitCloneWorker { private final String target; From b47cc56a86f0664e18ad75cef81ef44c592704a6 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Wed, 6 Nov 2019 08:32:00 +0100 Subject: [PATCH 36/60] Remove redundant logging --- .../java/sonia/scm/security/XsrfAccessTokenEnricher.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/security/XsrfAccessTokenEnricher.java b/scm-webapp/src/main/java/sonia/scm/security/XsrfAccessTokenEnricher.java index ed7093c09c..47166709c2 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/XsrfAccessTokenEnricher.java +++ b/scm-webapp/src/main/java/sonia/scm/security/XsrfAccessTokenEnricher.java @@ -82,8 +82,6 @@ public class XsrfAccessTokenEnricher implements AccessTokenEnricher { if (configuration.isEnabledXsrfProtection()) { if (isEnrichable()) { builder.custom(Xsrf.TOKEN_KEY, createToken()); - } else { - LOG.trace("skip xsrf enrichment, because jwt session is started from a non wui client"); } } else { LOG.trace("xsrf is disabled, skip xsrf enrichment"); @@ -98,15 +96,16 @@ public class XsrfAccessTokenEnricher implements AccessTokenEnricher { return true; } else { LOG.trace("skip xsrf enrichment, because jwt session is started from a non wui client"); + return false; } } catch (ProvisionException ex) { if (ex.getCause() instanceof OutOfScopeException) { LOG.trace("skip xsrf enrichment, because no request scope is available"); + return false; } else { throw ex; } } - return false; } @VisibleForTesting From 2826273719b8b31c65ff3ea69e903bbcbc277829 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 6 Nov 2019 08:06:26 +0000 Subject: [PATCH 37/60] Close branch feature/remove_redundant_logging From b4f1e8874af7399599041be50de41ff2d442c7c7 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Wed, 6 Nov 2019 09:54:17 +0100 Subject: [PATCH 38/60] change mergeStrategy order --- .../src/main/java/sonia/scm/repository/api/MergeStrategy.java | 4 ++-- .../main/java/sonia/scm/repository/spi/GitMergeCommand.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/api/MergeStrategy.java b/scm-core/src/main/java/sonia/scm/repository/api/MergeStrategy.java index 8fa6b1c49c..4ccf1f706b 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/MergeStrategy.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/MergeStrategy.java @@ -1,7 +1,7 @@ package sonia.scm.repository.api; public enum MergeStrategy { - SQUASH, MERGE_COMMIT, - FAST_FORWARD_IF_POSSIBLE + FAST_FORWARD_IF_POSSIBLE, + SQUASH } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java index 378ec3afbd..ff882b4f20 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java @@ -34,9 +34,9 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand private final GitWorkdirFactory workdirFactory; private static final Set STRATEGIES = ImmutableSet.of( - MergeStrategy.SQUASH, MergeStrategy.MERGE_COMMIT, - MergeStrategy.FAST_FORWARD_IF_POSSIBLE + MergeStrategy.FAST_FORWARD_IF_POSSIBLE, + MergeStrategy.SQUASH ); GitMergeCommand(GitContext context, sonia.scm.repository.Repository repository, GitWorkdirFactory workdirFactory) { From cf90654e24d734fe784f806009ccde9a5da32dbb Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Wed, 6 Nov 2019 15:26:20 +0100 Subject: [PATCH 39/60] implement fast forward if possible --- .../repository/api/MergeCommandResult.java | 9 +++++++ .../scm/repository/spi/GitMergeCommand.java | 15 ++++++++--- .../repository/spi/GitMergeCommandTest.java | 25 +++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandResult.java b/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandResult.java index 53f712cddc..5088fcad1c 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandResult.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandResult.java @@ -13,6 +13,7 @@ import static java.util.Collections.unmodifiableCollection; */ public class MergeCommandResult { private final Collection filesWithConflict; + private boolean aborted = false; private MergeCommandResult(Collection filesWithConflict) { this.filesWithConflict = filesWithConflict; @@ -41,4 +42,12 @@ public class MergeCommandResult { public Collection getFilesWithConflict() { return unmodifiableCollection(filesWithConflict); } + + public boolean isAborted() { + return aborted; + } + + public void setAborted(boolean aborted) { + this.aborted = aborted; + } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java index ff882b4f20..6d5c33f874 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java @@ -3,7 +3,6 @@ package sonia.scm.repository.spi; import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.api.MergeCommand.FastForwardMode; import org.eclipse.jgit.api.MergeResult; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.ObjectId; @@ -20,6 +19,7 @@ import sonia.scm.repository.api.MergeStrategy; import java.io.IOException; import java.text.MessageFormat; +import java.util.Collections; import java.util.Set; public class GitMergeCommand extends AbstractGitCommand implements MergeCommand { @@ -93,10 +93,17 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand @Override MergeCommandResult run() throws IOException { MergeResult result = doMergeInClone(); + if (result.getMergeStatus().isSuccessful()) { - doCommit(); + if (mergeStrategy != MergeStrategy.FAST_FORWARD_IF_POSSIBLE) { + doCommit(); + } push(); return MergeCommandResult.success(); + } else if (mergeStrategy == MergeStrategy.FAST_FORWARD_IF_POSSIBLE) { + MergeCommandResult failure = MergeCommandResult.failure(Collections.emptyList()); + failure.setAborted(true); + return failure; } else { return analyseFailure(result); } @@ -113,8 +120,10 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand if (mergeStrategy == MergeStrategy.SQUASH) { mergeCommand.setSquash(true); + } else if (mergeStrategy == MergeStrategy.FAST_FORWARD_IF_POSSIBLE) { + mergeCommand.setFastForward(org.eclipse.jgit.api.MergeCommand.FastForwardMode.FF_ONLY); } else { - mergeCommand.setFastForward(FastForwardMode.NO_FF); + mergeCommand.setFastForward(org.eclipse.jgit.api.MergeCommand.FastForwardMode.NO_FF); } result = mergeCommand.call(); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java index f27b1e3760..4f04205202 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java @@ -262,6 +262,31 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase { assertThat(changes.size()).isEqualTo(3); } + + @Test + public void shouldMergeWithFastForward() throws IOException, GitAPIException { + Repository repository = createContext().open(); + + ObjectId featureBranchHead = new Git(repository).log().add(repository.resolve("squash")).setMaxCount(1).call().iterator().next().getId(); + + GitMergeCommand command = createCommand(); + MergeCommandRequest request = new MergeCommandRequest(); + request.setBranchToMerge("squash"); + request.setTargetBranch("master"); + request.setMergeStrategy(MergeStrategy.FAST_FORWARD_IF_POSSIBLE); + request.setAuthor(new Person("Dirk Gently", "dirk@holistic.det")); + + MergeCommandResult mergeCommandResult = command.merge(request); + + assertThat(mergeCommandResult.isSuccess()).isTrue(); + + Iterable commits = new Git(repository).log().add(repository.resolve("master")).setMaxCount(1).call(); + RevCommit mergeCommit = commits.iterator().next(); + PersonIdent mergeAuthor = mergeCommit.getAuthorIdent(); + assertThat(mergeAuthor.getName()).isEqualTo("Philip J Fry"); + assertThat(mergeCommit.getId()).isEqualTo(featureBranchHead); + } + @Test(expected = NotFoundException.class) public void shouldHandleNotExistingSourceBranchInMerge() { GitMergeCommand command = createCommand(); From de09097203de3c92406bf364517283c263f02502 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Thu, 7 Nov 2019 08:14:15 +0100 Subject: [PATCH 40/60] cleanup --- .../sonia/scm/repository/api/MergeCommandBuilder.java | 7 ++++++- .../java/sonia/scm/repository/spi/GitMergeCommand.java | 10 +++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java index 267dfa9862..db6c677e1f 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java @@ -58,7 +58,7 @@ public class MergeCommandBuilder { } /** - * Use this to check if merge-strategy is supported by mergeCommand + * Use this to check if merge-strategy is supported by mergeCommand. * * @return boolean. */ @@ -66,6 +66,11 @@ public class MergeCommandBuilder { return mergeCommand.isSupported(strategy); } + /** + * Use this to get a Set of all supported merge strategies by merge command. + * + * @return boolean. + */ public Set getSupportedMergeStrategies() { return mergeCommand.getSupportedMergeStrategies(); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java index 6d5c33f874..1ef4eacce3 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java @@ -95,12 +95,12 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand MergeResult result = doMergeInClone(); if (result.getMergeStatus().isSuccessful()) { - if (mergeStrategy != MergeStrategy.FAST_FORWARD_IF_POSSIBLE) { + if (!isFastForward()) { doCommit(); } push(); return MergeCommandResult.success(); - } else if (mergeStrategy == MergeStrategy.FAST_FORWARD_IF_POSSIBLE) { + } else if (isFastForward()) { MergeCommandResult failure = MergeCommandResult.failure(Collections.emptyList()); failure.setAborted(true); return failure; @@ -120,7 +120,7 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand if (mergeStrategy == MergeStrategy.SQUASH) { mergeCommand.setSquash(true); - } else if (mergeStrategy == MergeStrategy.FAST_FORWARD_IF_POSSIBLE) { + } else if (isFastForward()) { mergeCommand.setFastForward(org.eclipse.jgit.api.MergeCommand.FastForwardMode.FF_ONLY); } else { mergeCommand.setFastForward(org.eclipse.jgit.api.MergeCommand.FastForwardMode.NO_FF); @@ -150,5 +150,9 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand logger.info("could not merged branch {} into {} due to conflict in paths {}", toMerge, target, result.getConflicts().keySet()); return MergeCommandResult.failure(result.getConflicts().keySet()); } + + private boolean isFastForward() { + return mergeStrategy == MergeStrategy.FAST_FORWARD_IF_POSSIBLE; + } } } From ec6b4493c80cec368182958e279cd3297fd2fa18 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Thu, 7 Nov 2019 11:50:28 +0100 Subject: [PATCH 41/60] use strategy pattern for mergeStrategies --- .../repository/api/MergeCommandResult.java | 9 -- .../repository/spi/AbstractGitCommand.java | 21 +++- .../spi/GitFastForwardIfPossible.java | 42 +++++++ .../scm/repository/spi/GitMergeCommand.java | 110 ++---------------- .../scm/repository/spi/GitMergeCommit.java | 31 +++++ .../scm/repository/spi/GitMergeStrategy.java | 82 +++++++++++++ .../repository/spi/GitMergeWithSquash.java | 30 +++++ .../scm/repository/spi/GitModifyCommand.java | 2 +- 8 files changed, 213 insertions(+), 114 deletions(-) create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitFastForwardIfPossible.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommit.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeStrategy.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeWithSquash.java diff --git a/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandResult.java b/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandResult.java index 5088fcad1c..53f712cddc 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandResult.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandResult.java @@ -13,7 +13,6 @@ import static java.util.Collections.unmodifiableCollection; */ public class MergeCommandResult { private final Collection filesWithConflict; - private boolean aborted = false; private MergeCommandResult(Collection filesWithConflict) { this.filesWithConflict = filesWithConflict; @@ -42,12 +41,4 @@ public class MergeCommandResult { public Collection getFilesWithConflict() { return unmodifiableCollection(filesWithConflict); } - - public boolean isAborted() { - return aborted; - } - - public void setAborted(boolean aborted) { - this.aborted = aborted; - } } 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 73159da5c1..adf7878221 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 @@ -152,19 +152,28 @@ class AbstractGitCommand } ObjectId resolveRevisionOrThrowNotFound(Repository repository, String revision) throws IOException { + sonia.scm.repository.Repository scmRepository = context.getRepository(); + return resolveRevisionOrThrowNotFound(repository, revision, scmRepository); + } + + static ObjectId resolveRevisionOrThrowNotFound(Repository repository, String revision, sonia.scm.repository.Repository scmRepository) throws IOException { ObjectId resolved = repository.resolve(revision); if (resolved == null) { - throw notFound(entity("Revision", revision).in(context.getRepository())); + throw notFound(entity("Revision", revision).in(scmRepository)); } else { return resolved; } } - abstract class GitCloneWorker { + abstract static class GitCloneWorker { private final Git clone; + private final GitContext context; + private final sonia.scm.repository.Repository repository; - GitCloneWorker(Git clone) { + GitCloneWorker(Git clone, GitContext context, sonia.scm.repository.Repository repository) { this.clone = clone; + this.context = context; + this.repository = repository; } abstract R run() throws IOException; @@ -173,6 +182,10 @@ class AbstractGitCommand return clone; } + GitContext getContext() { + return context; + } + void checkOutBranch(String branchName) throws IOException { try { clone.checkout().setName(branchName).call(); @@ -199,7 +212,7 @@ class AbstractGitCommand ObjectId resolveRevision(String revision) throws IOException { ObjectId resolved = clone.getRepository().resolve(revision); if (resolved == null) { - return resolveRevisionOrThrowNotFound(clone.getRepository(), "origin/" + revision); + return resolveRevisionOrThrowNotFound(clone.getRepository(), "origin/" + revision, context.getRepository()); } else { return resolved; } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitFastForwardIfPossible.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitFastForwardIfPossible.java new file mode 100644 index 0000000000..c7820009a1 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitFastForwardIfPossible.java @@ -0,0 +1,42 @@ +package sonia.scm.repository.spi; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.MergeCommand; +import org.eclipse.jgit.api.MergeResult; +import sonia.scm.repository.Repository; +import sonia.scm.repository.api.MergeCommandResult; + +import java.io.IOException; + +class GitFastForwardIfPossible extends GitMergeStrategy { + + GitFastForwardIfPossible(Git clone, MergeCommandRequest request, GitContext context, Repository repository) { + super(clone, request, context, repository); + } + + @Override + MergeCommandResult run() throws IOException { + MergeResult fastForwardResult = mergeWithFastForwardMode(MergeCommand.FastForwardMode.FF_ONLY); + if (fastForwardResult.getMergeStatus().isSuccessful()) { + push(); + return MergeCommandResult.success(); + } else { + return mergeWithCommit(); + } + } + + private MergeCommandResult mergeWithCommit() throws IOException { + MergeResult mergeCommitResult = mergeWithFastForwardMode(MergeCommand.FastForwardMode.NO_FF); + if (mergeCommitResult.getMergeStatus().isSuccessful()) { + return MergeCommandResult.success(); + } else { + return analyseFailure(mergeCommitResult); + } + } + + private MergeResult mergeWithFastForwardMode(MergeCommand.FastForwardMode fastForwardMode) throws IOException { + MergeCommand mergeCommand = getClone().merge(); + mergeCommand.setFastForward(fastForwardMode); + return doMergeInClone(mergeCommand); + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java index 1ef4eacce3..94c29bdce9 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java @@ -1,36 +1,19 @@ package sonia.scm.repository.spi; -import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.api.MergeResult; -import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.merge.ResolveMerger; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import sonia.scm.repository.GitWorkdirFactory; import sonia.scm.repository.InternalRepositoryException; -import sonia.scm.repository.Person; import sonia.scm.repository.api.MergeCommandResult; import sonia.scm.repository.api.MergeDryRunCommandResult; import sonia.scm.repository.api.MergeStrategy; import java.io.IOException; -import java.text.MessageFormat; -import java.util.Collections; import java.util.Set; public class GitMergeCommand extends AbstractGitCommand implements MergeCommand { - private static final Logger logger = LoggerFactory.getLogger(GitMergeCommand.class); - - private static final String MERGE_COMMIT_MESSAGE_TEMPLATE = String.join("\n", - "Merge of branch {0} into {1}", - "", - "Automatic merge by SCM-Manager."); - private final GitWorkdirFactory workdirFactory; private static final Set STRATEGIES = ImmutableSet.of( @@ -46,7 +29,16 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand @Override public MergeCommandResult merge(MergeCommandRequest request) { - return inClone(clone -> new MergeWorker(clone, request), workdirFactory, request.getTargetBranch()); + return mergeWithStrategy(request); + } + + private MergeCommandResult mergeWithStrategy(MergeCommandRequest request) { + if (request.getMergeStrategy() == MergeStrategy.SQUASH) { + return inClone(clone -> new GitMergeWithSquash(clone, request, context, repository), workdirFactory, request.getTargetBranch()); + } else if (request.getMergeStrategy() == MergeStrategy.FAST_FORWARD_IF_POSSIBLE) { + return inClone(clone -> new GitFastForwardIfPossible(clone, request, context, repository), workdirFactory, request.getTargetBranch()); + } + return inClone(clone -> new GitMergeCommit(clone, request, context, repository), workdirFactory, request.getTargetBranch()); } @Override @@ -73,86 +65,4 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand return STRATEGIES; } - private class MergeWorker extends GitCloneWorker { - - private final String target; - private final String toMerge; - private final Person author; - private final String messageTemplate; - private final MergeStrategy mergeStrategy; - - private MergeWorker(Git clone, MergeCommandRequest request) { - super(clone); - this.target = request.getTargetBranch(); - this.toMerge = request.getBranchToMerge(); - this.author = request.getAuthor(); - this.messageTemplate = request.getMessageTemplate(); - this.mergeStrategy = request.getMergeStrategy(); - } - - @Override - MergeCommandResult run() throws IOException { - MergeResult result = doMergeInClone(); - - if (result.getMergeStatus().isSuccessful()) { - if (!isFastForward()) { - doCommit(); - } - push(); - return MergeCommandResult.success(); - } else if (isFastForward()) { - MergeCommandResult failure = MergeCommandResult.failure(Collections.emptyList()); - failure.setAborted(true); - return failure; - } else { - return analyseFailure(result); - } - } - - private MergeResult doMergeInClone() throws IOException { - MergeResult result; - try { - ObjectId sourceRevision = resolveRevision(toMerge); - org.eclipse.jgit.api.MergeCommand mergeCommand = getClone().merge(); - mergeCommand - .setCommit(false) // we want to set the author manually - .include(toMerge, sourceRevision); - - if (mergeStrategy == MergeStrategy.SQUASH) { - mergeCommand.setSquash(true); - } else if (isFastForward()) { - mergeCommand.setFastForward(org.eclipse.jgit.api.MergeCommand.FastForwardMode.FF_ONLY); - } else { - mergeCommand.setFastForward(org.eclipse.jgit.api.MergeCommand.FastForwardMode.NO_FF); - } - - result = mergeCommand.call(); - } catch (GitAPIException e) { - throw new InternalRepositoryException(context.getRepository(), "could not merge branch " + toMerge + " into " + target, e); - } - return result; - } - - private void doCommit() { - logger.debug("merged branch {} into {}", toMerge, target); - doCommit(MessageFormat.format(determineMessageTemplate(), toMerge, target), author); - } - - private String determineMessageTemplate() { - if (Strings.isNullOrEmpty(messageTemplate)) { - return MERGE_COMMIT_MESSAGE_TEMPLATE; - } else { - return messageTemplate; - } - } - - private MergeCommandResult analyseFailure(MergeResult result) { - logger.info("could not merged branch {} into {} due to conflict in paths {}", toMerge, target, result.getConflicts().keySet()); - return MergeCommandResult.failure(result.getConflicts().keySet()); - } - - private boolean isFastForward() { - return mergeStrategy == MergeStrategy.FAST_FORWARD_IF_POSSIBLE; - } - } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommit.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommit.java new file mode 100644 index 0000000000..627efb9584 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommit.java @@ -0,0 +1,31 @@ +package sonia.scm.repository.spi; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.MergeCommand; +import org.eclipse.jgit.api.MergeResult; +import sonia.scm.repository.Repository; +import sonia.scm.repository.api.MergeCommandResult; + +import java.io.IOException; + +class GitMergeCommit extends GitMergeStrategy { + + GitMergeCommit(Git clone, MergeCommandRequest request, GitContext context, Repository repository) { + super(clone, request, context, repository); + } + + @Override + MergeCommandResult run() throws IOException { + org.eclipse.jgit.api.MergeCommand mergeCommand = getClone().merge(); + mergeCommand.setFastForward(MergeCommand.FastForwardMode.NO_FF); + MergeResult result = doMergeInClone(mergeCommand); + + if (result.getMergeStatus().isSuccessful()) { + doCommit(); + push(); + return MergeCommandResult.success(); + } else { + return analyseFailure(result); + } + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeStrategy.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeStrategy.java new file mode 100644 index 0000000000..43a3145405 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeStrategy.java @@ -0,0 +1,82 @@ +package sonia.scm.repository.spi; + +import com.google.common.base.Strings; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.MergeCommand; +import org.eclipse.jgit.api.MergeResult; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.ObjectId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.repository.Person; +import sonia.scm.repository.api.MergeCommandResult; +import sonia.scm.repository.api.MergeStrategy; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Collections; + +abstract class GitMergeStrategy extends AbstractGitCommand.GitCloneWorker { + + private static final Logger logger = LoggerFactory.getLogger(GitMergeStrategy.class); + + private static final String MERGE_COMMIT_MESSAGE_TEMPLATE = String.join("\n", + "Merge of branch {0} into {1}", + "", + "Automatic merge by SCM-Manager."); + + private final String target; + private final String toMerge; + private final Person author; + private final String messageTemplate; + private final MergeStrategy mergeStrategy; + + GitMergeStrategy(Git clone, MergeCommandRequest request, GitContext context, sonia.scm.repository.Repository repository) { + super(clone, context, repository); + this.target = request.getTargetBranch(); + this.toMerge = request.getBranchToMerge(); + this.author = request.getAuthor(); + this.messageTemplate = request.getMessageTemplate(); + this.mergeStrategy = request.getMergeStrategy(); + } + + MergeResult doMergeInClone(MergeCommand mergeCommand) throws IOException { + MergeResult result; + try { + ObjectId sourceRevision = resolveRevision(toMerge); + mergeCommand + .setCommit(false) // we want to set the author manually + .include(toMerge, sourceRevision); + + result = mergeCommand.call(); + } catch (GitAPIException e) { + throw new InternalRepositoryException(getContext().getRepository(), "could not merge branch " + toMerge + " into " + target, e); + } + return result; + } + + void doCommit() { + logger.debug("merged branch {} into {}", toMerge, target); + doCommit(MessageFormat.format(determineMessageTemplate(), toMerge, target), author); + } + + private String determineMessageTemplate() { + if (Strings.isNullOrEmpty(messageTemplate)) { + return MERGE_COMMIT_MESSAGE_TEMPLATE; + } else { + return messageTemplate; + } + } + + MergeCommandResult analyseFailure(MergeResult result) { + logger.info("could not merged branch {} into {} due to conflict in paths {}", toMerge, target, result.getConflicts().keySet()); + return MergeCommandResult.failure(result.getConflicts().keySet()); + } + + MergeCommandResult aborted() { + MergeCommandResult failure = MergeCommandResult.failure(Collections.emptyList()); + failure.setAborted(true); + return failure; + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeWithSquash.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeWithSquash.java new file mode 100644 index 0000000000..dfbd31fe32 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeWithSquash.java @@ -0,0 +1,30 @@ +package sonia.scm.repository.spi; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.MergeResult; +import sonia.scm.repository.Repository; +import sonia.scm.repository.api.MergeCommandResult; + +import java.io.IOException; + +class GitMergeWithSquash extends GitMergeStrategy { + + GitMergeWithSquash(Git clone, MergeCommandRequest request, GitContext context, Repository repository) { + super(clone, request, context, repository); + } + + @Override + MergeCommandResult run() throws IOException { + org.eclipse.jgit.api.MergeCommand mergeCommand = getClone().merge(); + mergeCommand.setSquash(true); + MergeResult result = doMergeInClone(mergeCommand); + + if (result.getMergeStatus().isSuccessful()) { + doCommit(); + push(); + return MergeCommandResult.success(); + } else { + return analyseFailure(result); + } + } +} 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..19234c32e3 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 @@ -46,7 +46,7 @@ public class GitModifyCommand extends AbstractGitCommand implements ModifyComman private final ModifyCommandRequest request; ModifyWorker(Git clone, ModifyCommandRequest request) { - super(clone); + super(clone, context, repository); this.workDir = clone.getRepository().getWorkTree(); this.request = request; } From e736ae6c50a83a9cdbf9ac29025587016b88fafe Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Thu, 7 Nov 2019 13:42:45 +0100 Subject: [PATCH 42/60] cleanup --- .../scm/repository/spi/GitFastForwardIfPossible.java | 2 ++ .../sonia/scm/repository/spi/GitMergeStrategy.java | 10 ---------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitFastForwardIfPossible.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitFastForwardIfPossible.java index c7820009a1..417280046f 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitFastForwardIfPossible.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitFastForwardIfPossible.java @@ -28,6 +28,8 @@ class GitFastForwardIfPossible extends GitMergeStrategy { private MergeCommandResult mergeWithCommit() throws IOException { MergeResult mergeCommitResult = mergeWithFastForwardMode(MergeCommand.FastForwardMode.NO_FF); if (mergeCommitResult.getMergeStatus().isSuccessful()) { + doCommit(); + push(); return MergeCommandResult.success(); } else { return analyseFailure(mergeCommitResult); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeStrategy.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeStrategy.java index 43a3145405..d4d9de3de7 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeStrategy.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeStrategy.java @@ -11,11 +11,9 @@ import org.slf4j.LoggerFactory; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Person; import sonia.scm.repository.api.MergeCommandResult; -import sonia.scm.repository.api.MergeStrategy; import java.io.IOException; import java.text.MessageFormat; -import java.util.Collections; abstract class GitMergeStrategy extends AbstractGitCommand.GitCloneWorker { @@ -30,7 +28,6 @@ abstract class GitMergeStrategy extends AbstractGitCommand.GitCloneWorker Date: Fri, 8 Nov 2019 08:36:00 +0100 Subject: [PATCH 43/60] refactor after review --- .../repository/api/MergeCommandBuilder.java | 3 +++ .../spi/GitFastForwardIfPossible.java | 22 ++++++------------ .../scm/repository/spi/GitMergeCommand.java | 21 ++++++++++++----- .../scm/repository/spi/GitMergeCommit.java | 2 +- .../repository/spi/GitMergeWithSquash.java | 3 ++- .../repository/spi/GitMergeCommandTest.java | 23 +++++++++++++++++++ 6 files changed, 51 insertions(+), 23 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java index db6c677e1f..6c4324c259 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java @@ -120,6 +120,9 @@ public class MergeCommandBuilder { * @return This builder instance. */ public MergeCommandBuilder setMergeStrategy(MergeStrategy strategy) { + if (!mergeCommand.isSupported(strategy)) { + throw new IllegalArgumentException("merge strategy not supported: " + strategy); + } request.setMergeStrategy(strategy); return this; } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitFastForwardIfPossible.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitFastForwardIfPossible.java index 417280046f..64a20a33cb 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitFastForwardIfPossible.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitFastForwardIfPossible.java @@ -10,35 +10,27 @@ import java.io.IOException; class GitFastForwardIfPossible extends GitMergeStrategy { + private GitMergeStrategy fallbackMerge; + GitFastForwardIfPossible(Git clone, MergeCommandRequest request, GitContext context, Repository repository) { super(clone, request, context, repository); + fallbackMerge = new GitMergeCommit(clone, request, context, repository); } @Override MergeCommandResult run() throws IOException { - MergeResult fastForwardResult = mergeWithFastForwardMode(MergeCommand.FastForwardMode.FF_ONLY); + MergeResult fastForwardResult = mergeWithFastForwardOnlyMode(); if (fastForwardResult.getMergeStatus().isSuccessful()) { push(); return MergeCommandResult.success(); } else { - return mergeWithCommit(); + return fallbackMerge.run(); } } - private MergeCommandResult mergeWithCommit() throws IOException { - MergeResult mergeCommitResult = mergeWithFastForwardMode(MergeCommand.FastForwardMode.NO_FF); - if (mergeCommitResult.getMergeStatus().isSuccessful()) { - doCommit(); - push(); - return MergeCommandResult.success(); - } else { - return analyseFailure(mergeCommitResult); - } - } - - private MergeResult mergeWithFastForwardMode(MergeCommand.FastForwardMode fastForwardMode) throws IOException { + private MergeResult mergeWithFastForwardOnlyMode() throws IOException { MergeCommand mergeCommand = getClone().merge(); - mergeCommand.setFastForward(fastForwardMode); + mergeCommand.setFastForward(MergeCommand.FastForwardMode.FF_ONLY); return doMergeInClone(mergeCommand); } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java index 94c29bdce9..27b4143b41 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java @@ -12,6 +12,8 @@ import sonia.scm.repository.api.MergeStrategy; import java.io.IOException; import java.util.Set; +import static org.eclipse.jgit.merge.MergeStrategy.RECURSIVE; + public class GitMergeCommand extends AbstractGitCommand implements MergeCommand { private final GitWorkdirFactory workdirFactory; @@ -33,19 +35,26 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand } private MergeCommandResult mergeWithStrategy(MergeCommandRequest request) { - if (request.getMergeStrategy() == MergeStrategy.SQUASH) { - return inClone(clone -> new GitMergeWithSquash(clone, request, context, repository), workdirFactory, request.getTargetBranch()); - } else if (request.getMergeStrategy() == MergeStrategy.FAST_FORWARD_IF_POSSIBLE) { - return inClone(clone -> new GitFastForwardIfPossible(clone, request, context, repository), workdirFactory, request.getTargetBranch()); + switch(request.getMergeStrategy()) { + case SQUASH: + return inClone(clone -> new GitMergeWithSquash(clone, request, context, repository), workdirFactory, request.getTargetBranch()); + + case FAST_FORWARD_IF_POSSIBLE: + return inClone(clone -> new GitFastForwardIfPossible(clone, request, context, repository), workdirFactory, request.getTargetBranch()); + + case MERGE_COMMIT: + return inClone(clone -> new GitMergeCommit(clone, request, context, repository), workdirFactory, request.getTargetBranch()); + + default: + throw new IllegalArgumentException("unknown merge strategy: " + request.getMergeStrategy()); } - return inClone(clone -> new GitMergeCommit(clone, request, context, repository), workdirFactory, request.getTargetBranch()); } @Override public MergeDryRunCommandResult dryRun(MergeCommandRequest request) { try { Repository repository = context.open(); - ResolveMerger merger = (ResolveMerger) org.eclipse.jgit.merge.MergeStrategy.RECURSIVE.newMerger(repository, true); + ResolveMerger merger = (ResolveMerger) RECURSIVE.newMerger(repository, true); return new MergeDryRunCommandResult( merger.merge( resolveRevisionOrThrowNotFound(repository, request.getBranchToMerge()), diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommit.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommit.java index 627efb9584..6aa68a0ea8 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommit.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommit.java @@ -16,7 +16,7 @@ class GitMergeCommit extends GitMergeStrategy { @Override MergeCommandResult run() throws IOException { - org.eclipse.jgit.api.MergeCommand mergeCommand = getClone().merge(); + MergeCommand mergeCommand = getClone().merge(); mergeCommand.setFastForward(MergeCommand.FastForwardMode.NO_FF); MergeResult result = doMergeInClone(mergeCommand); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeWithSquash.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeWithSquash.java index dfbd31fe32..b688956404 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeWithSquash.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeWithSquash.java @@ -4,6 +4,7 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.MergeResult; import sonia.scm.repository.Repository; import sonia.scm.repository.api.MergeCommandResult; +import org.eclipse.jgit.api.MergeCommand; import java.io.IOException; @@ -15,7 +16,7 @@ class GitMergeWithSquash extends GitMergeStrategy { @Override MergeCommandResult run() throws IOException { - org.eclipse.jgit.api.MergeCommand mergeCommand = getClone().merge(); + MergeCommand mergeCommand = getClone().merge(); mergeCommand.setSquash(true); MergeResult result = doMergeInClone(mergeCommand); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java index 4f04205202..d3c78ae9da 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java @@ -287,6 +287,29 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase { assertThat(mergeCommit.getId()).isEqualTo(featureBranchHead); } + @Test + public void shouldDoMergeCommitIfFastForwardIsNotPossible() throws IOException, GitAPIException { + GitMergeCommand command = createCommand(); + MergeCommandRequest request = new MergeCommandRequest(); + request.setTargetBranch("master"); + request.setBranchToMerge("mergeable"); + request.setMergeStrategy(MergeStrategy.FAST_FORWARD_IF_POSSIBLE); + request.setAuthor(new Person("Dirk Gently", "dirk@holistic.det")); + + MergeCommandResult mergeCommandResult = command.merge(request); + + assertThat(mergeCommandResult.isSuccess()).isTrue(); + + Repository repository = createContext().open(); + Iterable commits = new Git(repository).log().add(repository.resolve("master")).setMaxCount(1).call(); + RevCommit mergeCommit = commits.iterator().next(); + PersonIdent mergeAuthor = mergeCommit.getAuthorIdent(); + String message = mergeCommit.getFullMessage(); + assertThat(mergeAuthor.getName()).isEqualTo("Dirk Gently"); + assertThat(mergeAuthor.getEmailAddress()).isEqualTo("dirk@holistic.det"); + assertThat(message).contains("master", "mergeable"); + } + @Test(expected = NotFoundException.class) public void shouldHandleNotExistingSourceBranchInMerge() { GitMergeCommand command = createCommand(); From b1216a898b56cd06e9d40b2ea69581ea9042cf1b Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Fri, 8 Nov 2019 11:46:13 +0100 Subject: [PATCH 44/60] remove mergeResource and move dryRun to review-plugin --- scm-core/pom.xml | 6 + .../scm/api/v2/resources/MergeCommandDto.java | 0 .../repository/spi/GitMergeCommandTest.java | 9 + .../scm/api/v2/resources/MergeResource.java | 95 -------- .../RepositoryToRepositoryDtoMapper.java | 6 - .../scm/api/v2/resources/ResourceLinks.java | 20 -- .../api/v2/resources/MergeResourceTest.java | 205 ------------------ 7 files changed, 15 insertions(+), 326 deletions(-) rename {scm-webapp => scm-core}/src/main/java/sonia/scm/api/v2/resources/MergeCommandDto.java (100%) delete mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/MergeResource.java delete mode 100644 scm-webapp/src/test/java/sonia/scm/api/v2/resources/MergeResourceTest.java diff --git a/scm-core/pom.xml b/scm-core/pom.xml index c7bbc664c9..8437b256b2 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -214,6 +214,12 @@ shiro-unit test + + org.hibernate + hibernate-validator + 5.3.6.Final + compile + diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MergeCommandDto.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/MergeCommandDto.java similarity index 100% rename from scm-webapp/src/main/java/sonia/scm/api/v2/resources/MergeCommandDto.java rename to scm-core/src/main/java/sonia/scm/api/v2/resources/MergeCommandDto.java diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java index d3c78ae9da..d7e8482f55 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java @@ -64,6 +64,7 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase { MergeCommandRequest request = new MergeCommandRequest(); request.setTargetBranch("master"); request.setBranchToMerge("mergeable"); + request.setMergeStrategy(MergeStrategy.MERGE_COMMIT); request.setAuthor(new Person("Dirk Gently", "dirk@holistic.det")); MergeCommandResult mergeCommandResult = command.merge(request); @@ -90,6 +91,7 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase { MergeCommandRequest request = new MergeCommandRequest(); request.setTargetBranch("master"); request.setBranchToMerge("empty_merge"); + request.setMergeStrategy(MergeStrategy.MERGE_COMMIT); request.setAuthor(new Person("Dirk Gently", "dirk@holistic.det")); MergeCommandResult mergeCommandResult = command.merge(request); @@ -111,6 +113,7 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase { request.setTargetBranch("master"); request.setBranchToMerge("mergeable"); request.setAuthor(new Person("Dirk Gently", "dirk@holistic.det")); + request.setMergeStrategy(MergeStrategy.MERGE_COMMIT); MergeCommandResult mergeCommandResult = command.merge(request); @@ -134,6 +137,7 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase { MergeCommandRequest request = new MergeCommandRequest(); request.setTargetBranch("master"); request.setBranchToMerge("mergeable"); + request.setMergeStrategy(MergeStrategy.MERGE_COMMIT); request.setAuthor(new Person("Dirk Gently", "dirk@holistic.det")); request.setMessageTemplate("simple"); @@ -154,6 +158,7 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase { MergeCommandRequest request = new MergeCommandRequest(); request.setBranchToMerge("test-branch"); request.setTargetBranch("master"); + request.setMergeStrategy(MergeStrategy.MERGE_COMMIT); MergeCommandResult mergeCommandResult = command.merge(request); @@ -175,6 +180,7 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase { MergeCommandRequest request = new MergeCommandRequest(); request.setTargetBranch("master"); request.setBranchToMerge("mergeable"); + request.setMergeStrategy(MergeStrategy.MERGE_COMMIT); MergeCommandResult mergeCommandResult = command.merge(request); @@ -194,6 +200,7 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase { request.setAuthor(new Person("Dirk Gently", "dirk@holistic.det")); request.setTargetBranch("mergeable"); request.setBranchToMerge("master"); + request.setMergeStrategy(MergeStrategy.MERGE_COMMIT); MergeCommandResult mergeCommandResult = command.merge(request); @@ -316,6 +323,7 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase { MergeCommandRequest request = new MergeCommandRequest(); request.setTargetBranch("mergeable"); request.setBranchToMerge("not_existing"); + request.setMergeStrategy(MergeStrategy.MERGE_COMMIT); command.merge(request); } @@ -324,6 +332,7 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase { public void shouldHandleNotExistingTargetBranchInMerge() { GitMergeCommand command = createCommand(); MergeCommandRequest request = new MergeCommandRequest(); + request.setMergeStrategy(MergeStrategy.MERGE_COMMIT); request.setTargetBranch("not_existing"); request.setBranchToMerge("master"); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MergeResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MergeResource.java deleted file mode 100644 index df59ba2abc..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MergeResource.java +++ /dev/null @@ -1,95 +0,0 @@ -package sonia.scm.api.v2.resources; - -import com.webcohesion.enunciate.metadata.rs.ResponseCode; -import com.webcohesion.enunciate.metadata.rs.StatusCodes; -import lombok.extern.slf4j.Slf4j; -import org.apache.http.HttpStatus; -import sonia.scm.ConcurrentModificationException; -import sonia.scm.repository.NamespaceAndName; -import sonia.scm.repository.RepositoryPermissions; -import sonia.scm.repository.api.MergeCommandBuilder; -import sonia.scm.repository.api.MergeCommandResult; -import sonia.scm.repository.api.MergeDryRunCommandResult; -import sonia.scm.repository.api.RepositoryService; -import sonia.scm.repository.api.RepositoryServiceFactory; -import sonia.scm.web.VndMediaType; - -import javax.inject.Inject; -import javax.validation.Valid; -import javax.ws.rs.Consumes; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Response; - -@Slf4j -public class MergeResource { - - private final RepositoryServiceFactory serviceFactory; - private final MergeResultToDtoMapper mapper; - - @Inject - public MergeResource(RepositoryServiceFactory serviceFactory, MergeResultToDtoMapper mapper) { - this.serviceFactory = serviceFactory; - this.mapper = mapper; - } - - @POST - @Path("") - @Produces(VndMediaType.MERGE_RESULT) - @Consumes(VndMediaType.MERGE_COMMAND) - @StatusCodes({ - @ResponseCode(code = 204, condition = "merge has been executed successfully"), - @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), - @ResponseCode(code = 403, condition = "not authorized, the current user does not have the privilege to write the repository"), - @ResponseCode(code = 409, condition = "The branches could not be merged automatically due to conflicts (conflicting files will be returned)"), - @ResponseCode(code = 500, condition = "internal server error") - }) - public Response merge(@PathParam("namespace") String namespace, @PathParam("name") String name, @Valid MergeCommandDto mergeCommand) { - NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name); - log.info("Merge in Repository {}/{} from {} to {}", namespace, name, mergeCommand.getSourceRevision(), mergeCommand.getTargetRevision()); - try (RepositoryService repositoryService = serviceFactory.create(namespaceAndName)) { - RepositoryPermissions.push(repositoryService.getRepository()).check(); - MergeCommandResult mergeCommandResult = createMergeCommand(mergeCommand, repositoryService).executeMerge(); - if (mergeCommandResult.isSuccess()) { - return Response.noContent().build(); - } else { - return Response.status(HttpStatus.SC_CONFLICT).entity(mapper.map(mergeCommandResult)).build(); - } - } - } - - @POST - @Path("dry-run/") - @StatusCodes({ - @ResponseCode(code = 204, condition = "merge can be done automatically"), - @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), - @ResponseCode(code = 409, condition = "The branches can not be merged automatically due to conflicts"), - @ResponseCode(code = 500, condition = "internal server error") - }) - public Response dryRun(@PathParam("namespace") String namespace, @PathParam("name") String name, @Valid MergeCommandDto mergeCommand) { - - NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name); - log.info("Merge in Repository {}/{} from {} to {}", namespace, name, mergeCommand.getSourceRevision(), mergeCommand.getTargetRevision()); - try (RepositoryService repositoryService = serviceFactory.create(namespaceAndName)) { - if (RepositoryPermissions.push(repositoryService.getRepository()).isPermitted()) { - MergeDryRunCommandResult mergeCommandResult = createMergeCommand(mergeCommand, repositoryService).dryRun(); - if (mergeCommandResult.isMergeable()) { - return Response.noContent().build(); - } else { - throw new ConcurrentModificationException("revision", mergeCommand.getTargetRevision()); - } - } else { - return Response.noContent().build(); - } - } - } - - private MergeCommandBuilder createMergeCommand(MergeCommandDto mergeCommand, RepositoryService repositoryService) { - return repositoryService - .getMergeCommand() - .setBranchToMerge(mergeCommand.getSourceRevision()) - .setTargetBranch(mergeCommand.getTargetRevision()); - } -} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java index 67a6f26de5..c09f90c83d 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java @@ -63,12 +63,6 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper Date: Fri, 8 Nov 2019 13:10:31 +0100 Subject: [PATCH 45/60] fix dependencies after removing mergeResource --- .../sonia/scm/api/v2/resources/RepositoryResource.java | 10 +++------- .../sonia/scm/api/v2/resources/RepositoryTestBase.java | 4 +--- .../sonia/scm/api/v2/resources/ResourceLinksMock.java | 1 - 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java index 1cfc8b332b..2294dc600e 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java @@ -42,7 +42,6 @@ public class RepositoryResource { private final Provider diffRootResource; private final Provider modificationsRootResource; private final Provider fileHistoryRootResource; - private final Provider mergeResource; private final Provider incomingRootResource; @Inject @@ -57,8 +56,8 @@ public class RepositoryResource { Provider diffRootResource, Provider modificationsRootResource, Provider fileHistoryRootResource, - Provider incomingRootResource, - Provider mergeResource) { + Provider incomingRootResource + ) { this.dtoToRepositoryMapper = dtoToRepositoryMapper; this.manager = manager; this.repositoryToDtoMapper = repositoryToDtoMapper; @@ -72,8 +71,8 @@ public class RepositoryResource { this.diffRootResource = diffRootResource; this.modificationsRootResource = modificationsRootResource; this.fileHistoryRootResource = fileHistoryRootResource; - this.mergeResource = mergeResource; this.incomingRootResource = incomingRootResource; + } /** @@ -208,9 +207,6 @@ public class RepositoryResource { return incomingRootResource.get(); } - @Path("merge/") - public MergeResource merge() {return mergeResource.get(); } - private Supplier loadBy(String namespace, String name) { NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name); return () -> Optional.ofNullable(manager.get(namespaceAndName)).orElseThrow(() -> notFound(entity(namespaceAndName))); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTestBase.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTestBase.java index a8901e2d79..8f183f2aa6 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTestBase.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTestBase.java @@ -22,7 +22,6 @@ public abstract class RepositoryTestBase { protected Provider fileHistoryRootResource; protected Provider repositoryCollectionResource; protected Provider incomingRootResource; - protected Provider mergeResource; RepositoryRootResource getRepositoryRootResource() { @@ -39,8 +38,7 @@ public abstract class RepositoryTestBase { diffRootResource, modificationsRootResource, fileHistoryRootResource, - incomingRootResource, - mergeResource)), repositoryCollectionResource); + incomingRootResource)), repositoryCollectionResource); } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java index 478b3efc92..672771aadd 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java @@ -45,7 +45,6 @@ public class ResourceLinksMock { when(resourceLinks.uiPlugin()).thenReturn(new ResourceLinks.UIPluginLinks(uriInfo)); when(resourceLinks.authentication()).thenReturn(new ResourceLinks.AuthenticationLinks(uriInfo)); when(resourceLinks.index()).thenReturn(new ResourceLinks.IndexLinks(uriInfo)); - when(resourceLinks.merge()).thenReturn(new ResourceLinks.MergeLinks(uriInfo)); when(resourceLinks.permissions()).thenReturn(new ResourceLinks.PermissionsLinks(uriInfo)); when(resourceLinks.repositoryVerbs()).thenReturn(new ResourceLinks.RepositoryVerbLinks(uriInfo)); when(resourceLinks.repositoryRole()).thenReturn(new ResourceLinks.RepositoryRoleLinks(uriInfo)); From 8e0026962d65c8814beb9394390ceeca98232558 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Fri, 8 Nov 2019 14:10:27 +0100 Subject: [PATCH 46/60] Fix typo --- .../main/java/sonia/scm/repository/spi/GitMergeStrategy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeStrategy.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeStrategy.java index d4d9de3de7..1d53b99c99 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeStrategy.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeStrategy.java @@ -66,7 +66,7 @@ abstract class GitMergeStrategy extends AbstractGitCommand.GitCloneWorker Date: Fri, 8 Nov 2019 15:43:51 +0100 Subject: [PATCH 47/60] Secure test --- .../test/java/sonia/scm/repository/spi/GitMergeCommandTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java index d7e8482f55..fcd721c3a2 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java @@ -289,6 +289,7 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase { Iterable commits = new Git(repository).log().add(repository.resolve("master")).setMaxCount(1).call(); RevCommit mergeCommit = commits.iterator().next(); + assertThat(mergeCommit.getParentCount()).isEqualTo(1); PersonIdent mergeAuthor = mergeCommit.getAuthorIdent(); assertThat(mergeAuthor.getName()).isEqualTo("Philip J Fry"); assertThat(mergeCommit.getId()).isEqualTo(featureBranchHead); @@ -311,6 +312,7 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase { Iterable commits = new Git(repository).log().add(repository.resolve("master")).setMaxCount(1).call(); RevCommit mergeCommit = commits.iterator().next(); PersonIdent mergeAuthor = mergeCommit.getAuthorIdent(); + assertThat(mergeCommit.getParentCount()).isEqualTo(2); String message = mergeCommit.getFullMessage(); assertThat(mergeAuthor.getName()).isEqualTo("Dirk Gently"); assertThat(mergeAuthor.getEmailAddress()).isEqualTo("dirk@holistic.det"); From ae32b6f7971cd4594ab2d271ab398665e98c4092 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Fri, 8 Nov 2019 15:44:37 +0100 Subject: [PATCH 48/60] Use dedicated exception for unsupported merge strategies --- .../MergeStrategyNotSupportedException.java | 27 +++++++++++++++++++ .../scm/repository/spi/GitMergeCommand.java | 3 ++- .../main/resources/locales/de/plugins.json | 4 +++ .../main/resources/locales/en/plugins.json | 4 +++ 4 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 scm-core/src/main/java/sonia/scm/repository/api/MergeStrategyNotSupportedException.java diff --git a/scm-core/src/main/java/sonia/scm/repository/api/MergeStrategyNotSupportedException.java b/scm-core/src/main/java/sonia/scm/repository/api/MergeStrategyNotSupportedException.java new file mode 100644 index 0000000000..ae9fbb08b4 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/api/MergeStrategyNotSupportedException.java @@ -0,0 +1,27 @@ +package sonia.scm.repository.api; + +import sonia.scm.BadRequestException; +import sonia.scm.repository.Repository; + +import static sonia.scm.ContextEntry.ContextBuilder.entity; + +@SuppressWarnings("squid:MaximumInheritanceDepth") // exceptions have a deep inheritance depth themselves; therefore we accept this here +public class MergeStrategyNotSupportedException extends BadRequestException { + + private static final long serialVersionUID = 256498734456613496L; + + private static final String CODE = "6eRhF9gU41"; + + public MergeStrategyNotSupportedException(Repository repository, MergeStrategy strategy) { + super(entity(repository).build(), createMessage(strategy)); + } + + @Override + public String getCode() { + return CODE; + } + + private static String createMessage(MergeStrategy strategy) { + return "merge strategy " + strategy + " is not supported by this repository"; + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java index 27b4143b41..226dd0f285 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java @@ -8,6 +8,7 @@ import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.api.MergeCommandResult; import sonia.scm.repository.api.MergeDryRunCommandResult; import sonia.scm.repository.api.MergeStrategy; +import sonia.scm.repository.api.MergeStrategyNotSupportedException; import java.io.IOException; import java.util.Set; @@ -46,7 +47,7 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand return inClone(clone -> new GitMergeCommit(clone, request, context, repository), workdirFactory, request.getTargetBranch()); default: - throw new IllegalArgumentException("unknown merge strategy: " + request.getMergeStrategy()); + throw new MergeStrategyNotSupportedException(repository, request.getMergeStrategy()); } } diff --git a/scm-webapp/src/main/resources/locales/de/plugins.json b/scm-webapp/src/main/resources/locales/de/plugins.json index 2e36e60312..fe97d296c4 100644 --- a/scm-webapp/src/main/resources/locales/de/plugins.json +++ b/scm-webapp/src/main/resources/locales/de/plugins.json @@ -183,6 +183,10 @@ "65RdZ5atX1": { "displayName": "Fehler beim Löschen von Plugin-Dateien", "description": "Einige Dateien für die Plugin-Deinstallation konnten nicht gelöscht werden. Dieses kann zu Inkonsistenzen führen, so dass der SCM-Manager nicht mehr korrekt starten kann. Bitte prüfen Sie die Logs und bereinigen Sie das Plugin-Verzeichnis des SCM-Managers manuell. Um die Installation eines Plugins abzubrechen, löschen Sie die zugehörige smp Datei aus dem Plugin-Verzeichnis. Um ein Entfernen eines Plugins zu verhindern, entfernen Sie die Datei namens 'uninstall' aus dem entsprechenden Verzeichnis des Plugins." + }, + "6eRhF9gU41": { + "displayName": "Nicht unterstützte Merge-Strategie", + "description": "Die gewählte Merge-Strategie wird von dem Repository nicht unterstützt." } }, "namespaceStrategies": { diff --git a/scm-webapp/src/main/resources/locales/en/plugins.json b/scm-webapp/src/main/resources/locales/en/plugins.json index 11643c4a70..27171374c9 100644 --- a/scm-webapp/src/main/resources/locales/en/plugins.json +++ b/scm-webapp/src/main/resources/locales/en/plugins.json @@ -183,6 +183,10 @@ "65RdZ5atX1": { "displayName": "Error removing plugin files", "description": "Some files to cancel the plugin (un)installation could not be deleted. This can lead to inconsistencies so that the SCM-Manager cannot restart properly. Please check the logs and clean up the plugin folder manually. To cancel the installation of a plugin, remove the corresponding smp file. To cancel the uninstallation, remove the file named 'uninstall' inside the directory for this plugin." + }, + "6eRhF9gU41": { + "displayName": "Merge strategy not supported", + "description": "The selected merge strategy is not supported by the repository." } }, "namespaceStrategies": { From 520c0d6ecf35a9971b8603741f4190e8b00a0ff7 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Fri, 8 Nov 2019 15:41:45 +0000 Subject: [PATCH 49/60] Close branch feature/merge_with_squash From c2e30a582fde9f193db9776bbcdf71a5ea689091 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Fri, 8 Nov 2019 16:44:04 +0100 Subject: [PATCH 50/60] Remove unused mapper --- .../sonia/scm/api/v2/resources/MergeResultDto.java | 12 ------------ .../scm/api/v2/resources/MergeResultToDtoMapper.java | 9 --------- .../sonia/scm/api/v2/resources/MapperModule.java | 2 -- 3 files changed, 23 deletions(-) delete mode 100644 scm-core/src/main/java/sonia/scm/api/v2/resources/MergeResultDto.java delete mode 100644 scm-core/src/main/java/sonia/scm/api/v2/resources/MergeResultToDtoMapper.java diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/MergeResultDto.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/MergeResultDto.java deleted file mode 100644 index fa523153cf..0000000000 --- a/scm-core/src/main/java/sonia/scm/api/v2/resources/MergeResultDto.java +++ /dev/null @@ -1,12 +0,0 @@ -package sonia.scm.api.v2.resources; - -import lombok.Getter; -import lombok.Setter; - -import java.util.Collection; - -@Getter -@Setter -public class MergeResultDto { - private Collection filesWithConflict; -} diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/MergeResultToDtoMapper.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/MergeResultToDtoMapper.java deleted file mode 100644 index 1dbbe8aacd..0000000000 --- a/scm-core/src/main/java/sonia/scm/api/v2/resources/MergeResultToDtoMapper.java +++ /dev/null @@ -1,9 +0,0 @@ -package sonia.scm.api.v2.resources; - -import org.mapstruct.Mapper; -import sonia.scm.repository.api.MergeCommandResult; - -@Mapper -public interface MergeResultToDtoMapper { - MergeResultDto map(MergeCommandResult result); -} 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 6b6846f039..48de63969d 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 @@ -47,8 +47,6 @@ public class MapperModule extends AbstractModule { bind(ScmViolationExceptionToErrorDtoMapper.class).to(Mappers.getMapper(ScmViolationExceptionToErrorDtoMapper.class).getClass()); bind(ExceptionWithContextToErrorDtoMapper.class).to(Mappers.getMapper(ExceptionWithContextToErrorDtoMapper.class).getClass()); - bind(MergeResultToDtoMapper.class).to(Mappers.getMapper(MergeResultToDtoMapper.class).getClass()); - // no mapstruct required bind(MeDtoFactory.class); bind(UIPluginDtoMapper.class); From 1695d13a984a336175a76e0a1dc3b477466ebe19 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Mon, 11 Nov 2019 10:30:11 +0100 Subject: [PATCH 51/60] Create new branches without clone of git repository --- .../scm/repository/spi/GitBranchCommand.java | 85 ++++++++++++++----- .../spi/GitRepositoryServiceProvider.java | 12 ++- .../spi/GitRepositoryServiceResolver.java | 10 ++- .../repository/spi/GitBranchCommandTest.java | 63 +++++++++++--- 4 files changed, 133 insertions(+), 37 deletions(-) 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 fe39006a66..fba340e0a4 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 @@ -35,49 +35,88 @@ package sonia.scm.repository.spi; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.transport.PushResult; -import org.eclipse.jgit.transport.RemoteRefUpdate; +import sonia.scm.event.ScmEventBus; import sonia.scm.repository.Branch; import sonia.scm.repository.GitUtil; -import sonia.scm.repository.GitWorkdirFactory; import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.repository.PostReceiveRepositoryHookEvent; +import sonia.scm.repository.PreReceiveRepositoryHookEvent; import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryHookEvent; +import sonia.scm.repository.RepositoryHookType; import sonia.scm.repository.api.BranchRequest; -import sonia.scm.repository.util.WorkingCopy; +import sonia.scm.repository.api.HookBranchProvider; +import sonia.scm.repository.api.HookContext; +import sonia.scm.repository.api.HookContextFactory; +import sonia.scm.repository.api.HookFeature; -import java.util.stream.StreamSupport; +import java.io.IOException; +import java.util.List; +import java.util.Set; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; public class GitBranchCommand extends AbstractGitCommand implements BranchCommand { - private final GitWorkdirFactory workdirFactory; + private final HookContextFactory hookContextFactory; + private final ScmEventBus eventBus; - GitBranchCommand(GitContext context, Repository repository, GitWorkdirFactory workdirFactory) { + GitBranchCommand(GitContext context, Repository repository, HookContextFactory hookContextFactory, ScmEventBus eventBus) { super(context, repository); - this.workdirFactory = workdirFactory; + this.hookContextFactory = hookContextFactory; + this.eventBus = eventBus; } @Override public Branch branch(BranchRequest request) { - 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(); - StreamSupport.stream(call.spliterator(), false) - .flatMap(pushResult -> pushResult.getRemoteUpdates().stream()) - .filter(remoteRefUpdate -> remoteRefUpdate.getStatus() != RemoteRefUpdate.Status.OK) - .findFirst() - .ifPresent(r -> this.handlePushError(r, request, context.getRepository())); + try (Git git = new Git(context.open())) { + RepositoryHookEvent hookEvent = createHookEvent(request); + eventBus.post(new PreReceiveRepositoryHookEvent(hookEvent)); + Ref ref = git.branchCreate().setStartPoint(request.getParentBranch()).setName(request.getNewBranch()).call(); + eventBus.post(new PostReceiveRepositoryHookEvent(hookEvent)); return Branch.normalBranch(request.getNewBranch(), GitUtil.getId(ref.getObjectId())); - } catch (GitAPIException ex) { + } catch (GitAPIException | IOException ex) { throw new InternalRepositoryException(repository, "could not create branch " + request.getNewBranch(), ex); } } - private void handlePushError(RemoteRefUpdate remoteRefUpdate, BranchRequest request, Repository repository) { - if (remoteRefUpdate.getStatus() != RemoteRefUpdate.Status.OK) { - // TODO handle failed remote update - throw new IntegrateChangesFromWorkdirException(repository, - String.format("Could not push new branch '%s' into central repository", request.getNewBranch())); + private RepositoryHookEvent createHookEvent(BranchRequest request) { + HookContext context = hookContextFactory.createContext(new BranchHookContextProvider(request), this.context.getRepository()); + return new RepositoryHookEvent(context, this.context.getRepository(), RepositoryHookType.PRE_RECEIVE); + } + + private static class BranchHookContextProvider extends HookContextProvider { + private final BranchRequest request; + + public BranchHookContextProvider(BranchRequest request) { + this.request = request; + } + + @Override + public Set getSupportedFeatures() { + return singleton(HookFeature.BRANCH_PROVIDER); + } + + @Override + public HookBranchProvider getBranchProvider() { + return new HookBranchProvider() { + @Override + public List getCreatedOrModified() { + return singletonList(request.getNewBranch()); + } + + @Override + public List getDeletedOrClosed() { + return emptyList(); + } + }; + } + + @Override + public HookChangesetProvider getChangesetProvider() { + return request -> new HookChangesetResponse(emptyList()); } } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java index dc43b8d9b7..8c3d7eb17a 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java @@ -35,10 +35,12 @@ package sonia.scm.repository.spi; import com.google.common.collect.ImmutableSet; import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; +import sonia.scm.event.ScmEventBus; import sonia.scm.repository.Feature; import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.Repository; import sonia.scm.repository.api.Command; +import sonia.scm.repository.api.HookContextFactory; import sonia.scm.web.lfs.LfsBlobStoreFactory; import java.io.IOException; @@ -77,10 +79,12 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider //~--- constructors --------------------------------------------------------- - public GitRepositoryServiceProvider(GitRepositoryHandler handler, Repository repository, GitRepositoryConfigStoreProvider storeProvider, LfsBlobStoreFactory lfsBlobStoreFactory) { + public GitRepositoryServiceProvider(GitRepositoryHandler handler, Repository repository, GitRepositoryConfigStoreProvider storeProvider, LfsBlobStoreFactory lfsBlobStoreFactory, HookContextFactory hookContextFactory, ScmEventBus eventBus) { this.handler = handler; this.repository = repository; this.lfsBlobStoreFactory = lfsBlobStoreFactory; + this.hookContextFactory = hookContextFactory; + this.eventBus = eventBus; this.context = new GitContext(handler.getDirectory(repository.getId()), repository, storeProvider); } @@ -133,7 +137,7 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider @Override public BranchCommand getBranchCommand() { - return new GitBranchCommand(context, repository, handler.getWorkdirFactory()); + return new GitBranchCommand(context, repository, hookContextFactory, eventBus); } /** @@ -292,4 +296,8 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider private final Repository repository; private final LfsBlobStoreFactory lfsBlobStoreFactory; + + private final HookContextFactory hookContextFactory; + + private final ScmEventBus eventBus; } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceResolver.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceResolver.java index 547c6b25f8..7fc5fb27c4 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceResolver.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceResolver.java @@ -36,9 +36,11 @@ package sonia.scm.repository.spi; import com.google.inject.Inject; import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; +import sonia.scm.event.ScmEventBus; import sonia.scm.plugin.Extension; import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.Repository; +import sonia.scm.repository.api.HookContextFactory; import sonia.scm.web.lfs.LfsBlobStoreFactory; /** @@ -51,12 +53,16 @@ public class GitRepositoryServiceResolver implements RepositoryServiceResolver { private final GitRepositoryHandler handler; private final GitRepositoryConfigStoreProvider storeProvider; private final LfsBlobStoreFactory lfsBlobStoreFactory; + private final HookContextFactory hookContextFactory; + private final ScmEventBus eventBus; @Inject - public GitRepositoryServiceResolver(GitRepositoryHandler handler, GitRepositoryConfigStoreProvider storeProvider, LfsBlobStoreFactory lfsBlobStoreFactory) { + public GitRepositoryServiceResolver(GitRepositoryHandler handler, GitRepositoryConfigStoreProvider storeProvider, LfsBlobStoreFactory lfsBlobStoreFactory, HookContextFactory hookContextFactory, ScmEventBus eventBus) { this.handler = handler; this.storeProvider = storeProvider; this.lfsBlobStoreFactory = lfsBlobStoreFactory; + this.hookContextFactory = hookContextFactory; + this.eventBus = eventBus; } @Override @@ -64,7 +70,7 @@ public class GitRepositoryServiceResolver implements RepositoryServiceResolver { GitRepositoryServiceProvider provider = null; if (GitRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) { - provider = new GitRepositoryServiceProvider(handler, repository, storeProvider, lfsBlobStoreFactory); + provider = new GitRepositoryServiceProvider(handler, repository, storeProvider, lfsBlobStoreFactory, hookContextFactory, eventBus); } return provider; diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java index aa5e641b31..8b097ff586 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java @@ -1,20 +1,35 @@ package sonia.scm.repository.spi; -import org.assertj.core.api.Assertions; -import org.junit.Rule; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.junit.MockitoJUnitRunner; +import sonia.scm.event.ScmEventBus; import sonia.scm.repository.Branch; +import sonia.scm.repository.PostReceiveRepositoryHookEvent; +import sonia.scm.repository.PreReceiveRepositoryHookEvent; import sonia.scm.repository.api.BranchRequest; -import sonia.scm.repository.util.WorkdirProvider; +import sonia.scm.repository.api.HookContext; +import sonia.scm.repository.api.HookContextFactory; import java.io.IOException; import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +@RunWith(MockitoJUnitRunner.class) public class GitBranchCommandTest extends AbstractGitCommandTestBase { - @Rule - public BindTransportProtocolRule transportProtocolRule = new BindTransportProtocolRule(); + @Mock + private HookContextFactory hookContextFactory; + @Mock + private ScmEventBus eventBus; @Test public void shouldCreateBranchWithDefinedSourceBranch() throws IOException { @@ -26,10 +41,10 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase { branchRequest.setParentBranch(source.getName()); branchRequest.setNewBranch("new_branch"); - new GitBranchCommand(context, repository, new SimpleGitWorkdirFactory(new WorkdirProvider())).branch(branchRequest); + new GitBranchCommand(context, repository, hookContextFactory, eventBus).branch(branchRequest); Branch newBranch = findBranch(context, "new_branch"); - Assertions.assertThat(newBranch.getRevision()).isEqualTo(source.getRevision()); + assertThat(newBranch.getRevision()).isEqualTo(source.getRevision()); } private Branch findBranch(GitContext context, String name) throws IOException { @@ -41,17 +56,45 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase { public void shouldCreateBranch() throws IOException { GitContext context = createContext(); - Assertions.assertThat(readBranches(context)).filteredOn(b -> b.getName().equals("new_branch")).isEmpty(); + assertThat(readBranches(context)).filteredOn(b -> b.getName().equals("new_branch")).isEmpty(); BranchRequest branchRequest = new BranchRequest(); branchRequest.setNewBranch("new_branch"); - new GitBranchCommand(context, repository, new SimpleGitWorkdirFactory(new WorkdirProvider())).branch(branchRequest); + new GitBranchCommand(context, repository, hookContextFactory, eventBus).branch(branchRequest); - Assertions.assertThat(readBranches(context)).filteredOn(b -> b.getName().equals("new_branch")).isNotEmpty(); + assertThat(readBranches(context)).filteredOn(b -> b.getName().equals("new_branch")).isNotEmpty(); } private List readBranches(GitContext context) throws IOException { return new GitBranchesCommand(context, repository).getBranches(); } + + @Test + public void shouldPostEvents() { + ArgumentCaptor captor = ArgumentCaptor.forClass(Object.class); + doNothing().when(eventBus).post(captor.capture()); + when(hookContextFactory.createContext(any(), any())).thenAnswer(this::createMockedContext); + + GitContext context = createContext(); + + BranchRequest branchRequest = new BranchRequest(); + branchRequest.setParentBranch("mergeable"); + branchRequest.setNewBranch("new_branch"); + + new GitBranchCommand(context, repository, hookContextFactory, eventBus).branch(branchRequest); + + List events = captor.getAllValues(); + assertThat(events.get(0)).isInstanceOf(PreReceiveRepositoryHookEvent.class); + assertThat(events.get(1)).isInstanceOf(PostReceiveRepositoryHookEvent.class); + + PreReceiveRepositoryHookEvent event = (PreReceiveRepositoryHookEvent) events.get(0); + assertThat(event.getContext().getBranchProvider().getCreatedOrModified()).containsExactly("new_branch"); + } + + private HookContext createMockedContext(InvocationOnMock invocation) { + HookContext mock = mock(HookContext.class); + when(mock.getBranchProvider()).thenReturn(((HookContextProvider) invocation.getArgument(0)).getBranchProvider()); + return mock; + } } From 7cd2cb4ccb9befb7f802acb35086776231f4713b Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Mon, 11 Nov 2019 11:15:56 +0100 Subject: [PATCH 52/60] implement deleteBranchCommand for git --- .../repository/api/BranchCommandBuilder.java | 4 ++++ .../scm/repository/spi/BranchCommand.java | 2 ++ .../scm/repository/spi/GitBranchCommand.java | 16 ++++++++++++++++ .../repository/spi/GitBranchCommandTest.java | 18 ++++++++++++++++++ 4 files changed, 40 insertions(+) diff --git a/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java index 4ec78c9bdc..58e3c5719c 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java @@ -68,6 +68,10 @@ public final class BranchCommandBuilder { return command.branch(request); } + public void delete(String branchName) { + command.delete(branchName); + } + private BranchCommand command; private BranchRequest request = new BranchRequest(); } diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/BranchCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/BranchCommand.java index c659c7fac0..bd644e6d03 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/BranchCommand.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/BranchCommand.java @@ -41,4 +41,6 @@ import sonia.scm.repository.api.BranchRequest; */ public interface BranchCommand { Branch branch(BranchRequest name); + + void delete(String branchName); } 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 fe39006a66..ff3732c836 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 @@ -45,8 +45,11 @@ import sonia.scm.repository.Repository; import sonia.scm.repository.api.BranchRequest; import sonia.scm.repository.util.WorkingCopy; +import java.io.IOException; import java.util.stream.StreamSupport; +import static sonia.scm.ContextEntry.ContextBuilder.entity; + public class GitBranchCommand extends AbstractGitCommand implements BranchCommand { private final GitWorkdirFactory workdirFactory; @@ -73,6 +76,19 @@ public class GitBranchCommand extends AbstractGitCommand implements BranchComman } } + @Override + public void delete(String branchName) { + try (Git gitRepo = new Git(context.open())) { + gitRepo + .branchDelete() + .setBranchNames(branchName) + .setForce(true) + .call(); + } catch (GitAPIException | IOException ex) { + throw new InternalRepositoryException(entity(context.getRepository()), String.format("Could not delete branch: %s", branchName)); + } + } + private void handlePushError(RemoteRefUpdate remoteRefUpdate, BranchRequest request, Repository repository) { if (remoteRefUpdate.getStatus() != RemoteRefUpdate.Status.OK) { // TODO handle failed remote update diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java index aa5e641b31..65745228b7 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java @@ -4,12 +4,15 @@ import org.assertj.core.api.Assertions; import org.junit.Rule; import org.junit.Test; import sonia.scm.repository.Branch; +import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.api.BranchRequest; import sonia.scm.repository.util.WorkdirProvider; import java.io.IOException; import java.util.List; +import static org.junit.jupiter.api.Assertions.assertThrows; + public class GitBranchCommandTest extends AbstractGitCommandTestBase { @@ -51,6 +54,21 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase { Assertions.assertThat(readBranches(context)).filteredOn(b -> b.getName().equals("new_branch")).isNotEmpty(); } + @Test + public void shouldDeleteBranch() throws IOException { + GitContext context = createContext(); + String branchToBeDeleted = "squash"; + new GitBranchCommand(context, repository, new SimpleGitWorkdirFactory(new WorkdirProvider())).delete(branchToBeDeleted); + Assertions.assertThat(readBranches(context)).filteredOn(b -> b.getName().equals(branchToBeDeleted)).isEmpty(); + } + + @Test + public void shouldThrowInternalRepositoryException() { + GitContext context = createContext(); + String branchToBeDeleted = "master"; + assertThrows(InternalRepositoryException.class, () -> new GitBranchCommand(context, repository, new SimpleGitWorkdirFactory(new WorkdirProvider())).delete(branchToBeDeleted)); + } + private List readBranches(GitContext context) throws IOException { return new GitBranchesCommand(context, repository).getBranches(); } From 39d41a56c54fd3cdf1499825ce7a577e2da6d496 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Mon, 11 Nov 2019 11:16:33 +0100 Subject: [PATCH 53/60] implement deleteBranchCommand for hg --- .../scm/repository/spi/HgBranchCommand.java | 33 ++++++++++++++++--- .../repository/spi/HgBranchCommandTest.java | 18 ++++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) 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 e41dbf96da..eca8019329 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 @@ -36,7 +36,9 @@ import com.aragost.javahg.commands.PullCommand; import org.apache.shiro.SecurityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.ContextEntry; import sonia.scm.repository.Branch; +import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Repository; import sonia.scm.repository.api.BranchRequest; import sonia.scm.repository.util.WorkingCopy; @@ -67,23 +69,46 @@ public class HgBranchCommand extends AbstractCommand implements BranchCommand { LOG.debug("Created new branch '{}' in repository {} with changeset {}", request.getNewBranch(), getRepository().getNamespaceAndName(), emptyChangeset.getNode()); - pullNewBranchIntoCentralRepository(request, workingCopy); + pullChangesIntoCentralRepository(workingCopy, request.getNewBranch()); return Branch.normalBranch(request.getNewBranch(), emptyChangeset.getNode()); } } + @Override + public void delete(String branchName) { + try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(getContext(), branchName)) { + User currentUser = SecurityUtils.getSubject().getPrincipals().oneByType(User.class); + + LOG.debug("Closing branch '{}' in repository {}", branchName, getRepository().getNamespaceAndName()); + + com.aragost.javahg.commands.CommitCommand + .on(workingCopy.getWorkingRepository()) + .user(getFormattedUser(currentUser)) + .message(String.format("Close branch: %s", branchName)) + .closeBranch() + .execute(); + pullChangesIntoCentralRepository(workingCopy, branchName); + } catch (Exception ex) { + throw new InternalRepositoryException(ContextEntry.ContextBuilder.entity(getContext().getScmRepository()), String.format("Could not close branch: %s", branchName)); + } + } + + private String getFormattedUser(User currentUser) { + return String.format("%s <%s>", currentUser.getDisplayName(), currentUser.getMail()); + } + private Changeset createNewBranchWithEmptyCommit(BranchRequest request, com.aragost.javahg.Repository repository) { com.aragost.javahg.commands.BranchCommand.on(repository).set(request.getNewBranch()); User currentUser = SecurityUtils.getSubject().getPrincipals().oneByType(User.class); return CommitCommand .on(repository) - .user(String.format("%s <%s>", currentUser.getDisplayName(), currentUser.getMail())) + .user(getFormattedUser(currentUser)) .message("Create new branch " + request.getNewBranch()) .execute(); } - private void pullNewBranchIntoCentralRepository(BranchRequest request, WorkingCopy workingCopy) { + private void pullChangesIntoCentralRepository(WorkingCopy workingCopy, String branch) { try { PullCommand pullCommand = PullCommand.on(workingCopy.getCentralRepository()); workdirFactory.configure(pullCommand); @@ -91,7 +116,7 @@ public class HgBranchCommand extends AbstractCommand implements BranchCommand { } catch (Exception e) { // TODO handle failed update throw new IntegrateChangesFromWorkdirException(getRepository(), - String.format("Could not pull new branch '%s' into central repository", request.getNewBranch()), + String.format("Could not pull changes '%s' into central repository", branch), e); } } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java index 882dab05d0..8ecbb3dd97 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java @@ -6,6 +6,7 @@ import org.junit.Before; import org.junit.Test; import sonia.scm.repository.Branch; import sonia.scm.repository.HgTestUtil; +import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.api.BranchRequest; import sonia.scm.repository.util.WorkdirProvider; import sonia.scm.web.HgRepositoryEnvironmentBuilder; @@ -13,6 +14,7 @@ import sonia.scm.web.HgRepositoryEnvironmentBuilder; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; public class HgBranchCommandTest extends AbstractHgCommandTestBase { @@ -54,6 +56,22 @@ public class HgBranchCommandTest extends AbstractHgCommandTestBase { assertThat(cmdContext.open().changeset(newBranch.getRevision()).getParent1().getBranch()).isEqualTo("test-branch"); } + @Test + public void shouldCloseBranch() { + String branchToBeClosed = "test-branch"; + + new HgBranchCommand(cmdContext, repository, workdirFactory).delete(branchToBeClosed); + assertThat(readBranches()).filteredOn(b -> b.getName().equals("test-branch")).isEmpty(); + } + + @Test + public void shouldThrowInternalRepositoryException() { + String branchToBeClosed = "default"; + + new HgBranchCommand(cmdContext, repository, workdirFactory).delete(branchToBeClosed); + assertThrows(InternalRepositoryException.class, () -> new HgBranchCommand(cmdContext, repository, workdirFactory).delete(branchToBeClosed)); + } + private List readBranches() { return new HgBranchesCommand(cmdContext, repository).getBranches(); } From 782b201e5594801fe8cc6a1118b8ba5338ad2f33 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Mon, 11 Nov 2019 11:21:51 +0100 Subject: [PATCH 54/60] cleanup --- .../test/java/sonia/scm/repository/spi/HgBranchCommandTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java index 8ecbb3dd97..68649d9268 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java @@ -61,7 +61,7 @@ public class HgBranchCommandTest extends AbstractHgCommandTestBase { String branchToBeClosed = "test-branch"; new HgBranchCommand(cmdContext, repository, workdirFactory).delete(branchToBeClosed); - assertThat(readBranches()).filteredOn(b -> b.getName().equals("test-branch")).isEmpty(); + assertThat(readBranches()).filteredOn(b -> b.getName().equals(branchToBeClosed)).isEmpty(); } @Test From 73efd244abeee8c57be9de71dea018bb533f2b64 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Mon, 11 Nov 2019 11:53:28 +0100 Subject: [PATCH 55/60] add BranchCommand to git + hg repositoryServiceProvider --- .../sonia/scm/repository/spi/GitRepositoryServiceProvider.java | 1 + .../sonia/scm/repository/spi/HgRepositoryServiceProvider.java | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java index dc43b8d9b7..800f011597 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java @@ -64,6 +64,7 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider Command.DIFF_RESULT, Command.LOG, Command.TAGS, + Command.BRANCH, Command.BRANCHES, Command.INCOMING, Command.OUTGOING, diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java index c80699add8..6286269d02 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java @@ -61,7 +61,8 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider Command.CAT, Command.DIFF, Command.LOG, - Command.TAGS, + Command.TAGS, + Command.BRANCH, Command.BRANCHES, Command.INCOMING, Command.OUTGOING, From 6a2bf9d32a163401bd487024552911c5c5c281d0 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 11 Nov 2019 15:08:27 +0000 Subject: [PATCH 56/60] Close branch feature/create_branch_without_clone From f7e42db850abf46b3ae82276466ffb1cbe92f904 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Mon, 11 Nov 2019 16:50:39 +0100 Subject: [PATCH 57/60] Rename method --- .../java/sonia/scm/repository/api/BranchCommandBuilder.java | 2 +- .../main/java/sonia/scm/repository/spi/BranchCommand.java | 2 +- .../java/sonia/scm/repository/spi/GitBranchCommand.java | 2 +- .../java/sonia/scm/repository/spi/GitBranchCommandTest.java | 6 +++--- .../main/java/sonia/scm/repository/spi/HgBranchCommand.java | 2 +- .../java/sonia/scm/repository/spi/HgBranchCommandTest.java | 6 +++--- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java index 58e3c5719c..33f4a482ac 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java @@ -69,7 +69,7 @@ public final class BranchCommandBuilder { } public void delete(String branchName) { - command.delete(branchName); + command.deleteOrClose(branchName); } private BranchCommand command; diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/BranchCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/BranchCommand.java index bd644e6d03..d5ba7f8dca 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/BranchCommand.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/BranchCommand.java @@ -42,5 +42,5 @@ import sonia.scm.repository.api.BranchRequest; public interface BranchCommand { Branch branch(BranchRequest name); - void delete(String branchName); + void deleteOrClose(String branchName); } 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 7a3b260c6f..3650f3c54e 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 @@ -84,7 +84,7 @@ public class GitBranchCommand extends AbstractGitCommand implements BranchComman } @Override - public void delete(String branchName) { + public void deleteOrClose(String branchName) { try (Git gitRepo = new Git(context.open())) { RepositoryHookEvent hookEvent = createBranchHookEvent(BranchHookContextProvider.deleteHookEvent(branchName)); eventBus.post(new PreReceiveRepositoryHookEvent(hookEvent)); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java index 408b2cca0c..d9b23f6c7a 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java @@ -73,14 +73,14 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase { public void shouldDeleteBranch() throws IOException { GitContext context = createContext(); String branchToBeDeleted = "squash"; - createCommand().delete(branchToBeDeleted); + createCommand().deleteOrClose(branchToBeDeleted); assertThat(readBranches(context)).filteredOn(b -> b.getName().equals(branchToBeDeleted)).isEmpty(); } @Test public void shouldThrowInternalRepositoryException() { String branchToBeDeleted = "master"; - assertThrows(InternalRepositoryException.class, () -> createCommand().delete(branchToBeDeleted)); + assertThrows(InternalRepositoryException.class, () -> createCommand().deleteOrClose(branchToBeDeleted)); } private GitBranchCommand createCommand() { @@ -118,7 +118,7 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase { doNothing().when(eventBus).post(captor.capture()); when(hookContextFactory.createContext(any(), any())).thenAnswer(this::createMockedContext); - createCommand().delete("squash"); + createCommand().deleteOrClose("squash"); List events = captor.getAllValues(); assertThat(events.get(0)).isInstanceOf(PreReceiveRepositoryHookEvent.class); 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 eca8019329..37e0937aca 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 @@ -76,7 +76,7 @@ public class HgBranchCommand extends AbstractCommand implements BranchCommand { } @Override - public void delete(String branchName) { + public void deleteOrClose(String branchName) { try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(getContext(), branchName)) { User currentUser = SecurityUtils.getSubject().getPrincipals().oneByType(User.class); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java index 68649d9268..4a41e469ac 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java @@ -60,7 +60,7 @@ public class HgBranchCommandTest extends AbstractHgCommandTestBase { public void shouldCloseBranch() { String branchToBeClosed = "test-branch"; - new HgBranchCommand(cmdContext, repository, workdirFactory).delete(branchToBeClosed); + new HgBranchCommand(cmdContext, repository, workdirFactory).deleteOrClose(branchToBeClosed); assertThat(readBranches()).filteredOn(b -> b.getName().equals(branchToBeClosed)).isEmpty(); } @@ -68,8 +68,8 @@ public class HgBranchCommandTest extends AbstractHgCommandTestBase { public void shouldThrowInternalRepositoryException() { String branchToBeClosed = "default"; - new HgBranchCommand(cmdContext, repository, workdirFactory).delete(branchToBeClosed); - assertThrows(InternalRepositoryException.class, () -> new HgBranchCommand(cmdContext, repository, workdirFactory).delete(branchToBeClosed)); + new HgBranchCommand(cmdContext, repository, workdirFactory).deleteOrClose(branchToBeClosed); + assertThrows(InternalRepositoryException.class, () -> new HgBranchCommand(cmdContext, repository, workdirFactory).deleteOrClose(branchToBeClosed)); } private List readBranches() { From 8ed874c10386e3ab446e3993a7cab371a0521b1e Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Mon, 11 Nov 2019 16:51:46 +0100 Subject: [PATCH 58/60] Use dedicated exception for default branch deletion --- .../CannotDeleteDefaultBranchException.java | 19 +++++++++++++++++++ .../scm/repository/spi/GitBranchCommand.java | 3 +++ .../repository/spi/GitBranchCommandTest.java | 5 ++--- .../main/resources/locales/de/plugins.json | 4 ++++ .../main/resources/locales/en/plugins.json | 4 ++++ 5 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/repository/spi/CannotDeleteDefaultBranchException.java diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/CannotDeleteDefaultBranchException.java b/scm-core/src/main/java/sonia/scm/repository/spi/CannotDeleteDefaultBranchException.java new file mode 100644 index 0000000000..0053d5384a --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/spi/CannotDeleteDefaultBranchException.java @@ -0,0 +1,19 @@ +package sonia.scm.repository.spi; + +import sonia.scm.ContextEntry; +import sonia.scm.ExceptionWithContext; +import sonia.scm.repository.Repository; + +public class CannotDeleteDefaultBranchException extends ExceptionWithContext { + + public static final String CODE = "78RhWxTIw1"; + + public CannotDeleteDefaultBranchException(Repository repository, String branchName) { + super(ContextEntry.ContextBuilder.entity("Branch", branchName).in(repository).build(), "default branch cannot be deleted"); + } + + @Override + public String getCode() { + return CODE; + } +} 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 3650f3c54e..fd3c25cf39 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 @@ -33,6 +33,7 @@ package sonia.scm.repository.spi; import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.CannotDeleteCurrentBranchException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.Ref; import sonia.scm.event.ScmEventBus; @@ -94,6 +95,8 @@ public class GitBranchCommand extends AbstractGitCommand implements BranchComman .setForce(true) .call(); eventBus.post(new PostReceiveRepositoryHookEvent(hookEvent)); + } catch (CannotDeleteCurrentBranchException e) { + throw new CannotDeleteDefaultBranchException(context.getRepository(), branchName); } catch (GitAPIException | IOException ex) { throw new InternalRepositoryException(entity(context.getRepository()), String.format("Could not delete branch: %s", branchName)); } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java index d9b23f6c7a..8bfb0d16ad 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java @@ -8,7 +8,6 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.event.ScmEventBus; import sonia.scm.repository.Branch; -import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.PostReceiveRepositoryHookEvent; import sonia.scm.repository.PreReceiveRepositoryHookEvent; import sonia.scm.repository.api.BranchRequest; @@ -78,9 +77,9 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase { } @Test - public void shouldThrowInternalRepositoryException() { + public void shouldThrowExceptionWhenDeletingDefaultBranch() { String branchToBeDeleted = "master"; - assertThrows(InternalRepositoryException.class, () -> createCommand().deleteOrClose(branchToBeDeleted)); + assertThrows(CannotDeleteDefaultBranchException.class, () -> createCommand().deleteOrClose(branchToBeDeleted)); } private GitBranchCommand createCommand() { diff --git a/scm-webapp/src/main/resources/locales/de/plugins.json b/scm-webapp/src/main/resources/locales/de/plugins.json index fe97d296c4..9d25485fa9 100644 --- a/scm-webapp/src/main/resources/locales/de/plugins.json +++ b/scm-webapp/src/main/resources/locales/de/plugins.json @@ -187,6 +187,10 @@ "6eRhF9gU41": { "displayName": "Nicht unterstützte Merge-Strategie", "description": "Die gewählte Merge-Strategie wird von dem Repository nicht unterstützt." + }, + "78RhWxTIw1": { + "displayName": "Der Default-Branch kann nicht gelöscht werden", + "description": "Der Default-Branch kann nicht gelöscht werden. Bitte wählen Sie zuerst einen neuen Default-Branch." } }, "namespaceStrategies": { diff --git a/scm-webapp/src/main/resources/locales/en/plugins.json b/scm-webapp/src/main/resources/locales/en/plugins.json index 27171374c9..cde0cbb9d4 100644 --- a/scm-webapp/src/main/resources/locales/en/plugins.json +++ b/scm-webapp/src/main/resources/locales/en/plugins.json @@ -187,6 +187,10 @@ "6eRhF9gU41": { "displayName": "Merge strategy not supported", "description": "The selected merge strategy is not supported by the repository." + }, + "78RhWxTIw1": { + "displayName": "Default branch cannot be deleted", + "description": "The default branch of a repository cannot be deleted. Please select another default branch first." } }, "namespaceStrategies": { From b14fb1d8521f82329dcbbe05e92707bbb24f7919 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 12 Nov 2019 07:48:20 +0000 Subject: [PATCH 59/60] Close branch bugfix/merge_with_conflict From 875b33d5c8e048ac5a11ad456c59bf7b29c05cde Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Tue, 12 Nov 2019 07:50:07 +0000 Subject: [PATCH 60/60] Close branch feature/delete_source_branch