mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-03-05 20:00:55 +01:00
Merged in feature/delete_source_branch (pull request #350)
Feature/delete source branch
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -41,4 +41,6 @@ import sonia.scm.repository.api.BranchRequest;
|
||||
*/
|
||||
public interface BranchCommand {
|
||||
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,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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
|
||||
Command.DIFF_RESULT,
|
||||
Command.LOG,
|
||||
Command.TAGS,
|
||||
Command.BRANCH,
|
||||
Command.BRANCHES,
|
||||
Command.INCOMING,
|
||||
Command.OUTGOING,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user