mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-14 09:25:43 +01:00
Merge with 2.0.0-m3
This commit is contained in:
@@ -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<String> filesWithConflict;
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -68,6 +68,10 @@ public final class BranchCommandBuilder {
|
|||||||
return command.branch(request);
|
return command.branch(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void delete(String branchName) {
|
||||||
|
command.deleteOrClose(branchName);
|
||||||
|
}
|
||||||
|
|
||||||
private BranchCommand command;
|
private BranchCommand command;
|
||||||
private BranchRequest request = new BranchRequest();
|
private BranchRequest request = new BranchRequest();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,4 +41,6 @@ import sonia.scm.repository.api.BranchRequest;
|
|||||||
*/
|
*/
|
||||||
public interface BranchCommand {
|
public interface BranchCommand {
|
||||||
Branch branch(BranchRequest name);
|
Branch branch(BranchRequest name);
|
||||||
|
|
||||||
|
void deleteOrClose(String branchName);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,51 +33,120 @@
|
|||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
|
import org.eclipse.jgit.api.errors.CannotDeleteCurrentBranchException;
|
||||||
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;
|
||||||
|
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||||
|
|
||||||
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 = createBranchHookEvent(BranchHookContextProvider.createHookEvent(request.getNewBranch()));
|
||||||
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) {
|
@Override
|
||||||
if (remoteRefUpdate.getStatus() != RemoteRefUpdate.Status.OK) {
|
public void deleteOrClose(String branchName) {
|
||||||
// TODO handle failed remote update
|
try (Git gitRepo = new Git(context.open())) {
|
||||||
throw new IntegrateChangesFromWorkdirException(repository,
|
RepositoryHookEvent hookEvent = createBranchHookEvent(BranchHookContextProvider.deleteHookEvent(branchName));
|
||||||
String.format("Could not push new branch '%s' into central repository", request.getNewBranch()));
|
eventBus.post(new PreReceiveRepositoryHookEvent(hookEvent));
|
||||||
|
gitRepo
|
||||||
|
.branchDelete()
|
||||||
|
.setBranchNames(branchName)
|
||||||
|
.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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private RepositoryHookEvent createBranchHookEvent(BranchHookContextProvider hookEvent) {
|
||||||
|
HookContext context = hookContextFactory.createContext(hookEvent, this.context.getRepository());
|
||||||
|
return new RepositoryHookEvent(context, this.context.getRepository(), RepositoryHookType.PRE_RECEIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class BranchHookContextProvider extends HookContextProvider {
|
||||||
|
private final List<String> newBranches;
|
||||||
|
private final List<String> deletedBranches;
|
||||||
|
|
||||||
|
private BranchHookContextProvider(List<String> newBranches, List<String> deletedBranches) {
|
||||||
|
this.newBranches = newBranches;
|
||||||
|
this.deletedBranches = deletedBranches;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BranchHookContextProvider createHookEvent(String newBranch) {
|
||||||
|
return new BranchHookContextProvider(singletonList(newBranch), emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
static BranchHookContextProvider deleteHookEvent(String deletedBranch) {
|
||||||
|
return new BranchHookContextProvider(emptyList(), singletonList(deletedBranch));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<HookFeature> getSupportedFeatures() {
|
||||||
|
return singleton(HookFeature.BRANCH_PROVIDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HookBranchProvider getBranchProvider() {
|
||||||
|
return new HookBranchProvider() {
|
||||||
|
@Override
|
||||||
|
public List<String> getCreatedOrModified() {
|
||||||
|
return newBranches;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getDeletedOrClosed() {
|
||||||
|
return deletedBranches;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HookChangesetProvider getChangesetProvider() {
|
||||||
|
return r -> new HookChangesetResponse(emptyList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -64,6 +66,7 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
|
|||||||
Command.DIFF_RESULT,
|
Command.DIFF_RESULT,
|
||||||
Command.LOG,
|
Command.LOG,
|
||||||
Command.TAGS,
|
Command.TAGS,
|
||||||
|
Command.BRANCH,
|
||||||
Command.BRANCHES,
|
Command.BRANCHES,
|
||||||
Command.INCOMING,
|
Command.INCOMING,
|
||||||
Command.OUTGOING,
|
Command.OUTGOING,
|
||||||
@@ -77,10 +80,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 +138,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 +297,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -1,20 +1,37 @@
|
|||||||
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.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
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 +43,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);
|
createCommand().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 +58,79 @@ 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);
|
createCommand().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();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldDeleteBranch() throws IOException {
|
||||||
|
GitContext context = createContext();
|
||||||
|
String branchToBeDeleted = "squash";
|
||||||
|
createCommand().deleteOrClose(branchToBeDeleted);
|
||||||
|
assertThat(readBranches(context)).filteredOn(b -> b.getName().equals(branchToBeDeleted)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldThrowExceptionWhenDeletingDefaultBranch() {
|
||||||
|
String branchToBeDeleted = "master";
|
||||||
|
assertThrows(CannotDeleteDefaultBranchException.class, () -> createCommand().deleteOrClose(branchToBeDeleted));
|
||||||
|
}
|
||||||
|
|
||||||
|
private GitBranchCommand createCommand() {
|
||||||
|
return new GitBranchCommand(createContext(), repository, hookContextFactory, eventBus);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 shouldPostCreateEvents() {
|
||||||
|
ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
|
||||||
|
doNothing().when(eventBus).post(captor.capture());
|
||||||
|
when(hookContextFactory.createContext(any(), any())).thenAnswer(this::createMockedContext);
|
||||||
|
|
||||||
|
BranchRequest branchRequest = new BranchRequest();
|
||||||
|
branchRequest.setParentBranch("mergeable");
|
||||||
|
branchRequest.setNewBranch("new_branch");
|
||||||
|
|
||||||
|
createCommand().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");
|
||||||
|
assertThat(event.getContext().getBranchProvider().getDeletedOrClosed()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldPostDeleteEvents() {
|
||||||
|
ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
|
||||||
|
doNothing().when(eventBus).post(captor.capture());
|
||||||
|
when(hookContextFactory.createContext(any(), any())).thenAnswer(this::createMockedContext);
|
||||||
|
|
||||||
|
createCommand().deleteOrClose("squash");
|
||||||
|
|
||||||
|
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().getDeletedOrClosed()).containsExactly("squash");
|
||||||
|
assertThat(event.getContext().getBranchProvider().getCreatedOrModified()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private HookContext createMockedContext(InvocationOnMock invocation) {
|
||||||
|
HookContext mock = mock(HookContext.class);
|
||||||
|
when(mock.getBranchProvider()).thenReturn(((HookContextProvider) invocation.getArgument(0)).getBranchProvider());
|
||||||
|
return mock;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,9 @@ import com.aragost.javahg.commands.PullCommand;
|
|||||||
import org.apache.shiro.SecurityUtils;
|
import org.apache.shiro.SecurityUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.ContextEntry;
|
||||||
import sonia.scm.repository.Branch;
|
import sonia.scm.repository.Branch;
|
||||||
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.api.BranchRequest;
|
import sonia.scm.repository.api.BranchRequest;
|
||||||
import sonia.scm.repository.util.WorkingCopy;
|
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 {}",
|
LOG.debug("Created new branch '{}' in repository {} with changeset {}",
|
||||||
request.getNewBranch(), getRepository().getNamespaceAndName(), emptyChangeset.getNode());
|
request.getNewBranch(), getRepository().getNamespaceAndName(), emptyChangeset.getNode());
|
||||||
|
|
||||||
pullNewBranchIntoCentralRepository(request, workingCopy);
|
pullChangesIntoCentralRepository(workingCopy, request.getNewBranch());
|
||||||
|
|
||||||
return Branch.normalBranch(request.getNewBranch(), emptyChangeset.getNode());
|
return Branch.normalBranch(request.getNewBranch(), emptyChangeset.getNode());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteOrClose(String branchName) {
|
||||||
|
try (WorkingCopy<com.aragost.javahg.Repository, com.aragost.javahg.Repository> 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) {
|
private Changeset createNewBranchWithEmptyCommit(BranchRequest request, com.aragost.javahg.Repository repository) {
|
||||||
com.aragost.javahg.commands.BranchCommand.on(repository).set(request.getNewBranch());
|
com.aragost.javahg.commands.BranchCommand.on(repository).set(request.getNewBranch());
|
||||||
User currentUser = SecurityUtils.getSubject().getPrincipals().oneByType(User.class);
|
User currentUser = SecurityUtils.getSubject().getPrincipals().oneByType(User.class);
|
||||||
return CommitCommand
|
return CommitCommand
|
||||||
.on(repository)
|
.on(repository)
|
||||||
.user(String.format("%s <%s>", currentUser.getDisplayName(), currentUser.getMail()))
|
.user(getFormattedUser(currentUser))
|
||||||
.message("Create new branch " + request.getNewBranch())
|
.message("Create new branch " + request.getNewBranch())
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pullNewBranchIntoCentralRepository(BranchRequest request, WorkingCopy<com.aragost.javahg.Repository, com.aragost.javahg.Repository> workingCopy) {
|
private void pullChangesIntoCentralRepository(WorkingCopy<com.aragost.javahg.Repository, com.aragost.javahg.Repository> workingCopy, String branch) {
|
||||||
try {
|
try {
|
||||||
PullCommand pullCommand = PullCommand.on(workingCopy.getCentralRepository());
|
PullCommand pullCommand = PullCommand.on(workingCopy.getCentralRepository());
|
||||||
workdirFactory.configure(pullCommand);
|
workdirFactory.configure(pullCommand);
|
||||||
@@ -91,7 +116,7 @@ public class HgBranchCommand extends AbstractCommand implements BranchCommand {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// TODO handle failed update
|
// TODO handle failed update
|
||||||
throw new IntegrateChangesFromWorkdirException(getRepository(),
|
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);
|
e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,8 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider
|
|||||||
Command.CAT,
|
Command.CAT,
|
||||||
Command.DIFF,
|
Command.DIFF,
|
||||||
Command.LOG,
|
Command.LOG,
|
||||||
Command.TAGS,
|
Command.TAGS,
|
||||||
|
Command.BRANCH,
|
||||||
Command.BRANCHES,
|
Command.BRANCHES,
|
||||||
Command.INCOMING,
|
Command.INCOMING,
|
||||||
Command.OUTGOING,
|
Command.OUTGOING,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import org.junit.Before;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import sonia.scm.repository.Branch;
|
import sonia.scm.repository.Branch;
|
||||||
import sonia.scm.repository.HgTestUtil;
|
import sonia.scm.repository.HgTestUtil;
|
||||||
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
import sonia.scm.repository.api.BranchRequest;
|
import sonia.scm.repository.api.BranchRequest;
|
||||||
import sonia.scm.repository.util.WorkdirProvider;
|
import sonia.scm.repository.util.WorkdirProvider;
|
||||||
import sonia.scm.web.HgRepositoryEnvironmentBuilder;
|
import sonia.scm.web.HgRepositoryEnvironmentBuilder;
|
||||||
@@ -13,6 +14,7 @@ import sonia.scm.web.HgRepositoryEnvironmentBuilder;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
public class HgBranchCommandTest extends AbstractHgCommandTestBase {
|
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");
|
assertThat(cmdContext.open().changeset(newBranch.getRevision()).getParent1().getBranch()).isEqualTo("test-branch");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCloseBranch() {
|
||||||
|
String branchToBeClosed = "test-branch";
|
||||||
|
|
||||||
|
new HgBranchCommand(cmdContext, repository, workdirFactory).deleteOrClose(branchToBeClosed);
|
||||||
|
assertThat(readBranches()).filteredOn(b -> b.getName().equals(branchToBeClosed)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldThrowInternalRepositoryException() {
|
||||||
|
String branchToBeClosed = "default";
|
||||||
|
|
||||||
|
new HgBranchCommand(cmdContext, repository, workdirFactory).deleteOrClose(branchToBeClosed);
|
||||||
|
assertThrows(InternalRepositoryException.class, () -> new HgBranchCommand(cmdContext, repository, workdirFactory).deleteOrClose(branchToBeClosed));
|
||||||
|
}
|
||||||
|
|
||||||
private List<Branch> readBranches() {
|
private List<Branch> readBranches() {
|
||||||
return new HgBranchesCommand(cmdContext, repository).getBranches();
|
return new HgBranchesCommand(cmdContext, repository).getBranches();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,8 +47,6 @@ public class MapperModule extends AbstractModule {
|
|||||||
bind(ScmViolationExceptionToErrorDtoMapper.class).to(Mappers.getMapper(ScmViolationExceptionToErrorDtoMapper.class).getClass());
|
bind(ScmViolationExceptionToErrorDtoMapper.class).to(Mappers.getMapper(ScmViolationExceptionToErrorDtoMapper.class).getClass());
|
||||||
bind(ExceptionWithContextToErrorDtoMapper.class).to(Mappers.getMapper(ExceptionWithContextToErrorDtoMapper.class).getClass());
|
bind(ExceptionWithContextToErrorDtoMapper.class).to(Mappers.getMapper(ExceptionWithContextToErrorDtoMapper.class).getClass());
|
||||||
|
|
||||||
bind(MergeResultToDtoMapper.class).to(Mappers.getMapper(MergeResultToDtoMapper.class).getClass());
|
|
||||||
|
|
||||||
// no mapstruct required
|
// no mapstruct required
|
||||||
bind(MeDtoFactory.class);
|
bind(MeDtoFactory.class);
|
||||||
bind(UIPluginDtoMapper.class);
|
bind(UIPluginDtoMapper.class);
|
||||||
|
|||||||
@@ -187,6 +187,10 @@
|
|||||||
"6eRhF9gU41": {
|
"6eRhF9gU41": {
|
||||||
"displayName": "Nicht unterstützte Merge-Strategie",
|
"displayName": "Nicht unterstützte Merge-Strategie",
|
||||||
"description": "Die gewählte Merge-Strategie wird von dem Repository nicht unterstützt."
|
"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": {
|
"namespaceStrategies": {
|
||||||
|
|||||||
@@ -187,6 +187,10 @@
|
|||||||
"6eRhF9gU41": {
|
"6eRhF9gU41": {
|
||||||
"displayName": "Merge strategy not supported",
|
"displayName": "Merge strategy not supported",
|
||||||
"description": "The selected merge strategy is not supported by the repository."
|
"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": {
|
"namespaceStrategies": {
|
||||||
|
|||||||
Reference in New Issue
Block a user