From c2e30a582fde9f193db9776bbcdf71a5ea689091 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Fri, 8 Nov 2019 16:44:04 +0100 Subject: [PATCH 01/11] 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 02/11] 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 03/11] 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 04/11] 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 05/11] 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 06/11] 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 07/11] 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 08/11] 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 09/11] 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 10/11] 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 11/11] Close branch feature/delete_source_branch