mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-02-22 06:26:56 +01:00
add repository import via dump for subversion
Subversion repositories can be imported from dump files (backups). Just upload your dump file and check if your file is compressed on the import form for Subversion. The repository will be imported synchronously and you will be redirected to the new repository after the import is finished.
This commit is contained in:
@@ -24,6 +24,8 @@
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -39,7 +41,6 @@ import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import sonia.scm.HandlerEventType;
|
||||
import sonia.scm.PageResult;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
@@ -58,17 +59,28 @@ import sonia.scm.repository.api.ImportFailedException;
|
||||
import sonia.scm.repository.api.PullCommandBuilder;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||
import sonia.scm.repository.api.UnbundleCommandBuilder;
|
||||
import sonia.scm.repository.api.UnbundleResponse;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.web.RestDispatcher;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@@ -86,6 +98,7 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyMap;
|
||||
import static org.mockito.ArgumentMatchers.anyObject;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
@@ -565,6 +578,101 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
||||
assertThrows(ImportFailedException.class, () -> repositoryConsumer.accept(repository));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldImportRepositoryFromBundle() throws IOException, URISyntaxException {
|
||||
when(manager.getHandler("svn")).thenReturn(repositoryHandler);
|
||||
when(repositoryHandler.getType()).thenReturn(new RepositoryType("svn", "svn", ImmutableSet.of(Command.UNBUNDLE)));
|
||||
when(repositoryManager.create(any(), any())).thenReturn(RepositoryTestData.createHeartOfGold());
|
||||
|
||||
RepositoryDto repositoryDto = new RepositoryDto();
|
||||
repositoryDto.setName("HeartOfGold");
|
||||
repositoryDto.setNamespace("hitchhiker");
|
||||
repositoryDto.setType("svn");
|
||||
|
||||
URL dumpUrl = Resources.getResource("sonia/scm/api/v2/svn.dump");
|
||||
byte[] svnDump = Resources.toByteArray(dumpUrl);
|
||||
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/svn/bundle");
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
multipartRequest(request, Collections.singletonMap("bundle", new ByteArrayInputStream(svnDump)), repositoryDto);
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertEquals(HttpServletResponse.SC_CREATED, response.getStatus());
|
||||
assertEquals("/v2/repositories/hitchhiker/HeartOfGold", response.getOutputHeaders().get("Location").get(0).toString());
|
||||
ArgumentCaptor<RepositoryImportEvent> event = ArgumentCaptor.forClass(RepositoryImportEvent.class);
|
||||
verify(eventBus).post(event.capture());
|
||||
assertFalse(event.getValue().isFailed());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldThrowFailedEventOnImportRepositoryFromBundle() throws IOException, URISyntaxException {
|
||||
when(manager.getHandler("svn")).thenReturn(repositoryHandler);
|
||||
when(repositoryHandler.getType()).thenReturn(new RepositoryType("svn", "svn", ImmutableSet.of(Command.UNBUNDLE)));
|
||||
doThrow(ImportFailedException.class).when(repositoryManager).create(any(), any());
|
||||
|
||||
RepositoryDto repositoryDto = new RepositoryDto();
|
||||
repositoryDto.setName("HeartOfGold");
|
||||
repositoryDto.setNamespace("hitchhiker");
|
||||
repositoryDto.setType("svn");
|
||||
|
||||
URL dumpUrl = Resources.getResource("sonia/scm/api/v2/svn.dump");
|
||||
byte[] svnDump = Resources.toByteArray(dumpUrl);
|
||||
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "import/svn/bundle");
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
multipartRequest(request, Collections.singletonMap("bundle", new ByteArrayInputStream(svnDump)), repositoryDto);
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, response.getStatus());
|
||||
ArgumentCaptor<RepositoryImportEvent> event = ArgumentCaptor.forClass(RepositoryImportEvent.class);
|
||||
verify(eventBus).post(event.capture());
|
||||
assertTrue(event.getValue().isFailed());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldImportCompressedBundle() throws IOException {
|
||||
URL dumpUrl = Resources.getResource("sonia/scm/api/v2/svn.dump.gz");
|
||||
byte[] svnDump = Resources.toByteArray(dumpUrl);
|
||||
|
||||
UnbundleCommandBuilder ubc = mock(UnbundleCommandBuilder.class, RETURNS_SELF);
|
||||
when(ubc.unbundle(any(File.class))).thenReturn(new UnbundleResponse(42));
|
||||
RepositoryService service = mock(RepositoryService.class);
|
||||
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
||||
when(service.getUnbundleCommand()).thenReturn(ubc);
|
||||
InputStream in = new ByteArrayInputStream(svnDump);
|
||||
|
||||
Consumer<Repository> repositoryConsumer = repositoryImportResource.unbundleImport(in, true);
|
||||
repositoryConsumer.accept(RepositoryTestData.createHeartOfGold("svn"));
|
||||
|
||||
verify(ubc).setCompressed(true);
|
||||
verify(ubc).unbundle(any(File.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldImportNonCompressedBundle() throws IOException {
|
||||
URL dumpUrl = Resources.getResource("sonia/scm/api/v2/svn.dump");
|
||||
byte[] svnDump = Resources.toByteArray(dumpUrl);
|
||||
|
||||
UnbundleCommandBuilder ubc = mock(UnbundleCommandBuilder.class, RETURNS_SELF);
|
||||
when(ubc.unbundle(any(File.class))).thenReturn(new UnbundleResponse(21));
|
||||
RepositoryService service = mock(RepositoryService.class);
|
||||
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
||||
when(service.getUnbundleCommand()).thenReturn(ubc);
|
||||
InputStream in = new ByteArrayInputStream(svnDump);
|
||||
|
||||
Consumer<Repository> repositoryConsumer = repositoryImportResource.unbundleImport(in, false);
|
||||
repositoryConsumer.accept(RepositoryTestData.createHeartOfGold("svn"));
|
||||
|
||||
verify(ubc, never()).setCompressed(true);
|
||||
verify(ubc).unbundle(any(File.class));
|
||||
}
|
||||
|
||||
private PageResult<Repository> createSingletonPageResult(Repository repository) {
|
||||
return new PageResult<>(singletonList(repository), 0);
|
||||
}
|
||||
@@ -586,4 +694,49 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
||||
when(repositoryManager.get(id)).thenReturn(repository);
|
||||
return repository;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method is a slightly adapted copy of Lin Zaho's gist at https://gist.github.com/lin-zhao/9985191
|
||||
*/
|
||||
private MockHttpRequest multipartRequest(MockHttpRequest request, Map<String, InputStream> files, RepositoryDto repository) throws IOException {
|
||||
String boundary = UUID.randomUUID().toString();
|
||||
request.contentType("multipart/form-data; boundary=" + boundary);
|
||||
|
||||
//Make sure this is deleted in afterTest()
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
try (OutputStreamWriter formWriter = new OutputStreamWriter(buffer)) {
|
||||
formWriter.append("--").append(boundary);
|
||||
|
||||
for (Map.Entry<String, InputStream> entry : files.entrySet()) {
|
||||
formWriter.append("\n");
|
||||
formWriter.append(String.format("Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"",
|
||||
entry.getKey(), entry.getKey())).append("\n");
|
||||
formWriter.append("Content-Type: application/octet-stream").append("\n\n");
|
||||
|
||||
InputStream stream = entry.getValue();
|
||||
int b = stream.read();
|
||||
while (b >= 0) {
|
||||
formWriter.write(b);
|
||||
b = stream.read();
|
||||
}
|
||||
stream.close();
|
||||
formWriter.append("\n").append("--").append(boundary);
|
||||
}
|
||||
|
||||
if (repository != null) {
|
||||
formWriter.append("\n");
|
||||
formWriter.append("Content-Disposition: form-data; name=\"repository\"").append("\n\n");
|
||||
StringWriter repositoryWriter = new StringWriter();
|
||||
new JsonFactory().createGenerator(repositoryWriter).setCodec(new ObjectMapper()).writeObject(repository);
|
||||
formWriter.append(repositoryWriter.getBuffer().toString()).append("\n");
|
||||
formWriter.append("--").append(boundary);
|
||||
}
|
||||
|
||||
formWriter.append("--");
|
||||
formWriter.flush();
|
||||
}
|
||||
request.setInputStream(new ByteArrayInputStream(buffer.toByteArray()));
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,4 +112,31 @@ public class RepositoryTypeToRepositoryTypeDtoMapperTest {
|
||||
RepositoryTypeDto dto = mapper.map(type);
|
||||
assertFalse(dto.getLinks().getLinkBy("import").isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAppendImportFromBundleLink() {
|
||||
RepositoryType type = new RepositoryType("hk", "Hitchhiker", ImmutableSet.of(Command.UNBUNDLE));
|
||||
when(subject.isPermitted("repository:create")).thenReturn(true);
|
||||
|
||||
RepositoryTypeDto dto = mapper.map(type);
|
||||
assertEquals(
|
||||
"https://scm-manager.org/scm/v2/repositories/import/hk/bundle",
|
||||
dto.getLinks().getLinkBy("import").get().getHref()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotAppendImportFromBundleLinkIfCommandNotSupported() {
|
||||
when(subject.isPermitted("repository:create")).thenReturn(true);
|
||||
RepositoryTypeDto dto = mapper.map(type);
|
||||
assertFalse(dto.getLinks().getLinkBy("import").isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotAppendImportFromBundleLinkIfNotPermitted() {
|
||||
RepositoryType type = new RepositoryType("hk", "Hitchhiker", ImmutableSet.of(Command.UNBUNDLE));
|
||||
|
||||
RepositoryTypeDto dto = mapper.map(type);
|
||||
assertFalse(dto.getLinks().getLinkBy("import").isPresent());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user