Create new branches without clone of git repository

This commit is contained in:
Rene Pfeuffer
2019-11-11 10:30:11 +01:00
parent 73cdab4ddc
commit 1695d13a98
4 changed files with 133 additions and 37 deletions

View File

@@ -35,49 +35,88 @@ package sonia.scm.repository.spi;
import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.transport.PushResult; import sonia.scm.event.ScmEventBus;
import org.eclipse.jgit.transport.RemoteRefUpdate;
import sonia.scm.repository.Branch; import sonia.scm.repository.Branch;
import sonia.scm.repository.GitUtil; import sonia.scm.repository.GitUtil;
import sonia.scm.repository.GitWorkdirFactory;
import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
import sonia.scm.repository.PreReceiveRepositoryHookEvent;
import sonia.scm.repository.Repository; 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.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 { 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); super(context, repository);
this.workdirFactory = workdirFactory; this.hookContextFactory = hookContextFactory;
this.eventBus = eventBus;
} }
@Override @Override
public Branch branch(BranchRequest request) { public Branch branch(BranchRequest request) {
try (WorkingCopy<org.eclipse.jgit.lib.Repository, org.eclipse.jgit.lib.Repository> workingCopy = workdirFactory.createWorkingCopy(context, request.getParentBranch())) { try (Git git = new Git(context.open())) {
Git clone = new Git(workingCopy.getWorkingRepository()); RepositoryHookEvent hookEvent = createHookEvent(request);
Ref ref = clone.branchCreate().setName(request.getNewBranch()).call(); eventBus.post(new PreReceiveRepositoryHookEvent(hookEvent));
Iterable<PushResult> call = clone.push().add(request.getNewBranch()).call(); Ref ref = git.branchCreate().setStartPoint(request.getParentBranch()).setName(request.getNewBranch()).call();
StreamSupport.stream(call.spliterator(), false) eventBus.post(new PostReceiveRepositoryHookEvent(hookEvent));
.flatMap(pushResult -> pushResult.getRemoteUpdates().stream())
.filter(remoteRefUpdate -> remoteRefUpdate.getStatus() != RemoteRefUpdate.Status.OK)
.findFirst()
.ifPresent(r -> this.handlePushError(r, request, context.getRepository()));
return Branch.normalBranch(request.getNewBranch(), GitUtil.getId(ref.getObjectId())); 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); throw new InternalRepositoryException(repository, "could not create branch " + request.getNewBranch(), ex);
} }
} }
private void handlePushError(RemoteRefUpdate remoteRefUpdate, BranchRequest request, Repository repository) { private RepositoryHookEvent createHookEvent(BranchRequest request) {
if (remoteRefUpdate.getStatus() != RemoteRefUpdate.Status.OK) { HookContext context = hookContextFactory.createContext(new BranchHookContextProvider(request), this.context.getRepository());
// TODO handle failed remote update return new RepositoryHookEvent(context, this.context.getRepository(), RepositoryHookType.PRE_RECEIVE);
throw new IntegrateChangesFromWorkdirException(repository, }
String.format("Could not push new branch '%s' into central repository", request.getNewBranch()));
private static class BranchHookContextProvider extends HookContextProvider {
private final BranchRequest request;
public BranchHookContextProvider(BranchRequest request) {
this.request = request;
}
@Override
public Set<HookFeature> getSupportedFeatures() {
return singleton(HookFeature.BRANCH_PROVIDER);
}
@Override
public HookBranchProvider getBranchProvider() {
return new HookBranchProvider() {
@Override
public List<String> getCreatedOrModified() {
return singletonList(request.getNewBranch());
}
@Override
public List<String> getDeletedOrClosed() {
return emptyList();
}
};
}
@Override
public HookChangesetProvider getChangesetProvider() {
return request -> new HookChangesetResponse(emptyList());
} }
} }
} }

View File

@@ -35,10 +35,12 @@ package sonia.scm.repository.spi;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.Feature; import sonia.scm.repository.Feature;
import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.GitRepositoryHandler;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
import sonia.scm.repository.api.Command; import sonia.scm.repository.api.Command;
import sonia.scm.repository.api.HookContextFactory;
import sonia.scm.web.lfs.LfsBlobStoreFactory; import sonia.scm.web.lfs.LfsBlobStoreFactory;
import java.io.IOException; import java.io.IOException;
@@ -77,10 +79,12 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
//~--- constructors --------------------------------------------------------- //~--- 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.handler = handler;
this.repository = repository; this.repository = repository;
this.lfsBlobStoreFactory = lfsBlobStoreFactory; this.lfsBlobStoreFactory = lfsBlobStoreFactory;
this.hookContextFactory = hookContextFactory;
this.eventBus = eventBus;
this.context = new GitContext(handler.getDirectory(repository.getId()), repository, storeProvider); this.context = new GitContext(handler.getDirectory(repository.getId()), repository, storeProvider);
} }
@@ -133,7 +137,7 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
@Override @Override
public BranchCommand getBranchCommand() 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 Repository repository;
private final LfsBlobStoreFactory lfsBlobStoreFactory; private final LfsBlobStoreFactory lfsBlobStoreFactory;
private final HookContextFactory hookContextFactory;
private final ScmEventBus eventBus;
} }

View File

@@ -36,9 +36,11 @@ package sonia.scm.repository.spi;
import com.google.inject.Inject; import com.google.inject.Inject;
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
import sonia.scm.event.ScmEventBus;
import sonia.scm.plugin.Extension; import sonia.scm.plugin.Extension;
import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.GitRepositoryHandler;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
import sonia.scm.repository.api.HookContextFactory;
import sonia.scm.web.lfs.LfsBlobStoreFactory; import sonia.scm.web.lfs.LfsBlobStoreFactory;
/** /**
@@ -51,12 +53,16 @@ public class GitRepositoryServiceResolver implements RepositoryServiceResolver {
private final GitRepositoryHandler handler; private final GitRepositoryHandler handler;
private final GitRepositoryConfigStoreProvider storeProvider; private final GitRepositoryConfigStoreProvider storeProvider;
private final LfsBlobStoreFactory lfsBlobStoreFactory; private final LfsBlobStoreFactory lfsBlobStoreFactory;
private final HookContextFactory hookContextFactory;
private final ScmEventBus eventBus;
@Inject @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.handler = handler;
this.storeProvider = storeProvider; this.storeProvider = storeProvider;
this.lfsBlobStoreFactory = lfsBlobStoreFactory; this.lfsBlobStoreFactory = lfsBlobStoreFactory;
this.hookContextFactory = hookContextFactory;
this.eventBus = eventBus;
} }
@Override @Override
@@ -64,7 +70,7 @@ public class GitRepositoryServiceResolver implements RepositoryServiceResolver {
GitRepositoryServiceProvider provider = null; GitRepositoryServiceProvider provider = null;
if (GitRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) { 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; return provider;

View File

@@ -1,20 +1,35 @@
package sonia.scm.repository.spi; package sonia.scm.repository.spi;
import org.assertj.core.api.Assertions;
import org.junit.Rule;
import org.junit.Test; 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.Branch;
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
import sonia.scm.repository.PreReceiveRepositoryHookEvent;
import sonia.scm.repository.api.BranchRequest; 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.io.IOException;
import java.util.List; 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 { public class GitBranchCommandTest extends AbstractGitCommandTestBase {
@Rule @Mock
public BindTransportProtocolRule transportProtocolRule = new BindTransportProtocolRule(); private HookContextFactory hookContextFactory;
@Mock
private ScmEventBus eventBus;
@Test @Test
public void shouldCreateBranchWithDefinedSourceBranch() throws IOException { public void shouldCreateBranchWithDefinedSourceBranch() throws IOException {
@@ -26,10 +41,10 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase {
branchRequest.setParentBranch(source.getName()); branchRequest.setParentBranch(source.getName());
branchRequest.setNewBranch("new_branch"); 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"); 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 { private Branch findBranch(GitContext context, String name) throws IOException {
@@ -41,17 +56,45 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase {
public void shouldCreateBranch() throws IOException { public void shouldCreateBranch() throws IOException {
GitContext context = createContext(); 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 branchRequest = new BranchRequest();
branchRequest.setNewBranch("new_branch"); 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<Branch> readBranches(GitContext context) throws IOException { private List<Branch> readBranches(GitContext context) throws IOException {
return new GitBranchesCommand(context, repository).getBranches(); return new GitBranchesCommand(context, repository).getBranches();
} }
@Test
public void shouldPostEvents() {
ArgumentCaptor<Object> 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<Object> 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;
}
} }