From 1695d13a984a336175a76e0a1dc3b477466ebe19 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Mon, 11 Nov 2019 10:30:11 +0100 Subject: [PATCH 1/2] 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 6a2bf9d32a163401bd487024552911c5c5c281d0 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 11 Nov 2019 15:08:27 +0000 Subject: [PATCH 2/2] Close branch feature/create_branch_without_clone