Merged in feature/delete_source_branch (pull request #350)

Feature/delete source branch
This commit is contained in:
Rene Pfeuffer
2019-11-12 07:50:07 +00:00
11 changed files with 164 additions and 20 deletions

View File

@@ -68,6 +68,10 @@ public final class BranchCommandBuilder {
return command.branch(request);
}
public void delete(String branchName) {
command.deleteOrClose(branchName);
}
private BranchCommand command;
private BranchRequest request = new BranchRequest();
}

View File

@@ -41,4 +41,6 @@ import sonia.scm.repository.api.BranchRequest;
*/
public interface BranchCommand {
Branch branch(BranchRequest name);
void deleteOrClose(String branchName);
}

View File

@@ -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;
}
}

View File

@@ -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;
@@ -57,6 +58,7 @@ 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 {
@@ -72,7 +74,7 @@ public class GitBranchCommand extends AbstractGitCommand implements BranchComman
@Override
public Branch branch(BranchRequest request) {
try (Git git = new Git(context.open())) {
RepositoryHookEvent hookEvent = createHookEvent(request);
RepositoryHookEvent hookEvent = createBranchHookEvent(BranchHookContextProvider.createHookEvent(request.getNewBranch()));
eventBus.post(new PreReceiveRepositoryHookEvent(hookEvent));
Ref ref = git.branchCreate().setStartPoint(request.getParentBranch()).setName(request.getNewBranch()).call();
eventBus.post(new PostReceiveRepositoryHookEvent(hookEvent));
@@ -82,16 +84,44 @@ public class GitBranchCommand extends AbstractGitCommand implements BranchComman
}
}
private RepositoryHookEvent createHookEvent(BranchRequest request) {
HookContext context = hookContextFactory.createContext(new BranchHookContextProvider(request), this.context.getRepository());
@Override
public void deleteOrClose(String branchName) {
try (Git gitRepo = new Git(context.open())) {
RepositoryHookEvent hookEvent = createBranchHookEvent(BranchHookContextProvider.deleteHookEvent(branchName));
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 BranchRequest request;
private final List<String> newBranches;
private final List<String> deletedBranches;
public BranchHookContextProvider(BranchRequest request) {
this.request = request;
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
@@ -104,19 +134,19 @@ public class GitBranchCommand extends AbstractGitCommand implements BranchComman
return new HookBranchProvider() {
@Override
public List<String> getCreatedOrModified() {
return singletonList(request.getNewBranch());
return newBranches;
}
@Override
public List<String> getDeletedOrClosed() {
return emptyList();
return deletedBranches;
}
};
}
@Override
public HookChangesetProvider getChangesetProvider() {
return request -> new HookChangesetResponse(emptyList());
return r -> new HookChangesetResponse(emptyList());
}
}
}

View File

@@ -66,6 +66,7 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
Command.DIFF_RESULT,
Command.LOG,
Command.TAGS,
Command.BRANCH,
Command.BRANCHES,
Command.INCOMING,
Command.OUTGOING,

View File

@@ -18,11 +18,13 @@ import java.io.IOException;
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 {
@@ -41,7 +43,7 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase {
branchRequest.setParentBranch(source.getName());
branchRequest.setNewBranch("new_branch");
new GitBranchCommand(context, repository, hookContextFactory, eventBus).branch(branchRequest);
createCommand().branch(branchRequest);
Branch newBranch = findBranch(context, "new_branch");
assertThat(newBranch.getRevision()).isEqualTo(source.getRevision());
@@ -61,28 +63,44 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase {
BranchRequest branchRequest = new BranchRequest();
branchRequest.setNewBranch("new_branch");
new GitBranchCommand(context, repository, hookContextFactory, eventBus).branch(branchRequest);
createCommand().branch(branchRequest);
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 {
return new GitBranchesCommand(context, repository).getBranches();
}
@Test
public void shouldPostEvents() {
public void shouldPostCreateEvents() {
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);
createCommand().branch(branchRequest);
List<Object> events = captor.getAllValues();
assertThat(events.get(0)).isInstanceOf(PreReceiveRepositoryHookEvent.class);
@@ -90,6 +108,24 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase {
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) {

View File

@@ -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 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) {
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<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 {
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);
}
}

View File

@@ -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,

View File

@@ -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).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() {
return new HgBranchesCommand(cmdContext, repository).getBranches();
}

View File

@@ -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": {

View File

@@ -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": {