From e595d6039c7ac8b0fcd1f3d3142c4fc8fad12490 Mon Sep 17 00:00:00 2001 From: Mohamed Karray Date: Wed, 5 Sep 2018 17:06:21 +0200 Subject: [PATCH 01/21] implement the diff endpoint for v2 --- .../main/java/sonia/scm/web/VndMediaType.java | 3 + .../sonia/scm/it/RepositoryAccessITCase.java | 37 ++++- .../api/v2/resources/DiffRootResource.java | 62 ++++++-- .../api/v2/resources/DiffResourceTest.java | 133 ++++++++++++++++++ 4 files changed, 222 insertions(+), 13 deletions(-) create mode 100644 scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java index 73bcf10bd0..20735f83fb 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -12,6 +12,8 @@ public class VndMediaType { private static final String SUBTYPE_PREFIX = "vnd.scmm-"; public static final String PREFIX = TYPE + "/" + SUBTYPE_PREFIX; public static final String SUFFIX = "+json;v=" + VERSION; + public static final String PLAIN_TEXT_PREFIX = "text/" + SUBTYPE_PREFIX; + public static final String PLAIN_TEXT_SUFFIX = "+plain;v=" + VERSION; public static final String USER = PREFIX + "user" + SUFFIX; public static final String GROUP = PREFIX + "group" + SUFFIX; @@ -20,6 +22,7 @@ public class VndMediaType { public static final String CHANGESET = PREFIX + "changeset" + SUFFIX; public static final String CHANGESET_COLLECTION = PREFIX + "changesetCollection" + SUFFIX; public static final String BRANCH = PREFIX + "branch" + SUFFIX; + public static final String DIFF = PLAIN_TEXT_PREFIX + "diff" + PLAIN_TEXT_SUFFIX; public static final String USER_COLLECTION = PREFIX + "userCollection" + SUFFIX; public static final String GROUP_COLLECTION = PREFIX + "groupCollection" + SUFFIX; public static final String REPOSITORY_COLLECTION = PREFIX + "repositoryCollection" + SUFFIX; diff --git a/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java b/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java index a9139f18c8..1722c7e4fa 100644 --- a/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java @@ -16,8 +16,8 @@ import java.io.IOException; import java.util.Collection; import java.util.List; -import static org.assertj.core.api.Assertions.assertThat; import static java.lang.Thread.sleep; +import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertNotNull; import static sonia.scm.it.RestUtil.given; @@ -152,5 +152,40 @@ public class RepositoryAccessITCase { assertThat(changesets).size().isBetween(2, 3); // svn has an implicit root revision '0' that is extra to the two commits } + + @Test + public void shouldFindDiffs() throws IOException { + RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder); + + RepositoryUtil.createAndCommitFile(repositoryClient, "scmadmin", "a.txt", "a"); + RepositoryUtil.createAndCommitFile(repositoryClient, "scmadmin", "b.txt", "b"); + + String changesetsUrl = given() + .when() + .get(TestData.getDefaultRepositoryUrl(repositoryType)) + .then() + .statusCode(HttpStatus.SC_OK) + .extract() + .path("_links.changesets.href"); + + String diffUrl = given() + .when() + .get(changesetsUrl) + .then() + .statusCode(HttpStatus.SC_OK) + .extract() + .path("_embedded.changesets[0]._links.diff.href"); + + given() + .when() + .get(diffUrl) + .then() + .statusCode(HttpStatus.SC_OK) + .extract() + .body() + .asString() + .contains("diff"); + + } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java index 176a86dda7..e98b3bc868 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java @@ -1,27 +1,65 @@ package sonia.scm.api.v2.resources; -import javax.ws.rs.DefaultValue; +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; +import sonia.scm.NotFoundException; +import sonia.scm.repository.NamespaceAndName; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryPermissions; +import sonia.scm.repository.RevisionNotFoundException; +import sonia.scm.repository.api.RepositoryService; +import sonia.scm.repository.api.RepositoryServiceFactory; +import sonia.scm.util.HttpUtil; +import sonia.scm.web.VndMediaType; + +import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; -import javax.ws.rs.QueryParam; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; +import javax.ws.rs.core.StreamingOutput; public class DiffRootResource { + public static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition"; + private final RepositoryServiceFactory serviceFactory; - @GET - @Path("") - public Response getAll(@DefaultValue("0") @QueryParam("page") int page, - @DefaultValue("10") @QueryParam("pageSize") int pageSize, - @QueryParam("sortBy") String sortBy, - @DefaultValue("false") @QueryParam("desc") boolean desc) { - throw new UnsupportedOperationException(); + @Inject + public DiffRootResource(RepositoryServiceFactory serviceFactory) { + this.serviceFactory = serviceFactory; } @GET - @Path("{id}") - public Response get(String id) { - throw new UnsupportedOperationException(); + @Path("{revision}") + @Produces(VndMediaType.DIFF) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 400, condition = "Bad Request"), + @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), + @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the diff"), + @ResponseCode(code = 404, condition = "not found, no revision with the specified param for the repository available or repository not found"), + @ResponseCode(code = 500, condition = "internal server error") + }) + public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision) throws NotFoundException { + HttpUtil.checkForCRLFInjection(revision); + try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { + Repository repository = repositoryService.getRepository(); + RepositoryPermissions.read(repository).check(); + StreamingOutput responseEntry = output -> { + try { + repositoryService.getDiffCommand() + .setRevision(revision) + .retriveContent(output); + } catch (RevisionNotFoundException e) { + throw new WebApplicationException(Response.Status.NOT_FOUND); + } + }; + return Response.ok(responseEntry) + .header(HEADER_CONTENT_DISPOSITION, HttpUtil.createContentDispositionAttachmentHeader(String.format("%s-%s.diff", name, revision))) + .build(); + } } } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java new file mode 100644 index 0000000000..a6f9c26779 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java @@ -0,0 +1,133 @@ +package sonia.scm.api.v2.resources; + + +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.subject.Subject; +import org.apache.shiro.subject.support.SubjectThreadState; +import org.apache.shiro.util.ThreadContext; +import org.apache.shiro.util.ThreadState; +import org.jboss.resteasy.core.Dispatcher; +import org.jboss.resteasy.mock.MockDispatcherFactory; +import org.jboss.resteasy.mock.MockHttpRequest; +import org.jboss.resteasy.mock.MockHttpResponse; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import sonia.scm.api.rest.AuthorizationExceptionMapper; +import sonia.scm.repository.NamespaceAndName; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryNotFoundException; +import sonia.scm.repository.RevisionNotFoundException; +import sonia.scm.repository.api.DiffCommandBuilder; +import sonia.scm.repository.api.RepositoryService; +import sonia.scm.repository.api.RepositoryServiceFactory; +import sonia.scm.web.VndMediaType; + +import java.net.URISyntaxException; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.Silent.class) +@Slf4j +public class DiffResourceTest { + + + public static final String DIFF_PATH = "space/repo/diff/"; + public static final String DIFF_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + DIFF_PATH; + private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + + @Mock + private RepositoryServiceFactory serviceFactory; + + @Mock + private RepositoryService service; + + @Mock + private DiffCommandBuilder diffCommandBuilder; + + private DiffRootResource diffRootResource; + + + private final Subject subject = mock(Subject.class); + private final ThreadState subjectThreadState = new SubjectThreadState(subject); + + + @Before + public void prepareEnvironment() throws Exception { + diffRootResource = new DiffRootResource(serviceFactory); + RepositoryRootResource repositoryRootResource = new RepositoryRootResource(MockProvider + .of(new RepositoryResource(null, null, null, null, null, + null, null, null, null, MockProvider.of(diffRootResource))), null); + dispatcher.getRegistry().addSingletonResource(repositoryRootResource); + when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service); + when(serviceFactory.create(any(Repository.class))).thenReturn(service); + when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo")); + dispatcher.getProviderFactory().registerProvider(NotFoundExceptionMapper.class); + dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class); + when(service.getDiffCommand()).thenReturn(diffCommandBuilder); + subjectThreadState.bind(); + ThreadContext.bind(subject); + when(subject.isPermitted(any(String.class))).thenReturn(true); + } + + @After + public void cleanupContext() { + ThreadContext.unbindSubject(); + } + + @Test + public void shouldGetDiffs() throws Exception { + when(diffCommandBuilder.setRevision(anyString())).thenReturn(diffCommandBuilder); + when(diffCommandBuilder.retriveContent(any())).thenReturn(diffCommandBuilder); + + MockHttpRequest request = MockHttpRequest + .get(DIFF_URL + "revision") + .accept(VndMediaType.DIFF); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(200, response.getStatus()); + log.info("Response :{}", response.getContentAsString()); + assertThat(response.getStatus()) + .isEqualTo(200); + assertThat(response.getContentAsString()) + .isNotNull(); + String expectedHeader = "Content-Disposition"; + String expectedValue = "attachment; filename=\"repo-revision.diff\"; filename*=utf-8''repo-revision.diff"; + assertThat(response.getOutputHeaders().containsKey(expectedHeader)).isTrue(); + assertThat((String) response.getOutputHeaders().get("Content-Disposition").get(0)) + .contains(expectedValue); + } + + @Test + public void shouldGet404OnMissingRepository() throws URISyntaxException, RepositoryNotFoundException { + when(serviceFactory.create(any(NamespaceAndName.class))).thenThrow(RepositoryNotFoundException.class); + MockHttpRequest request = MockHttpRequest + .get(DIFF_URL + "revision") + .accept(VndMediaType.DIFF); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(404, response.getStatus()); + } + + @Test + public void shouldGet404OnMissingRevision() throws Exception { + when(diffCommandBuilder.setRevision(anyString())).thenReturn(diffCommandBuilder); + when(diffCommandBuilder.retriveContent(any())).thenThrow(RevisionNotFoundException.class); + + MockHttpRequest request = MockHttpRequest + .get(DIFF_URL + "revision") + .accept(VndMediaType.DIFF); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(404, response.getStatus()); + } + +} From 56d323793e4720de41f31bc7f5a95b1ef63a1c49 Mon Sep 17 00:00:00 2001 From: Mohamed Karray Date: Thu, 6 Sep 2018 11:33:39 +0200 Subject: [PATCH 02/21] file history endpoint --- .../ChangesetCollectionToDtoMapper.java | 11 +- .../FileHistoryCollectionToDtoMapper.java | 24 +++ .../v2/resources/FileHistoryRootResource.java | 78 +++++++ .../FileObjectToFileObjectDtoMapper.java | 3 + .../InternalRepositoryExceptionMapper.java | 15 ++ .../api/v2/resources/RepositoryResource.java | 10 +- .../scm/api/v2/resources/ResourceLinks.java | 17 ++ .../v2/resources/BranchRootResourceTest.java | 2 +- .../resources/ChangesetRootResourceTest.java | 4 +- .../v2/resources/FileHistoryResourceTest.java | 203 ++++++++++++++++++ .../resources/PermissionRootResourceTest.java | 2 +- .../resources/RepositoryRootResourceTest.java | 2 +- .../api/v2/resources/ResourceLinksMock.java | 1 + .../v2/resources/SourceRootResourceTest.java | 2 +- 14 files changed, 363 insertions(+), 11 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryCollectionToDtoMapper.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/InternalRepositoryExceptionMapper.java create mode 100644 scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetCollectionToDtoMapper.java index fcc4085486..3af3a1d15a 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetCollectionToDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetCollectionToDtoMapper.java @@ -6,11 +6,12 @@ import sonia.scm.repository.Repository; import javax.inject.Inject; import java.util.Optional; +import java.util.function.Supplier; public class ChangesetCollectionToDtoMapper extends BasicCollectionToDtoMapper { private final ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper; - private final ResourceLinks resourceLinks; + protected final ResourceLinks resourceLinks; @Inject public ChangesetCollectionToDtoMapper(ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper, ResourceLinks resourceLinks) { @@ -20,10 +21,14 @@ public class ChangesetCollectionToDtoMapper extends BasicCollectionToDtoMapper pageResult, Repository repository) { - return super.map(pageNumber, pageSize, pageResult, createSelfLink(repository), Optional.empty(), changeset -> changesetToChangesetDtoMapper.map(changeset, repository)); + return this.map(pageNumber, pageSize, pageResult, repository, () -> createSelfLink(repository)); } - private String createSelfLink(Repository repository) { + public CollectionDto map(int pageNumber, int pageSize, PageResult pageResult, Repository repository, Supplier selfLinkSupplier) { + return super.map(pageNumber, pageSize, pageResult, selfLinkSupplier.get(), Optional.empty(), changeset -> changesetToChangesetDtoMapper.map(changeset, repository)); + } + + protected String createSelfLink(Repository repository) { return resourceLinks.changeset().all(repository.getNamespace(), repository.getName()); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryCollectionToDtoMapper.java new file mode 100644 index 0000000000..692b2f57b1 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryCollectionToDtoMapper.java @@ -0,0 +1,24 @@ +package sonia.scm.api.v2.resources; + +import sonia.scm.PageResult; +import sonia.scm.repository.Changeset; +import sonia.scm.repository.Repository; + +import javax.inject.Inject; + +public class FileHistoryCollectionToDtoMapper extends ChangesetCollectionToDtoMapper { + + + @Inject + public FileHistoryCollectionToDtoMapper(ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper, ResourceLinks resourceLinks) { + super(changesetToChangesetDtoMapper, resourceLinks); + } + + public CollectionDto map(int pageNumber, int pageSize, PageResult pageResult, Repository repository, String revision, String path) { + return super.map(pageNumber, pageSize, pageResult, repository, () -> createSelfLink(repository, revision, path)); + } + + protected String createSelfLink(Repository repository, String revision, String path) { + return super.resourceLinks.fileHistory().self(repository.getNamespace(), repository.getName(), revision, path); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java new file mode 100644 index 0000000000..d56590b384 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java @@ -0,0 +1,78 @@ +package sonia.scm.api.v2.resources; + +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; +import com.webcohesion.enunciate.metadata.rs.TypeHint; +import lombok.extern.slf4j.Slf4j; +import sonia.scm.PageResult; +import sonia.scm.repository.Changeset; +import sonia.scm.repository.ChangesetPagingResult; +import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.repository.NamespaceAndName; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryNotFoundException; +import sonia.scm.repository.RevisionNotFoundException; +import sonia.scm.repository.api.RepositoryService; +import sonia.scm.repository.api.RepositoryServiceFactory; +import sonia.scm.web.VndMediaType; + +import javax.inject.Inject; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; +import java.io.IOException; + +@Slf4j +public class FileHistoryRootResource { + + private final RepositoryServiceFactory serviceFactory; + + private final FileHistoryCollectionToDtoMapper fileHistoryCollectionToDtoMapper; + + + @Inject + public FileHistoryRootResource(RepositoryServiceFactory serviceFactory, FileHistoryCollectionToDtoMapper fileHistoryCollectionToDtoMapper) { + this.serviceFactory = serviceFactory; + this.fileHistoryCollectionToDtoMapper = fileHistoryCollectionToDtoMapper; + } + + @GET + @Path("{revision}/{path: .*}") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), + @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the changeset"), + @ResponseCode(code = 404, condition = "not found, no changesets available in the repository"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @Produces(VndMediaType.CHANGESET_COLLECTION) + @TypeHint(CollectionDto.class) + public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name, + @PathParam("revision") String revision, + @PathParam("path") String path, + @DefaultValue("0") @QueryParam("page") int page, + @DefaultValue("10") @QueryParam("pageSize") int pageSize) throws IOException, RevisionNotFoundException, RepositoryNotFoundException { + try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { + log.info("Get changesets of the file {} and revision {}", path, revision); + Repository repository = repositoryService.getRepository(); + ChangesetPagingResult changesets = repositoryService.getLogCommand() + .setPagingStart(page) + .setPagingLimit(pageSize) + .setPath(path) + .setStartChangeset(revision) + .getChangesets(); + if (changesets != null && changesets.getChangesets() != null) { + PageResult pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal()); + return Response.ok(fileHistoryCollectionToDtoMapper.map(page, pageSize, pageResult, repository, revision, path)).build(); + } else { + String message = String.format("for the revision %s and the file %s there is no changesets", revision, path); + log.error(message); + throw new InternalRepositoryException(message); + } + } + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java index bc814c7e0c..0c5e61991a 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java @@ -12,6 +12,8 @@ import sonia.scm.repository.SubRepository; import javax.inject.Inject; import java.net.URI; +import static de.otto.edison.hal.Link.link; + @Mapper public abstract class FileObjectToFileObjectDtoMapper extends BaseMapper { @@ -30,6 +32,7 @@ public abstract class FileObjectToFileObjectDtoMapper extends BaseMapper { + + public InternalRepositoryExceptionMapper() { + super(InternalRepositoryException.class, Response.Status.INTERNAL_SERVER_ERROR); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java index c47fc4f3d1..1585065b91 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java @@ -40,6 +40,7 @@ public class RepositoryResource { private final Provider contentResource; private final Provider permissionRootResource; private final Provider diffRootResource; + private final Provider fileHistoryRootResource; @Inject public RepositoryResource( @@ -50,7 +51,8 @@ public class RepositoryResource { Provider changesetRootResource, Provider sourceRootResource, Provider contentResource, Provider permissionRootResource, - Provider diffRootResource) { + Provider diffRootResource, + Provider fileHistoryRootResource) { this.dtoToRepositoryMapper = dtoToRepositoryMapper; this.manager = manager; this.repositoryToDtoMapper = repositoryToDtoMapper; @@ -62,6 +64,7 @@ public class RepositoryResource { this.contentResource = contentResource; this.permissionRootResource = permissionRootResource; this.diffRootResource = diffRootResource; + this.fileHistoryRootResource = fileHistoryRootResource; } /** @@ -165,6 +168,11 @@ public class RepositoryResource { return changesetRootResource.get(); } + @Path("history/") + public FileHistoryRootResource history() { + return fileHistoryRootResource.get(); + } + @Path("sources/") public SourceRootResource sources() { return sourceRootResource.get(); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java index d51a462c19..5610b61dfc 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java @@ -307,6 +307,23 @@ class ResourceLinks { } } + public FileHistoryLinks fileHistory() { + return new FileHistoryLinks(uriInfoStore.get()); + } + + static class FileHistoryLinks { + private final LinkBuilder fileHistoryLinkBuilder; + + FileHistoryLinks(UriInfo uriInfo) { + fileHistoryLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryResource.class, FileHistoryRootResource.class); + } + + String self(String namespace, String name, String changesetId, String path) { + return fileHistoryLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("history").parameters().method("getAll").parameters(changesetId, path).href(); + } + + } + public SourceLinks source() { return new SourceLinks(uriInfoStore.get()); } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java index e23d3dc39b..0a209e905f 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java @@ -92,7 +92,7 @@ public class BranchRootResourceTest { changesetCollectionToDtoMapper = new ChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks); BranchCollectionToDtoMapper branchCollectionToDtoMapper = new BranchCollectionToDtoMapper(branchToDtoMapper, resourceLinks); branchRootResource = new BranchRootResource(serviceFactory, branchToDtoMapper, branchCollectionToDtoMapper, changesetCollectionToDtoMapper); - RepositoryRootResource repositoryRootResource = new RepositoryRootResource(MockProvider.of(new RepositoryResource(null, null, null, null, MockProvider.of(branchRootResource), null, null, null, null, null)), null); + RepositoryRootResource repositoryRootResource = new RepositoryRootResource(MockProvider.of(new RepositoryResource(null, null, null, null, MockProvider.of(branchRootResource), null, null, null, null, null, null)), null); dispatcher.getRegistry().addSingletonResource(repositoryRootResource); when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java index 570830d651..44f9d7e7bc 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java @@ -81,7 +81,7 @@ public class ChangesetRootResourceTest { changesetRootResource = new ChangesetRootResource(serviceFactory, changesetCollectionToDtoMapper, changesetToChangesetDtoMapper); RepositoryRootResource repositoryRootResource = new RepositoryRootResource(MockProvider .of(new RepositoryResource(null, null, null, null, null, - MockProvider.of(changesetRootResource), null, null, null, null)), null); + MockProvider.of(changesetRootResource), null, null, null, null, null)), null); dispatcher.getRegistry().addSingletonResource(repositoryRootResource); when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service); when(serviceFactory.create(any(Repository.class))).thenReturn(service); @@ -125,7 +125,6 @@ public class ChangesetRootResourceTest { assertTrue(response.getContentAsString().contains(String.format("\"name\":\"%s\"", authorName))); assertTrue(response.getContentAsString().contains(String.format("\"mail\":\"%s\"", authorEmail))); assertTrue(response.getContentAsString().contains(String.format("\"description\":\"%s\"", commit))); - assertTrue(response.getContentAsString().contains(String.format("\"description\":\"%s\"", commit))); } @Test @@ -155,7 +154,6 @@ public class ChangesetRootResourceTest { assertTrue(response.getContentAsString().contains(String.format("\"name\":\"%s\"", authorName))); assertTrue(response.getContentAsString().contains(String.format("\"mail\":\"%s\"", authorEmail))); assertTrue(response.getContentAsString().contains(String.format("\"description\":\"%s\"", commit))); - assertTrue(response.getContentAsString().contains(String.format("\"description\":\"%s\"", commit))); } } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java new file mode 100644 index 0000000000..f89a92f124 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java @@ -0,0 +1,203 @@ +package sonia.scm.api.v2.resources; + +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.subject.Subject; +import org.apache.shiro.subject.support.SubjectThreadState; +import org.apache.shiro.util.ThreadContext; +import org.apache.shiro.util.ThreadState; +import org.assertj.core.util.Lists; +import org.jboss.resteasy.core.Dispatcher; +import org.jboss.resteasy.mock.MockDispatcherFactory; +import org.jboss.resteasy.mock.MockHttpRequest; +import org.jboss.resteasy.mock.MockHttpResponse; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import sonia.scm.api.rest.AuthorizationExceptionMapper; +import sonia.scm.repository.Changeset; +import sonia.scm.repository.ChangesetPagingResult; +import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.repository.NamespaceAndName; +import sonia.scm.repository.Person; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryNotFoundException; +import sonia.scm.repository.RevisionNotFoundException; +import sonia.scm.repository.api.LogCommandBuilder; +import sonia.scm.repository.api.RepositoryService; +import sonia.scm.repository.api.RepositoryServiceFactory; +import sonia.scm.web.VndMediaType; + +import java.net.URI; +import java.net.URISyntaxException; +import java.time.Instant; +import java.util.Date; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.Silent.class) +@Slf4j +public class FileHistoryResourceTest { + + public static final String FILE_HISTORY_PATH = "space/repo/history/"; + public static final String FILE_HISTORY_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + FILE_HISTORY_PATH; + private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + + private final URI baseUri = URI.create("/"); + private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); + + @Mock + private RepositoryServiceFactory serviceFactory; + + @Mock + private RepositoryService service; + + @Mock + private LogCommandBuilder logCommandBuilder; + + private FileHistoryCollectionToDtoMapper fileHistoryCollectionToDtoMapper; + + @InjectMocks + private ChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper; + + private FileHistoryRootResource fileHistoryRootResource; + + + private final Subject subject = mock(Subject.class); + private final ThreadState subjectThreadState = new SubjectThreadState(subject); + + + @Before + public void prepareEnvironment() throws Exception { + fileHistoryCollectionToDtoMapper = new FileHistoryCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks); + fileHistoryRootResource = new FileHistoryRootResource(serviceFactory, fileHistoryCollectionToDtoMapper); + RepositoryRootResource repositoryRootResource = new RepositoryRootResource(MockProvider + .of(new RepositoryResource(null, null, null, null, null, + null, null, null, null, null, MockProvider.of(fileHistoryRootResource))), null); + dispatcher.getRegistry().addSingletonResource(repositoryRootResource); + when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service); + when(serviceFactory.create(any(Repository.class))).thenReturn(service); + when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo")); + dispatcher.getProviderFactory().registerProvider(NotFoundExceptionMapper.class); + dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class); + dispatcher.getProviderFactory().registerProvider(InternalRepositoryExceptionMapper.class); + when(service.getLogCommand()).thenReturn(logCommandBuilder); + subjectThreadState.bind(); + ThreadContext.bind(subject); + when(subject.isPermitted(any(String.class))).thenReturn(true); + } + + @After + public void cleanupContext() { + ThreadContext.unbindSubject(); + } + + @Test + public void shouldGetFileHistory() throws Exception { + String id = "revision_123"; + String path = "root_dir/sub_dir/file-to-inspect.txt"; + Instant creationDate = Instant.now(); + String authorName = "name"; + String authorEmail = "em@i.l"; + String commit = "my branch commit"; + ChangesetPagingResult changesetPagingResult = mock(ChangesetPagingResult.class); + List changesetList = Lists.newArrayList(new Changeset(id, Date.from(creationDate).getTime(), new Person(authorName, authorEmail), commit)); + when(changesetPagingResult.getChangesets()).thenReturn(changesetList); + when(changesetPagingResult.getTotal()).thenReturn(1); + when(logCommandBuilder.setPagingStart(anyInt())).thenReturn(logCommandBuilder); + when(logCommandBuilder.setPagingLimit(anyInt())).thenReturn(logCommandBuilder); + when(logCommandBuilder.setStartChangeset(eq(id))).thenReturn(logCommandBuilder); + when(logCommandBuilder.setPath(eq(path))).thenReturn(logCommandBuilder); + when(logCommandBuilder.getChangesets()).thenReturn(changesetPagingResult); + MockHttpRequest request = MockHttpRequest + .get(FILE_HISTORY_URL + id + "/" + path) + .accept(VndMediaType.CHANGESET_COLLECTION); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(200, response.getStatus()); + log.info("Response :{}", response.getContentAsString()); + assertTrue(response.getContentAsString().contains(String.format("\"id\":\"%s\"", id))); + assertTrue(response.getContentAsString().contains(String.format("\"name\":\"%s\"", authorName))); + assertTrue(response.getContentAsString().contains(String.format("\"mail\":\"%s\"", authorEmail))); + assertTrue(response.getContentAsString().contains(String.format("\"description\":\"%s\"", commit))); + } + + + @Test + public void shouldGet404OnMissingRepository() throws URISyntaxException, RepositoryNotFoundException { + when(serviceFactory.create(any(NamespaceAndName.class))).thenThrow(RepositoryNotFoundException.class); + MockHttpRequest request = MockHttpRequest + .get(FILE_HISTORY_URL + "revision/a.txt") + .accept(VndMediaType.CHANGESET_COLLECTION); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(404, response.getStatus()); + } + + @Test + public void shouldGet404OnMissingRevision() throws Exception { + String id = "revision_123"; + String path = "root_dir/sub_dir/file-to-inspect.txt"; + + when(logCommandBuilder.setPagingStart(anyInt())).thenReturn(logCommandBuilder); + when(logCommandBuilder.setPagingLimit(anyInt())).thenReturn(logCommandBuilder); + when(logCommandBuilder.setStartChangeset(eq(id))).thenReturn(logCommandBuilder); + when(logCommandBuilder.setPath(eq(path))).thenReturn(logCommandBuilder); + when(logCommandBuilder.getChangesets()).thenThrow(RevisionNotFoundException.class); + + MockHttpRequest request = MockHttpRequest + .get(FILE_HISTORY_URL + id + "/" + path) + .accept(VndMediaType.CHANGESET_COLLECTION); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(404, response.getStatus()); + } + + @Test + public void shouldGet500OnInternalRepositoryException() throws Exception { + String id = "revision_123"; + String path = "root_dir/sub_dir/file-to-inspect.txt"; + + when(logCommandBuilder.setPagingStart(anyInt())).thenReturn(logCommandBuilder); + when(logCommandBuilder.setPagingLimit(anyInt())).thenReturn(logCommandBuilder); + when(logCommandBuilder.setStartChangeset(eq(id))).thenReturn(logCommandBuilder); + when(logCommandBuilder.setPath(eq(path))).thenReturn(logCommandBuilder); + when(logCommandBuilder.getChangesets()).thenThrow(InternalRepositoryException.class); + + MockHttpRequest request = MockHttpRequest + .get(FILE_HISTORY_URL + id + "/" + path) + .accept(VndMediaType.CHANGESET_COLLECTION); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(500, response.getStatus()); + } + + @Test + public void shouldGet500OnNullChangesets() throws Exception { + String id = "revision_123"; + String path = "root_dir/sub_dir/file-to-inspect.txt"; + + when(logCommandBuilder.setPagingStart(anyInt())).thenReturn(logCommandBuilder); + when(logCommandBuilder.setPagingLimit(anyInt())).thenReturn(logCommandBuilder); + when(logCommandBuilder.setStartChangeset(eq(id))).thenReturn(logCommandBuilder); + when(logCommandBuilder.setPath(eq(path))).thenReturn(logCommandBuilder); + when(logCommandBuilder.getChangesets()).thenReturn(null); + + MockHttpRequest request = MockHttpRequest + .get(FILE_HISTORY_URL + id + "/" + path) + .accept(VndMediaType.CHANGESET_COLLECTION); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(500, response.getStatus()); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PermissionRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PermissionRootResourceTest.java index 8d05c1f455..3f2ea9b317 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PermissionRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PermissionRootResourceTest.java @@ -138,7 +138,7 @@ public class PermissionRootResourceTest { permissionCollectionToDtoMapper = new PermissionCollectionToDtoMapper(permissionToPermissionDtoMapper, resourceLinks); permissionRootResource = new PermissionRootResource(permissionDtoToPermissionMapper, permissionToPermissionDtoMapper, permissionCollectionToDtoMapper, resourceLinks, repositoryManager); RepositoryRootResource repositoryRootResource = new RepositoryRootResource(MockProvider - .of(new RepositoryResource(null, null, null, null, null, null, null, null, MockProvider.of(permissionRootResource), null)), null); + .of(new RepositoryResource(null, null, null, null, null, null, null, null, MockProvider.of(permissionRootResource), null, null)), null); dispatcher = createDispatcher(repositoryRootResource); subjectThreadState.bind(); ThreadContext.bind(subject); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java index 539d469445..48ca62089f 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java @@ -79,7 +79,7 @@ public class RepositoryRootResourceTest { @Before public void prepareEnvironment() { initMocks(this); - RepositoryResource repositoryResource = new RepositoryResource(repositoryToDtoMapper, dtoToRepositoryMapper, repositoryManager, null, null, null, null, null, null, null); + RepositoryResource repositoryResource = new RepositoryResource(repositoryToDtoMapper, dtoToRepositoryMapper, repositoryManager, null, null, null, null, null, null, null, null); RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper = new RepositoryCollectionToDtoMapper(repositoryToDtoMapper, resourceLinks); RepositoryCollectionResource repositoryCollectionResource = new RepositoryCollectionResource(repositoryManager, repositoryCollectionToDtoMapper, dtoToRepositoryMapper, resourceLinks); RepositoryRootResource repositoryRootResource = new RepositoryRootResource(MockProvider.of(repositoryResource), MockProvider.of(repositoryCollectionResource)); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java index f9ace9f8f6..018797a7a3 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java @@ -22,6 +22,7 @@ public class ResourceLinksMock { when(resourceLinks.tag()).thenReturn(new ResourceLinks.TagCollectionLinks(uriInfo)); when(resourceLinks.branchCollection()).thenReturn(new ResourceLinks.BranchCollectionLinks(uriInfo)); when(resourceLinks.changeset()).thenReturn(new ResourceLinks.ChangesetLinks(uriInfo)); + when(resourceLinks.fileHistory()).thenReturn(new ResourceLinks.FileHistoryLinks(uriInfo)); when(resourceLinks.source()).thenReturn(new ResourceLinks.SourceLinks(uriInfo)); when(resourceLinks.permission()).thenReturn(new ResourceLinks.PermissionLinks(uriInfo)); when(resourceLinks.config()).thenReturn(new ResourceLinks.ConfigLinks(uriInfo)); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java index 52e445a801..5321219b18 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java @@ -1,7 +1,6 @@ package sonia.scm.api.v2.resources; import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; @@ -74,6 +73,7 @@ public class SourceRootResourceTest { MockProvider.of(sourceRootResource), null, null, + null, null)), null); dispatcher = createDispatcher(repositoryRootResource); From 6acfb38132b75d959a18da8eaad4bdd0eeb96df3 Mon Sep 17 00:00:00 2001 From: Mohamed Karray Date: Thu, 6 Sep 2018 13:54:52 +0200 Subject: [PATCH 03/21] add crlf exception and exception mapper --- .../sonia/scm/util/CRLFInjectionException.java | 8 ++++++++ .../src/main/java/sonia/scm/util/HttpUtil.java | 3 +-- .../resources/CRLFInjectionExceptionMapper.java | 13 +++++++++++++ .../scm/api/v2/resources/DiffRootResource.java | 12 ++++++++++-- .../scm/api/v2/resources/DiffResourceTest.java | 16 ++++++++++++++++ 5 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/util/CRLFInjectionException.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/CRLFInjectionExceptionMapper.java diff --git a/scm-core/src/main/java/sonia/scm/util/CRLFInjectionException.java b/scm-core/src/main/java/sonia/scm/util/CRLFInjectionException.java new file mode 100644 index 0000000000..16dca8e910 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/util/CRLFInjectionException.java @@ -0,0 +1,8 @@ +package sonia.scm.util; + +public class CRLFInjectionException extends IllegalArgumentException{ + + public CRLFInjectionException(String message) { + super(message); + } +} diff --git a/scm-core/src/main/java/sonia/scm/util/HttpUtil.java b/scm-core/src/main/java/sonia/scm/util/HttpUtil.java index bc3f4e74cd..2744addc62 100644 --- a/scm-core/src/main/java/sonia/scm/util/HttpUtil.java +++ b/scm-core/src/main/java/sonia/scm/util/HttpUtil.java @@ -344,8 +344,7 @@ public final class HttpUtil "parameter \"{}\" contains a character which could be an indicator for a crlf injection", parameter); - throw new IllegalArgumentException( - "parameter contains an illegal character"); + throw new CRLFInjectionException("parameter contains an illegal character"); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/CRLFInjectionExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/CRLFInjectionExceptionMapper.java new file mode 100644 index 0000000000..f4e8d3aa3c --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/CRLFInjectionExceptionMapper.java @@ -0,0 +1,13 @@ +package sonia.scm.api.v2.resources; + +import sonia.scm.api.rest.StatusExceptionMapper; +import sonia.scm.util.CRLFInjectionException; + +import javax.ws.rs.core.Response; + +public class CRLFInjectionExceptionMapper extends StatusExceptionMapper { + + public CRLFInjectionExceptionMapper() { + super(CRLFInjectionException.class, Response.Status.BAD_REQUEST); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java index e98b3bc868..1a2b3d2189 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java @@ -31,6 +31,16 @@ public class DiffRootResource { this.serviceFactory = serviceFactory; } + + /** + * Get the repository diff of a revision + * + * @param namespace repository namespace + * @param name repository name + * @param revision the revision + * @return the dif of the revision + * @throws NotFoundException if the repository is not found + */ @GET @Path("{revision}") @Produces(VndMediaType.DIFF) @@ -45,8 +55,6 @@ public class DiffRootResource { public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision) throws NotFoundException { HttpUtil.checkForCRLFInjection(revision); try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { - Repository repository = repositoryService.getRepository(); - RepositoryPermissions.read(repository).check(); StreamingOutput responseEntry = output -> { try { repositoryService.getDiffCommand() diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java index a6f9c26779..0daeb07b7c 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java @@ -72,6 +72,7 @@ public class DiffResourceTest { when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo")); dispatcher.getProviderFactory().registerProvider(NotFoundExceptionMapper.class); dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class); + dispatcher.getProviderFactory().registerProvider(CRLFInjectionExceptionMapper.class); when(service.getDiffCommand()).thenReturn(diffCommandBuilder); subjectThreadState.bind(); ThreadContext.bind(subject); @@ -130,4 +131,19 @@ public class DiffResourceTest { assertEquals(404, response.getStatus()); } + @Test + public void shouldGet400OnCrlfInjection() throws Exception { + when(diffCommandBuilder.setRevision(anyString())).thenReturn(diffCommandBuilder); + when(diffCommandBuilder.retriveContent(any())).thenThrow(RevisionNotFoundException.class); + + MockHttpRequest request = MockHttpRequest + .get(DIFF_URL + "ny%0D%0ASet-cookie:%20Tamper=3079675143472450634") + .accept(VndMediaType.DIFF); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(400, response.getStatus()); + } + + + } From f9a8d903b78ecd3350a1c5368a36d081b93712d2 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 6 Sep 2018 14:41:48 +0200 Subject: [PATCH 04/21] fxi missing contextPath for ForwardingPushStateDispatchers --- .../sonia/scm/ForwardingPushStateDispatcher.java | 5 ++++- .../sonia/scm/ForwardingPushStateDispatcherTest.java | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/scm-webapp/src/main/java/sonia/scm/ForwardingPushStateDispatcher.java b/scm-webapp/src/main/java/sonia/scm/ForwardingPushStateDispatcher.java index 0b80f158f3..ae9ef97499 100644 --- a/scm-webapp/src/main/java/sonia/scm/ForwardingPushStateDispatcher.java +++ b/scm-webapp/src/main/java/sonia/scm/ForwardingPushStateDispatcher.java @@ -1,5 +1,7 @@ package sonia.scm; +import sonia.scm.util.HttpUtil; + import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -14,7 +16,8 @@ import java.io.IOException; public class ForwardingPushStateDispatcher implements PushStateDispatcher { @Override public void dispatch(HttpServletRequest request, HttpServletResponse response, String uri) throws IOException { - RequestDispatcher dispatcher = request.getRequestDispatcher("/index.html"); + String path = HttpUtil.append(request.getContextPath(), "index.html"); + RequestDispatcher dispatcher = request.getRequestDispatcher(path); try { dispatcher.forward(request, response); } catch (ServletException e) { diff --git a/scm-webapp/src/test/java/sonia/scm/ForwardingPushStateDispatcherTest.java b/scm-webapp/src/test/java/sonia/scm/ForwardingPushStateDispatcherTest.java index e96464ee98..c5a42d2346 100644 --- a/scm-webapp/src/test/java/sonia/scm/ForwardingPushStateDispatcherTest.java +++ b/scm-webapp/src/test/java/sonia/scm/ForwardingPushStateDispatcherTest.java @@ -33,6 +33,7 @@ public class ForwardingPushStateDispatcherTest { @Test public void testDispatch() throws ServletException, IOException { + when(request.getContextPath()).thenReturn(""); when(request.getRequestDispatcher("/index.html")).thenReturn(requestDispatcher); dispatcher.dispatch(request, response, "/something"); @@ -40,8 +41,19 @@ public class ForwardingPushStateDispatcherTest { verify(requestDispatcher).forward(request, response); } + @Test + public void testDispatchWithContextPath() throws ServletException, IOException { + when(request.getContextPath()).thenReturn("/scm"); + when(request.getRequestDispatcher("/scm/index.html")).thenReturn(requestDispatcher); + + dispatcher.dispatch(request, response, "/something"); + + verify(requestDispatcher).forward(request, response); + } + @Test(expected = IOException.class) public void testWrapServletException() throws ServletException, IOException { + when(request.getContextPath()).thenReturn(""); when(request.getRequestDispatcher("/index.html")).thenReturn(requestDispatcher); doThrow(ServletException.class).when(requestDispatcher).forward(request, response); From 94e23ca91e4674cfd4bd38e94804851aa44440c1 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 6 Sep 2018 14:42:48 +0200 Subject: [PATCH 05/21] create war file of scm-ui --- scm-ui/package.json | 14 ++++++------- scm-ui/pom.xml | 19 +++++++++++++++-- scm-ui/yarn.lock | 50 ++++++++++++++++++++++++++++++++++++++------- 3 files changed, 67 insertions(+), 16 deletions(-) diff --git a/scm-ui/package.json b/scm-ui/package.json index a531cb4bd9..c940d769c1 100644 --- a/scm-ui/package.json +++ b/scm-ui/package.json @@ -28,13 +28,13 @@ "redux-thunk": "^2.3.0" }, "scripts": { - "webfonts": "copyfiles -f node_modules/@fortawesome/fontawesome-free/webfonts/* target/styles/webfonts", - "build-css": "node-sass-chokidar --include-path ./styles --include-path ./node_modules styles/ -o target/styles", - "watch-css": "npm run build-css && node-sass-chokidar --include-path ./styles --include-path ./node_modules styles/ -o target/styles --watch --recursive", - "start-js": "ui-bundler serve --vendor vendor.bundle.js", + "webfonts": "copyfiles -f node_modules/@fortawesome/fontawesome-free/webfonts/* target/scm-ui/styles/webfonts", + "build-css": "node-sass-chokidar --include-path ./styles --include-path ./node_modules styles/ -o target/scm-ui/styles", + "watch-css": "npm run build-css && node-sass-chokidar --include-path ./styles --include-path ./node_modules styles/ -o target/scm-ui/styles --watch --recursive", + "start-js": "ui-bundler serve --target target/scm-ui --vendor vendor.bundle.js", "start": "npm-run-all -p webfonts watch-css start-js", - "build-js": "ui-bundler bundle target/scm-ui.bundle.js", - "build-vendor": "ui-bundler vendor target/vendor.bundle.js", + "build-js": "ui-bundler bundle --mode=production target/scm-ui/scm-ui.bundle.js", + "build-vendor": "ui-bundler vendor --mode=production target/scm-ui/vendor.bundle.js", "build": "npm-run-all -s webfonts build-css build-vendor build-js", "test": "ui-bundler test", "test-ci": "ui-bundler test --ci", @@ -42,7 +42,7 @@ "pre-commit": "jest && flow && eslint src" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.13", + "@scm-manager/ui-bundler": "^0.0.14", "copyfiles": "^2.0.0", "enzyme": "^3.3.0", "enzyme-adapter-react-16": "^1.1.1", diff --git a/scm-ui/pom.xml b/scm-ui/pom.xml index abe87e697f..5e3ca2b6f5 100644 --- a/scm-ui/pom.xml +++ b/scm-ui/pom.xml @@ -10,9 +10,9 @@ 2.0.0-SNAPSHOT - sonia.scm.clients + sonia.scm scm-ui - pom + war 2.0.0-SNAPSHOT scm-ui @@ -26,6 +26,7 @@ + scm-ui @@ -93,6 +94,20 @@ + + org.apache.maven.plugins + maven-war-plugin + 3.1.0 + + false + + + public + + + + + diff --git a/scm-ui/yarn.lock b/scm-ui/yarn.lock index 078a841ea2..157fcccc12 100644 --- a/scm-ui/yarn.lock +++ b/scm-ui/yarn.lock @@ -732,9 +732,9 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.13": - version "0.0.13" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.13.tgz#020e6c8ee870fccb6c451490cb18972ebfb0d2c4" +"@scm-manager/ui-bundler@^0.0.14": + version "0.0.14" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.14.tgz#f5a2f0a5b0a4e527c60e38ec53ce40ce7d3b4c6f" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -762,7 +762,7 @@ flow-bin "^0.79.1" gulp "^3.9.1" gulp-sourcemaps "^2.6.4" - gulp-util "^3.0.8" + gulp-uglify "^3.0.1" jest "^23.5.0" jest-junit "^5.1.0" node-mkdirs "^0.0.1" @@ -2042,7 +2042,7 @@ combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5, combined- dependencies: delayed-stream "~1.0.0" -commander@^2.11.0, commander@^2.17.1, commander@^2.2.0, commander@^2.9.0: +commander@^2.11.0, commander@^2.17.1, commander@^2.2.0, commander@^2.9.0, commander@~2.17.1: version "2.17.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" @@ -3743,7 +3743,20 @@ gulp-sourcemaps@^2.6.4: strip-bom-string "1.X" through2 "2.X" -gulp-util@^3.0.0, gulp-util@^3.0.8: +gulp-uglify@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/gulp-uglify/-/gulp-uglify-3.0.1.tgz#8d3eee466521bea6b10fd75dff72adf8b7ea2d97" + dependencies: + gulplog "^1.0.0" + has-gulplog "^0.1.0" + lodash "^4.13.1" + make-error-cause "^1.1.1" + safe-buffer "^5.1.2" + through2 "^2.0.0" + uglify-js "^3.0.5" + vinyl-sourcemaps-apply "^0.2.0" + +gulp-util@^3.0.0: version "3.0.8" resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" dependencies: @@ -5474,6 +5487,16 @@ lru-queue@0.1: dependencies: es5-ext "~0.10.2" +make-error-cause@^1.1.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/make-error-cause/-/make-error-cause-1.2.2.tgz#df0388fcd0b37816dff0a5fb8108939777dcbc9d" + dependencies: + make-error "^1.2.0" + +make-error@^1.2.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8" + make-iterator@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" @@ -7586,7 +7609,7 @@ source-map@^0.4.2, source-map@^0.4.4: dependencies: amdefine ">=0.0.4" -source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1, source-map@~0.5.3: +source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1, source-map@~0.5.3: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -8120,6 +8143,13 @@ uglify-js@^2.6: optionalDependencies: uglify-to-browserify "~1.0.0" +uglify-js@^3.0.5: + version "3.4.9" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3" + dependencies: + commander "~2.17.1" + source-map "~0.6.1" + uglify-to-browserify@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" @@ -8336,6 +8366,12 @@ vinyl-source-stream@^2.0.0: through2 "^2.0.3" vinyl "^2.1.0" +vinyl-sourcemaps-apply@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz#ab6549d61d172c2b1b87be5c508d239c8ef87705" + dependencies: + source-map "^0.5.1" + vinyl@^0.4.0: version "0.4.6" resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.4.6.tgz#2f356c87a550a255461f36bbeb2a5ba8bf784847" From 51529f31866b6d7bde4c29583dcf69aa218cc472 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 6 Sep 2018 14:43:25 +0200 Subject: [PATCH 06/21] update ui-bundler in order to compress ui bundles --- scm-plugins/scm-git-plugin/package.json | 2 +- scm-plugins/scm-git-plugin/yarn.lock | 50 ++++++++++++++++--- scm-plugins/scm-hg-plugin/package.json | 2 +- scm-plugins/scm-hg-plugin/yarn.lock | 50 ++++++++++++++++--- scm-plugins/scm-svn-plugin/package.json | 2 +- scm-plugins/scm-svn-plugin/yarn.lock | 50 ++++++++++++++++--- .../packages/ui-components/package.json | 2 +- .../packages/ui-components/yarn.lock | 50 ++++++++++++++++--- .../packages/ui-types/package.json | 2 +- scm-ui-components/packages/ui-types/yarn.lock | 50 ++++++++++++++++--- 10 files changed, 220 insertions(+), 40 deletions(-) diff --git a/scm-plugins/scm-git-plugin/package.json b/scm-plugins/scm-git-plugin/package.json index 7fc8484d75..7ad3eeb799 100644 --- a/scm-plugins/scm-git-plugin/package.json +++ b/scm-plugins/scm-git-plugin/package.json @@ -9,6 +9,6 @@ "@scm-manager/ui-extensions": "^0.0.7" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.13" + "@scm-manager/ui-bundler": "^0.0.14" } } diff --git a/scm-plugins/scm-git-plugin/yarn.lock b/scm-plugins/scm-git-plugin/yarn.lock index 56d3e0ffe2..978800d63d 100644 --- a/scm-plugins/scm-git-plugin/yarn.lock +++ b/scm-plugins/scm-git-plugin/yarn.lock @@ -707,9 +707,9 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.13": - version "0.0.13" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.13.tgz#020e6c8ee870fccb6c451490cb18972ebfb0d2c4" +"@scm-manager/ui-bundler@^0.0.14": + version "0.0.14" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.14.tgz#f5a2f0a5b0a4e527c60e38ec53ce40ce7d3b4c6f" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -737,7 +737,7 @@ flow-bin "^0.79.1" gulp "^3.9.1" gulp-sourcemaps "^2.6.4" - gulp-util "^3.0.8" + gulp-uglify "^3.0.1" jest "^23.5.0" jest-junit "^5.1.0" node-mkdirs "^0.0.1" @@ -1867,7 +1867,7 @@ combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5, combined- dependencies: delayed-stream "~1.0.0" -commander@^2.11.0, commander@^2.17.1, commander@^2.2.0, commander@^2.9.0: +commander@^2.11.0, commander@^2.17.1, commander@^2.2.0, commander@^2.9.0, commander@~2.17.1: version "2.17.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" @@ -3285,7 +3285,20 @@ gulp-sourcemaps@^2.6.4: strip-bom-string "1.X" through2 "2.X" -gulp-util@^3.0.0, gulp-util@^3.0.8: +gulp-uglify@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/gulp-uglify/-/gulp-uglify-3.0.1.tgz#8d3eee466521bea6b10fd75dff72adf8b7ea2d97" + dependencies: + gulplog "^1.0.0" + has-gulplog "^0.1.0" + lodash "^4.13.1" + make-error-cause "^1.1.1" + safe-buffer "^5.1.2" + through2 "^2.0.0" + uglify-js "^3.0.5" + vinyl-sourcemaps-apply "^0.2.0" + +gulp-util@^3.0.0: version "3.0.8" resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" dependencies: @@ -4733,6 +4746,16 @@ lru-queue@0.1: dependencies: es5-ext "~0.10.2" +make-error-cause@^1.1.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/make-error-cause/-/make-error-cause-1.2.2.tgz#df0388fcd0b37816dff0a5fb8108939777dcbc9d" + dependencies: + make-error "^1.2.0" + +make-error@^1.2.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8" + make-iterator@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" @@ -6402,7 +6425,7 @@ source-map@^0.4.4: dependencies: amdefine ">=0.0.4" -source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1, source-map@~0.5.3: +source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1, source-map@~0.5.3: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -6857,6 +6880,13 @@ uglify-js@^2.6: optionalDependencies: uglify-to-browserify "~1.0.0" +uglify-js@^3.0.5: + version "3.4.9" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3" + dependencies: + commander "~2.17.1" + source-map "~0.6.1" + uglify-to-browserify@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" @@ -7033,6 +7063,12 @@ vinyl-source-stream@^2.0.0: through2 "^2.0.3" vinyl "^2.1.0" +vinyl-sourcemaps-apply@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz#ab6549d61d172c2b1b87be5c508d239c8ef87705" + dependencies: + source-map "^0.5.1" + vinyl@^0.4.0: version "0.4.6" resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.4.6.tgz#2f356c87a550a255461f36bbeb2a5ba8bf784847" diff --git a/scm-plugins/scm-hg-plugin/package.json b/scm-plugins/scm-hg-plugin/package.json index e6de568588..7d44b2df04 100644 --- a/scm-plugins/scm-hg-plugin/package.json +++ b/scm-plugins/scm-hg-plugin/package.json @@ -9,6 +9,6 @@ "@scm-manager/ui-extensions": "^0.0.7" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.13" + "@scm-manager/ui-bundler": "^0.0.14" } } diff --git a/scm-plugins/scm-hg-plugin/yarn.lock b/scm-plugins/scm-hg-plugin/yarn.lock index 251b5241a2..27562e7150 100644 --- a/scm-plugins/scm-hg-plugin/yarn.lock +++ b/scm-plugins/scm-hg-plugin/yarn.lock @@ -641,9 +641,9 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.13": - version "0.0.13" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.13.tgz#020e6c8ee870fccb6c451490cb18972ebfb0d2c4" +"@scm-manager/ui-bundler@^0.0.14": + version "0.0.14" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.14.tgz#f5a2f0a5b0a4e527c60e38ec53ce40ce7d3b4c6f" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -671,7 +671,7 @@ flow-bin "^0.79.1" gulp "^3.9.1" gulp-sourcemaps "^2.6.4" - gulp-util "^3.0.8" + gulp-uglify "^3.0.1" jest "^23.5.0" jest-junit "^5.1.0" node-mkdirs "^0.0.1" @@ -1801,7 +1801,7 @@ combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5, combined- dependencies: delayed-stream "~1.0.0" -commander@^2.11.0, commander@^2.17.1, commander@^2.2.0, commander@^2.9.0: +commander@^2.11.0, commander@^2.17.1, commander@^2.2.0, commander@^2.9.0, commander@~2.17.1: version "2.17.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" @@ -3217,7 +3217,20 @@ gulp-sourcemaps@^2.6.4: strip-bom-string "1.X" through2 "2.X" -gulp-util@^3.0.0, gulp-util@^3.0.8: +gulp-uglify@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/gulp-uglify/-/gulp-uglify-3.0.1.tgz#8d3eee466521bea6b10fd75dff72adf8b7ea2d97" + dependencies: + gulplog "^1.0.0" + has-gulplog "^0.1.0" + lodash "^4.13.1" + make-error-cause "^1.1.1" + safe-buffer "^5.1.2" + through2 "^2.0.0" + uglify-js "^3.0.5" + vinyl-sourcemaps-apply "^0.2.0" + +gulp-util@^3.0.0: version "3.0.8" resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" dependencies: @@ -4636,6 +4649,16 @@ lru-queue@0.1: dependencies: es5-ext "~0.10.2" +make-error-cause@^1.1.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/make-error-cause/-/make-error-cause-1.2.2.tgz#df0388fcd0b37816dff0a5fb8108939777dcbc9d" + dependencies: + make-error "^1.2.0" + +make-error@^1.2.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8" + make-iterator@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" @@ -6283,7 +6306,7 @@ source-map@^0.4.4: dependencies: amdefine ">=0.0.4" -source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1, source-map@~0.5.3: +source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1, source-map@~0.5.3: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -6739,6 +6762,13 @@ uglify-js@^2.6: optionalDependencies: uglify-to-browserify "~1.0.0" +uglify-js@^3.0.5: + version "3.4.9" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3" + dependencies: + commander "~2.17.1" + source-map "~0.6.1" + uglify-to-browserify@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" @@ -6915,6 +6945,12 @@ vinyl-source-stream@^2.0.0: through2 "^2.0.3" vinyl "^2.1.0" +vinyl-sourcemaps-apply@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz#ab6549d61d172c2b1b87be5c508d239c8ef87705" + dependencies: + source-map "^0.5.1" + vinyl@^0.4.0: version "0.4.6" resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.4.6.tgz#2f356c87a550a255461f36bbeb2a5ba8bf784847" diff --git a/scm-plugins/scm-svn-plugin/package.json b/scm-plugins/scm-svn-plugin/package.json index 98f0394833..7183dad38a 100644 --- a/scm-plugins/scm-svn-plugin/package.json +++ b/scm-plugins/scm-svn-plugin/package.json @@ -9,6 +9,6 @@ "@scm-manager/ui-extensions": "^0.0.7" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.13" + "@scm-manager/ui-bundler": "^0.0.14" } } diff --git a/scm-plugins/scm-svn-plugin/yarn.lock b/scm-plugins/scm-svn-plugin/yarn.lock index 251b5241a2..27562e7150 100644 --- a/scm-plugins/scm-svn-plugin/yarn.lock +++ b/scm-plugins/scm-svn-plugin/yarn.lock @@ -641,9 +641,9 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.13": - version "0.0.13" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.13.tgz#020e6c8ee870fccb6c451490cb18972ebfb0d2c4" +"@scm-manager/ui-bundler@^0.0.14": + version "0.0.14" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.14.tgz#f5a2f0a5b0a4e527c60e38ec53ce40ce7d3b4c6f" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -671,7 +671,7 @@ flow-bin "^0.79.1" gulp "^3.9.1" gulp-sourcemaps "^2.6.4" - gulp-util "^3.0.8" + gulp-uglify "^3.0.1" jest "^23.5.0" jest-junit "^5.1.0" node-mkdirs "^0.0.1" @@ -1801,7 +1801,7 @@ combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5, combined- dependencies: delayed-stream "~1.0.0" -commander@^2.11.0, commander@^2.17.1, commander@^2.2.0, commander@^2.9.0: +commander@^2.11.0, commander@^2.17.1, commander@^2.2.0, commander@^2.9.0, commander@~2.17.1: version "2.17.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" @@ -3217,7 +3217,20 @@ gulp-sourcemaps@^2.6.4: strip-bom-string "1.X" through2 "2.X" -gulp-util@^3.0.0, gulp-util@^3.0.8: +gulp-uglify@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/gulp-uglify/-/gulp-uglify-3.0.1.tgz#8d3eee466521bea6b10fd75dff72adf8b7ea2d97" + dependencies: + gulplog "^1.0.0" + has-gulplog "^0.1.0" + lodash "^4.13.1" + make-error-cause "^1.1.1" + safe-buffer "^5.1.2" + through2 "^2.0.0" + uglify-js "^3.0.5" + vinyl-sourcemaps-apply "^0.2.0" + +gulp-util@^3.0.0: version "3.0.8" resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" dependencies: @@ -4636,6 +4649,16 @@ lru-queue@0.1: dependencies: es5-ext "~0.10.2" +make-error-cause@^1.1.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/make-error-cause/-/make-error-cause-1.2.2.tgz#df0388fcd0b37816dff0a5fb8108939777dcbc9d" + dependencies: + make-error "^1.2.0" + +make-error@^1.2.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8" + make-iterator@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" @@ -6283,7 +6306,7 @@ source-map@^0.4.4: dependencies: amdefine ">=0.0.4" -source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1, source-map@~0.5.3: +source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1, source-map@~0.5.3: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -6739,6 +6762,13 @@ uglify-js@^2.6: optionalDependencies: uglify-to-browserify "~1.0.0" +uglify-js@^3.0.5: + version "3.4.9" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3" + dependencies: + commander "~2.17.1" + source-map "~0.6.1" + uglify-to-browserify@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" @@ -6915,6 +6945,12 @@ vinyl-source-stream@^2.0.0: through2 "^2.0.3" vinyl "^2.1.0" +vinyl-sourcemaps-apply@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz#ab6549d61d172c2b1b87be5c508d239c8ef87705" + dependencies: + source-map "^0.5.1" + vinyl@^0.4.0: version "0.4.6" resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.4.6.tgz#2f356c87a550a255461f36bbeb2a5ba8bf784847" diff --git a/scm-ui-components/packages/ui-components/package.json b/scm-ui-components/packages/ui-components/package.json index 1b8eeb517e..308a6f02b5 100644 --- a/scm-ui-components/packages/ui-components/package.json +++ b/scm-ui-components/packages/ui-components/package.json @@ -12,7 +12,7 @@ "eslint-fix": "eslint src --fix" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.13", + "@scm-manager/ui-bundler": "^0.0.14", "create-index": "^2.3.0", "enzyme": "^3.5.0", "enzyme-adapter-react-16": "^1.3.1", diff --git a/scm-ui-components/packages/ui-components/yarn.lock b/scm-ui-components/packages/ui-components/yarn.lock index e0d20269fd..3df28a0731 100644 --- a/scm-ui-components/packages/ui-components/yarn.lock +++ b/scm-ui-components/packages/ui-components/yarn.lock @@ -728,9 +728,9 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.13": - version "0.0.13" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.13.tgz#020e6c8ee870fccb6c451490cb18972ebfb0d2c4" +"@scm-manager/ui-bundler@^0.0.14": + version "0.0.14" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.14.tgz#f5a2f0a5b0a4e527c60e38ec53ce40ce7d3b4c6f" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -758,7 +758,7 @@ flow-bin "^0.79.1" gulp "^3.9.1" gulp-sourcemaps "^2.6.4" - gulp-util "^3.0.8" + gulp-uglify "^3.0.1" jest "^23.5.0" jest-junit "^5.1.0" node-mkdirs "^0.0.1" @@ -1983,7 +1983,7 @@ combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5, combined- dependencies: delayed-stream "~1.0.0" -commander@^2.11.0, commander@^2.17.1, commander@^2.2.0, commander@^2.9.0: +commander@^2.11.0, commander@^2.17.1, commander@^2.2.0, commander@^2.9.0, commander@~2.17.1: version "2.17.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" @@ -3605,7 +3605,20 @@ gulp-sourcemaps@^2.6.4: strip-bom-string "1.X" through2 "2.X" -gulp-util@^3.0.0, gulp-util@^3.0.8: +gulp-uglify@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/gulp-uglify/-/gulp-uglify-3.0.1.tgz#8d3eee466521bea6b10fd75dff72adf8b7ea2d97" + dependencies: + gulplog "^1.0.0" + has-gulplog "^0.1.0" + lodash "^4.13.1" + make-error-cause "^1.1.1" + safe-buffer "^5.1.2" + through2 "^2.0.0" + uglify-js "^3.0.5" + vinyl-sourcemaps-apply "^0.2.0" + +gulp-util@^3.0.0: version "3.0.8" resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" dependencies: @@ -5264,6 +5277,16 @@ lru-queue@0.1: dependencies: es5-ext "~0.10.2" +make-error-cause@^1.1.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/make-error-cause/-/make-error-cause-1.2.2.tgz#df0388fcd0b37816dff0a5fb8108939777dcbc9d" + dependencies: + make-error "^1.2.0" + +make-error@^1.2.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8" + make-iterator@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" @@ -7143,7 +7166,7 @@ source-map@^0.4.4: dependencies: amdefine ">=0.0.4" -source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1, source-map@~0.5.3: +source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1, source-map@~0.5.3: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -7627,6 +7650,13 @@ uglify-js@^2.6: optionalDependencies: uglify-to-browserify "~1.0.0" +uglify-js@^3.0.5: + version "3.4.9" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3" + dependencies: + commander "~2.17.1" + source-map "~0.6.1" + uglify-to-browserify@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" @@ -7839,6 +7869,12 @@ vinyl-source-stream@^2.0.0: through2 "^2.0.3" vinyl "^2.1.0" +vinyl-sourcemaps-apply@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz#ab6549d61d172c2b1b87be5c508d239c8ef87705" + dependencies: + source-map "^0.5.1" + vinyl@^0.4.0: version "0.4.6" resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.4.6.tgz#2f356c87a550a255461f36bbeb2a5ba8bf784847" diff --git a/scm-ui-components/packages/ui-types/package.json b/scm-ui-components/packages/ui-types/package.json index e9db94b9a6..5037199cbf 100644 --- a/scm-ui-components/packages/ui-types/package.json +++ b/scm-ui-components/packages/ui-types/package.json @@ -14,7 +14,7 @@ "check": "flow check" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.13" + "@scm-manager/ui-bundler": "^0.0.14" }, "browserify": { "transform": [ diff --git a/scm-ui-components/packages/ui-types/yarn.lock b/scm-ui-components/packages/ui-types/yarn.lock index 6c18546f36..2f7468447e 100644 --- a/scm-ui-components/packages/ui-types/yarn.lock +++ b/scm-ui-components/packages/ui-types/yarn.lock @@ -707,9 +707,9 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.13": - version "0.0.13" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.13.tgz#020e6c8ee870fccb6c451490cb18972ebfb0d2c4" +"@scm-manager/ui-bundler@^0.0.14": + version "0.0.14" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.14.tgz#f5a2f0a5b0a4e527c60e38ec53ce40ce7d3b4c6f" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -737,7 +737,7 @@ flow-bin "^0.79.1" gulp "^3.9.1" gulp-sourcemaps "^2.6.4" - gulp-util "^3.0.8" + gulp-uglify "^3.0.1" jest "^23.5.0" jest-junit "^5.1.0" node-mkdirs "^0.0.1" @@ -1856,7 +1856,7 @@ combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5, combined- dependencies: delayed-stream "~1.0.0" -commander@^2.11.0, commander@^2.17.1, commander@^2.2.0, commander@^2.9.0: +commander@^2.11.0, commander@^2.17.1, commander@^2.2.0, commander@^2.9.0, commander@~2.17.1: version "2.17.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" @@ -3252,7 +3252,20 @@ gulp-sourcemaps@^2.6.4: strip-bom-string "1.X" through2 "2.X" -gulp-util@^3.0.0, gulp-util@^3.0.8: +gulp-uglify@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/gulp-uglify/-/gulp-uglify-3.0.1.tgz#8d3eee466521bea6b10fd75dff72adf8b7ea2d97" + dependencies: + gulplog "^1.0.0" + has-gulplog "^0.1.0" + lodash "^4.13.1" + make-error-cause "^1.1.1" + safe-buffer "^5.1.2" + through2 "^2.0.0" + uglify-js "^3.0.5" + vinyl-sourcemaps-apply "^0.2.0" + +gulp-util@^3.0.0: version "3.0.8" resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" dependencies: @@ -4693,6 +4706,16 @@ lru-queue@0.1: dependencies: es5-ext "~0.10.2" +make-error-cause@^1.1.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/make-error-cause/-/make-error-cause-1.2.2.tgz#df0388fcd0b37816dff0a5fb8108939777dcbc9d" + dependencies: + make-error "^1.2.0" + +make-error@^1.2.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8" + make-iterator@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" @@ -6327,7 +6350,7 @@ source-map@^0.4.4: dependencies: amdefine ">=0.0.4" -source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1, source-map@~0.5.3: +source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1, source-map@~0.5.3: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -6778,6 +6801,13 @@ uglify-js@^2.6: optionalDependencies: uglify-to-browserify "~1.0.0" +uglify-js@^3.0.5: + version "3.4.9" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3" + dependencies: + commander "~2.17.1" + source-map "~0.6.1" + uglify-to-browserify@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" @@ -6954,6 +6984,12 @@ vinyl-source-stream@^2.0.0: through2 "^2.0.3" vinyl "^2.1.0" +vinyl-sourcemaps-apply@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz#ab6549d61d172c2b1b87be5c508d239c8ef87705" + dependencies: + source-map "^0.5.1" + vinyl@^0.4.0: version "0.4.6" resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.4.6.tgz#2f356c87a550a255461f36bbeb2a5ba8bf784847" From 9c80da5838845e1cb035b30e04f02f8c0b3684ec Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 6 Sep 2018 14:44:01 +0200 Subject: [PATCH 07/21] use scm-ui as overlay war, if livereload property was not set --- scm-webapp/pom.xml | 39 +++++++++++++++++++++++++-- scm-webapp/src/main/webapp/index.html | 10 ------- 2 files changed, 37 insertions(+), 12 deletions(-) delete mode 100644 scm-webapp/src/main/webapp/index.html diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index fb5cc57e44..45d2808d1d 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -515,7 +515,7 @@ sonia.scm.ui.proxy - http://localhost:3000 + ${livereload.proxy} @@ -546,7 +546,42 @@ - + + + livereload + + + + livereload + + + + + http://localhost:3000 + + + + + ui-overlay + + + + !livereload + + + + + + + sonia.scm + scm-ui + 2.0.0-SNAPSHOT + war + + + + + release diff --git a/scm-webapp/src/main/webapp/index.html b/scm-webapp/src/main/webapp/index.html deleted file mode 100644 index e149a39434..0000000000 --- a/scm-webapp/src/main/webapp/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - Title - - - - - From faea3cddcef8a59b363c9bcffa8bd23891443e4d Mon Sep 17 00:00:00 2001 From: Mohamed Karray Date: Thu, 6 Sep 2018 14:57:52 +0200 Subject: [PATCH 08/21] add crlf exception and exception mapper --- .../main/java/sonia/scm/api/v2/resources/DiffRootResource.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java index 1a2b3d2189..99a996b02f 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java @@ -4,8 +4,6 @@ import com.webcohesion.enunciate.metadata.rs.ResponseCode; import com.webcohesion.enunciate.metadata.rs.StatusCodes; import sonia.scm.NotFoundException; import sonia.scm.repository.NamespaceAndName; -import sonia.scm.repository.Repository; -import sonia.scm.repository.RepositoryPermissions; import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; From 8ec7677b754945d036713b5c41753193d0c6b2f0 Mon Sep 17 00:00:00 2001 From: Mohamed Karray Date: Mon, 10 Sep 2018 09:51:59 +0200 Subject: [PATCH 09/21] integration tests for the file history endpoint --- .../sonia/scm/it/RepositoryAccessITCase.java | 37 ++- .../java/sonia/scm/it/RepositoryRequests.java | 245 ++++++++++++++++++ .../java/sonia/scm/it/RepositoryUtil.java | 4 +- .../v2/resources/FileHistoryRootResource.java | 14 + 4 files changed, 296 insertions(+), 4 deletions(-) create mode 100644 scm-it/src/test/java/sonia/scm/it/RepositoryRequests.java diff --git a/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java b/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java index a9139f18c8..e3603d258b 100644 --- a/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java @@ -8,6 +8,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import sonia.scm.repository.Changeset; import sonia.scm.repository.client.api.ClientCommand; import sonia.scm.repository.client.api.RepositoryClient; @@ -16,10 +17,12 @@ import java.io.IOException; import java.util.Collection; import java.util.List; -import static org.assertj.core.api.Assertions.assertThat; import static java.lang.Thread.sleep; +import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertNotNull; +import static sonia.scm.it.RestUtil.ADMIN_PASSWORD; +import static sonia.scm.it.RestUtil.ADMIN_USERNAME; import static sonia.scm.it.RestUtil.given; import static sonia.scm.it.ScmTypes.availableScmTypes; @@ -31,6 +34,7 @@ public class RepositoryAccessITCase { private final String repositoryType; private File folder; + private RepositoryRequests.AppliedRepositoryGetRequest repositoryGetRequest; public RepositoryAccessITCase(String repositoryType) { this.repositoryType = repositoryType; @@ -42,9 +46,15 @@ public class RepositoryAccessITCase { } @Before - public void initClient() { + public void init() { TestData.createDefault(); folder = tempFolder.getRoot(); + repositoryGetRequest = RepositoryRequests.start() + .given() + .url(TestData.getDefaultRepositoryUrl(repositoryType)) + .usernameAndPassword(ADMIN_USERNAME, ADMIN_PASSWORD) + .get() + .assertStatusCode(HttpStatus.SC_OK); } @Test @@ -152,5 +162,28 @@ public class RepositoryAccessITCase { assertThat(changesets).size().isBetween(2, 3); // svn has an implicit root revision '0' that is extra to the two commits } + + @Test + @SuppressWarnings("unchecked") + public void shouldFindFileHistory() throws IOException { + RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder); + + String fileName_1 = "a.txt"; + Changeset changeset_1 = RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName_1, "a"); + + repositoryGetRequest + .usingRepositoryResponse() + .requestSources() + .usingSourcesResponse() + .requestFileHistory(fileName_1) + .assertStatusCode(HttpStatus.SC_OK) + .usingChangesetsResponse() + .assertChangesets(changesets -> { + assertThat(changesets).hasSize(1); + assertThat(changesets.get(0)).containsEntry("id", changeset_1.getId()); + assertThat(changesets.get(0)).containsEntry("description", changeset_1.getDescription()); + } + ); + } } diff --git a/scm-it/src/test/java/sonia/scm/it/RepositoryRequests.java b/scm-it/src/test/java/sonia/scm/it/RepositoryRequests.java new file mode 100644 index 0000000000..62ce82aded --- /dev/null +++ b/scm-it/src/test/java/sonia/scm/it/RepositoryRequests.java @@ -0,0 +1,245 @@ +package sonia.scm.it; + +import io.restassured.RestAssured; +import io.restassured.response.Response; + +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + + +/** + * Encapsulate rest requests of a repository in builder pattern + *

+ * A Get Request can be applied with the methods request*() + * These methods return a AppliedGet*Request object + * This object can be used to apply general assertions over the rest Assured response + * In the AppliedGet*Request classes there is a using*Response() method + * that return the *Response class containing specific operations related to the specific response + * the *Response class contains also the request*() method to apply the next GET request from a link in the response. + */ +public class RepositoryRequests { + + private String url; + private String username; + private String password; + + static RepositoryRequests start() { + return new RepositoryRequests(); + } + + Given given() { + return new Given(); + } + + + /** + * Apply a GET Request to the extracted url from the given link + * + * @param linkPropertyName the property name of link + * @param response the response containing the link + * @return the response of the GET request using the given link + */ + private Response getResponseFromLink(Response response, String linkPropertyName) { + return getResponse(response + .then() + .extract() + .path(linkPropertyName)); + } + + + /** + * Apply a GET Request to the given url and return the response. + * + * @param url the url of the GET request + * @return the response of the GET request using the given url + */ + private Response getResponse(String url) { + return RestAssured.given() + .auth().preemptive().basic(username, password) + .when() + .get(url); + } + + private void setUrl(String url) { + this.url = url; + } + + private void setUsername(String username) { + this.username = username; + } + + private void setPassword(String password) { + this.password = password; + } + + private String getUrl() { + return url; + } + + private String getUsername() { + return username; + } + + private String getPassword() { + return password; + } + + class Given { + + GivenUrl url(String url) { + setUrl(url); + return new GivenUrl(); + } + + } + + class GivenWithUrlAndAuth { + AppliedRepositoryGetRequest get() { + return new AppliedRepositoryGetRequest( + getResponse(url) + ); + } + } + + class AppliedGetRequest { + private Response response; + + public AppliedGetRequest(Response response) { + this.response = response; + } + + /** + * apply custom assertions to the actual response + * + * @param consumer consume the response in order to assert the content. the header, the payload etc.. + * @return the self object + */ + SELF assertResponse(Consumer consumer) { + consumer.accept(response); + return (SELF) this; + } + + /** + * special assertion of the status code + * + * @param expectedStatusCode the expected status code + * @return the self object + */ + SELF assertStatusCode(int expectedStatusCode) { + this.response.then().assertThat().statusCode(expectedStatusCode); + return (SELF) this; + } + + } + + class AppliedRepositoryGetRequest extends AppliedGetRequest { + + AppliedRepositoryGetRequest(Response response) { + super(response); + } + + RepositoryResponse usingRepositoryResponse() { + return new RepositoryResponse(super.response); + } + } + + class RepositoryResponse { + + private Response repositoryResponse; + + public RepositoryResponse(Response repositoryResponse) { + this.repositoryResponse = repositoryResponse; + } + + AppliedGetSourcesRequest requestSources() { + return new AppliedGetSourcesRequest(getResponseFromLink(repositoryResponse, "_links.sources.href")); + } + + AppliedGetChangesetsRequest requestChangesets(String fileName) { + return new AppliedGetChangesetsRequest(getResponseFromLink(repositoryResponse, "_links.changesets.href")); + } + } + + class AppliedGetChangesetsRequest extends AppliedGetRequest { + + AppliedGetChangesetsRequest(Response response) { + super(response); + } + + ChangesetsResponse usingChangesetsResponse() { + return new ChangesetsResponse(super.response); + } + } + + class ChangesetsResponse { + private Response changesetsResponse; + + public ChangesetsResponse(Response changesetsResponse) { + this.changesetsResponse = changesetsResponse; + } + + ChangesetsResponse assertChangesets(Consumer> changesetsConsumer) { + List changesets = changesetsResponse.then().extract().path("_embedded.changesets"); + changesetsConsumer.accept(changesets); + return this; + } + + AppliedGetDiffRequest requestDiff(String revision) { + return new AppliedGetDiffRequest(getResponseFromLink(changesetsResponse, "_embedded.changesets.find{it.id=='" + revision + "'}._links.diff.href")); + } + + } + + class AppliedGetSourcesRequest extends AppliedGetRequest { + + public AppliedGetSourcesRequest(Response sourcesResponse) { + super(sourcesResponse); + } + + SourcesResponse usingSourcesResponse() { + return new SourcesResponse(super.response); + } + } + + class SourcesResponse { + + private Response sourcesResponse; + + SourcesResponse(Response sourcesResponse) { + this.sourcesResponse = sourcesResponse; + } + + SourcesResponse assertRevision(Consumer assertRevision) { + String revision = sourcesResponse.then().extract().path("revision"); + assertRevision.accept(revision); + return this; + } + + SourcesResponse assertFiles(Consumer assertFiles) { + List files = sourcesResponse.then().extract().path("files"); + assertFiles.accept(files); + return this; + } + + AppliedGetChangesetsRequest requestFileHistory(String fileName) { + return new AppliedGetChangesetsRequest(getResponseFromLink(sourcesResponse, "files.find{it.name=='" + fileName + "'}._links.history.href")); + } + } + + class AppliedGetDiffRequest extends AppliedGetRequest { + + AppliedGetDiffRequest(Response response) { + super(response); + } + } + + class GivenUrl { + + GivenWithUrlAndAuth usernameAndPassword(String username, String password) { + setUsername(username); + setPassword(password); + return new GivenWithUrlAndAuth(); + } + } +} diff --git a/scm-it/src/test/java/sonia/scm/it/RepositoryUtil.java b/scm-it/src/test/java/sonia/scm/it/RepositoryUtil.java index e49927b1b9..b2d0f44578 100644 --- a/scm-it/src/test/java/sonia/scm/it/RepositoryUtil.java +++ b/scm-it/src/test/java/sonia/scm/it/RepositoryUtil.java @@ -40,11 +40,11 @@ public class RepositoryUtil { return name; } - static void createAndCommitFile(RepositoryClient repositoryClient, String username, String fileName, String content) throws IOException { + static Changeset createAndCommitFile(RepositoryClient repositoryClient, String username, String fileName, String content) throws IOException { File file = new File(repositoryClient.getWorkingCopy(), fileName); Files.write(content, file, Charsets.UTF_8); addWithParentDirectories(repositoryClient, file); - commit(repositoryClient, username, "added " + fileName); + return commit(repositoryClient, username, "added " + fileName); } private static String addWithParentDirectories(RepositoryClient repositoryClient, File file) throws IOException { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java index d56590b384..118cc4167a 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java @@ -40,6 +40,20 @@ public class FileHistoryRootResource { this.fileHistoryCollectionToDtoMapper = fileHistoryCollectionToDtoMapper; } + /** + * Get all changesets related to the given file starting with the given revision + * + * @param namespace the repository namespace + * @param name the repository name + * @param revision the revision + * @param path the path of the file + * @param page pagination + * @param pageSize pagination + * @return all changesets related to the given file starting with the given revision + * @throws IOException on io error + * @throws RevisionNotFoundException on missing revision + * @throws RepositoryNotFoundException on missing repository + */ @GET @Path("{revision}/{path: .*}") @StatusCodes({ From 72a5b6f43c0485e2f31f16b1d76bc5c36ed4c8a3 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 10 Sep 2018 12:04:56 +0200 Subject: [PATCH 10/21] use web-resources to deliver web resources from the WebResourceServlet --- scm-webapp/pom.xml | 7 +++++++ .../java/sonia/scm/WebResourceServlet.java | 18 +++++++++++------- .../java/sonia/scm/WebResourceServletTest.java | 2 +- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 45d2808d1d..95c90fcf51 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -237,6 +237,12 @@ ${mustache.version} + + com.github.sdorra + web-resources + 1.0.1 + + com.github.sdorra spotter-core @@ -248,6 +254,7 @@ tika-core 1.18 + diff --git a/scm-webapp/src/main/java/sonia/scm/WebResourceServlet.java b/scm-webapp/src/main/java/sonia/scm/WebResourceServlet.java index 764e4f18d2..98117851db 100644 --- a/scm-webapp/src/main/java/sonia/scm/WebResourceServlet.java +++ b/scm-webapp/src/main/java/sonia/scm/WebResourceServlet.java @@ -1,7 +1,7 @@ package sonia.scm; +import com.github.sdorra.webresources.WebResourceSender; import com.google.common.annotations.VisibleForTesting; -import com.google.common.io.Resources; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.filter.WebElement; @@ -15,7 +15,6 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.io.OutputStream; import java.net.URL; /** @@ -27,6 +26,7 @@ import java.net.URL; @WebElement(value = WebResourceServlet.PATTERN, regex = true) public class WebResourceServlet extends HttpServlet { + /** * exclude api requests and the old frontend servlets. * @@ -37,6 +37,11 @@ public class WebResourceServlet extends HttpServlet { private static final Logger LOG = LoggerFactory.getLogger(WebResourceServlet.class); + private final WebResourceSender sender = WebResourceSender.create() + .withGZIP() + .withGZIPMinLength(512) + .withBufferSize(16384); + private final UberWebResourceLoader webResourceLoader; private final PushStateDispatcher pushStateDispatcher; @@ -53,7 +58,7 @@ public class WebResourceServlet extends HttpServlet { LOG.trace("try to load {}", uri); URL url = webResourceLoader.getResource(uri); if (url != null) { - serveResource(response, url); + serveResource(request, response, url); } else { dispatch(request, response, uri); } @@ -72,10 +77,9 @@ public class WebResourceServlet extends HttpServlet { return HttpUtil.getStrippedURI(request); } - private void serveResource(HttpServletResponse response, URL url) { - // TODO lastModifiedDate, if-... ??? - try (OutputStream output = response.getOutputStream()) { - Resources.copy(url, output); + private void serveResource(HttpServletRequest request, HttpServletResponse response, URL url) { + try { + sender.resource(url).send(request, response); } catch (IOException ex) { LOG.warn("failed to serve resource: {}", url); response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); diff --git a/scm-webapp/src/test/java/sonia/scm/WebResourceServletTest.java b/scm-webapp/src/test/java/sonia/scm/WebResourceServletTest.java index fa39239d5d..b3864d894f 100644 --- a/scm-webapp/src/test/java/sonia/scm/WebResourceServletTest.java +++ b/scm-webapp/src/test/java/sonia/scm/WebResourceServletTest.java @@ -22,7 +22,7 @@ import java.io.IOException; import static org.junit.Assert.*; import static org.mockito.Mockito.*; -@RunWith(MockitoJUnitRunner.class) +@RunWith(MockitoJUnitRunner.Silent.class) public class WebResourceServletTest { @Rule From e6e5fee54c5383017e191a9c2316ebf02ef9e2bf Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 10 Sep 2018 12:54:52 +0200 Subject: [PATCH 11/21] update web-resources to v1.0.2 --- scm-webapp/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 95c90fcf51..761a28730c 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -240,7 +240,7 @@ com.github.sdorra web-resources - 1.0.1 + 1.0.2 From f57d87b257bd565fc2a7064c46572e024b86a455 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 10 Sep 2018 11:06:03 +0000 Subject: [PATCH 12/21] Close branch feature/diff_endpoint_v2 From 13b8b922734e22c8f1d7aa59f7b918ce4754d4ba Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 10 Sep 2018 13:44:42 +0200 Subject: [PATCH 13/21] fixed web resource path loading on windows --- .../sonia/scm/plugin/WebResourceLoader.java | 4 +- .../scm/plugin/PathWebResourceLoader.java | 85 +++---------------- 2 files changed, 14 insertions(+), 75 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/plugin/WebResourceLoader.java b/scm-core/src/main/java/sonia/scm/plugin/WebResourceLoader.java index 94b31ac844..61466c0e2c 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/WebResourceLoader.java +++ b/scm-core/src/main/java/sonia/scm/plugin/WebResourceLoader.java @@ -53,9 +53,11 @@ public interface WebResourceLoader * Returns a {@link URL} for the given path. The method will return null if no * resources could be found for the given path. * + * Note: The path is a web path and uses "/" as path separator + * * @param path resource path * * @return url object for the given path or null */ - public URL getResource(String path); + URL getResource(String path); } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PathWebResourceLoader.java b/scm-webapp/src/main/java/sonia/scm/plugin/PathWebResourceLoader.java index 230cfa6c7a..4b9e502c4d 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PathWebResourceLoader.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PathWebResourceLoader.java @@ -31,18 +31,11 @@ package sonia.scm.plugin; -//~--- non-JDK imports -------------------------------------------------------- - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -//~--- JDK imports ------------------------------------------------------------ - -import java.io.File; - import java.net.MalformedURLException; import java.net.URL; - import java.nio.file.Files; import java.nio.file.Path; @@ -55,47 +48,27 @@ import java.nio.file.Path; public class PathWebResourceLoader implements WebResourceLoader { - /** Field description */ - private static final String DEFAULT_SEPARATOR = "/"; + private static final String SEPARATOR = "/"; /** * the logger for PathWebResourceLoader */ - private static final Logger logger = + private static final Logger LOG = LoggerFactory.getLogger(PathWebResourceLoader.class); - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - * - * @param directory - */ public PathWebResourceLoader(Path directory) { this.directory = directory; } - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param path - * - * @return - */ @Override - public URL getResource(String path) - { + public URL getResource(String path) { URL resource = null; Path file = directory.resolve(filePath(path)); if (Files.exists(file) && ! Files.isDirectory(file)) { - logger.trace("found path {} at {}", path, file); + LOG.trace("found path {} at {}", path, file); try { @@ -103,56 +76,20 @@ public class PathWebResourceLoader implements WebResourceLoader } catch (MalformedURLException ex) { - logger.error("could not transform path to url", ex); + LOG.error("could not transform path to url", ex); } + } else { + LOG.trace("could not find file {}", file); } return resource; } - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param path - * - * @return - */ - private String filePath(String path) - { - - // TODO handle illegal path parts, such as .. - String filePath = filePath(DEFAULT_SEPARATOR, path); - - if (!DEFAULT_SEPARATOR.equals(File.separator)) - { - filePath = filePath(File.separator, path); + private String filePath(String path) { + if (path.startsWith(SEPARATOR)) { + return path.substring(1); } - - return filePath; - } - - /** - * Method description - * - * - * @param separator - * @param path - * - * @return - */ - private String filePath(String separator, String path) - { - String filePath = path; - - if (filePath.startsWith(separator)) - { - filePath = filePath.substring(separator.length()); - } - - return filePath; + return path; } //~--- fields --------------------------------------------------------------- From 0cb44e3317bd5c865c258fd13a194b61e26a5217 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 10 Sep 2018 14:52:32 +0200 Subject: [PATCH 14/21] allow changing context path, by using a template for index.html --- scm-ui/public/{index.html => index.mustache} | 23 ++----- .../scm/ForwardingPushStateDispatcher.java | 27 -------- .../scm/PushStateDispatcherProvider.java | 12 +++- .../scm/TemplatingPushStateDispatcher.java | 61 +++++++++++++++++ .../ForwardingPushStateDispatcherTest.java | 63 ------------------ .../scm/PushStateDispatcherProviderTest.java | 17 +++-- .../TemplatingPushStateDispatcherTest.java | 66 +++++++++++++++++++ 7 files changed, 157 insertions(+), 112 deletions(-) rename scm-ui/public/{index.html => index.mustache} (56%) delete mode 100644 scm-webapp/src/main/java/sonia/scm/ForwardingPushStateDispatcher.java create mode 100644 scm-webapp/src/main/java/sonia/scm/TemplatingPushStateDispatcher.java delete mode 100644 scm-webapp/src/test/java/sonia/scm/ForwardingPushStateDispatcherTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/TemplatingPushStateDispatcherTest.java diff --git a/scm-ui/public/index.html b/scm-ui/public/index.mustache similarity index 56% rename from scm-ui/public/index.html rename to scm-ui/public/index.mustache index 23dc1b9782..802be2ca97 100644 --- a/scm-ui/public/index.html +++ b/scm-ui/public/index.mustache @@ -8,20 +8,11 @@ manifest.json provides metadata used when your web app is added to the homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/ --> - - - - - - + + SCM-Manager @@ -41,9 +32,9 @@ To create a production bundle, use `npm run build` or `yarn build`. --> - - + + diff --git a/scm-webapp/src/main/java/sonia/scm/ForwardingPushStateDispatcher.java b/scm-webapp/src/main/java/sonia/scm/ForwardingPushStateDispatcher.java deleted file mode 100644 index ae9ef97499..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/ForwardingPushStateDispatcher.java +++ /dev/null @@ -1,27 +0,0 @@ -package sonia.scm; - -import sonia.scm.util.HttpUtil; - -import javax.servlet.RequestDispatcher; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -/** - * This dispatcher forwards every request to the index.html of the application. - * - * @since 2.0.0 - */ -public class ForwardingPushStateDispatcher implements PushStateDispatcher { - @Override - public void dispatch(HttpServletRequest request, HttpServletResponse response, String uri) throws IOException { - String path = HttpUtil.append(request.getContextPath(), "index.html"); - RequestDispatcher dispatcher = request.getRequestDispatcher(path); - try { - dispatcher.forward(request, response); - } catch (ServletException e) { - throw new IOException("failed to forward request", e); - } - } -} diff --git a/scm-webapp/src/main/java/sonia/scm/PushStateDispatcherProvider.java b/scm-webapp/src/main/java/sonia/scm/PushStateDispatcherProvider.java index 653f7b4bdc..f0d2807497 100644 --- a/scm-webapp/src/main/java/sonia/scm/PushStateDispatcherProvider.java +++ b/scm-webapp/src/main/java/sonia/scm/PushStateDispatcherProvider.java @@ -3,12 +3,13 @@ package sonia.scm; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; +import javax.inject.Inject; import javax.inject.Provider; /** * Injection Provider for the {@link PushStateDispatcher}. The provider will return a {@link ProxyPushStateDispatcher} * if the system property {@code PushStateDispatcherProvider#PROPERTY_TARGET} is set to a proxy target url, otherwise - * a {@link ForwardingPushStateDispatcher} is used. + * a {@link TemplatingPushStateDispatcher} is used. * * @since 2.0.0 */ @@ -17,11 +18,18 @@ public class PushStateDispatcherProvider implements Provider templatingPushStateDispatcherProvider; + + @Inject + public PushStateDispatcherProvider(Provider templatingPushStateDispatcherProvider) { + this.templatingPushStateDispatcherProvider = templatingPushStateDispatcherProvider; + } + @Override public PushStateDispatcher get() { String target = System.getProperty(PROPERTY_TARGET); if (Strings.isNullOrEmpty(target)) { - return new ForwardingPushStateDispatcher(); + return templatingPushStateDispatcherProvider.get(); } return new ProxyPushStateDispatcher(target); } diff --git a/scm-webapp/src/main/java/sonia/scm/TemplatingPushStateDispatcher.java b/scm-webapp/src/main/java/sonia/scm/TemplatingPushStateDispatcher.java new file mode 100644 index 0000000000..6652975c4a --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/TemplatingPushStateDispatcher.java @@ -0,0 +1,61 @@ +package sonia.scm; + +import com.google.common.annotations.VisibleForTesting; +import sonia.scm.template.Template; +import sonia.scm.template.TemplateEngine; +import sonia.scm.template.TemplateEngineFactory; + +import javax.inject.Inject; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.Writer; + +/** + * This dispatcher renders the /index.mustache template, which is merged in from the scm-ui package. + * + * @since 2.0.0 + */ +public class TemplatingPushStateDispatcher implements PushStateDispatcher { + + @VisibleForTesting + static final String TEMPLATE = "/index.mustache"; + + private final TemplateEngine templateEngine; + + @Inject + public TemplatingPushStateDispatcher(TemplateEngineFactory templateEngineFactory) { + this(templateEngineFactory.getDefaultEngine()); + } + + @VisibleForTesting + TemplatingPushStateDispatcher(TemplateEngine templateEngine) { + this.templateEngine = templateEngine; + } + + @Override + public void dispatch(HttpServletRequest request, HttpServletResponse response, String uri) throws IOException { + response.setContentType("text/html"); + response.setCharacterEncoding("UTF-8"); + + Template template = templateEngine.getTemplate(TEMPLATE); + try (Writer writer = response.getWriter()) { + template.execute(writer, new IndexHtmlModel(request)); + } + } + + @VisibleForTesting + static class IndexHtmlModel { + + private final HttpServletRequest request; + + private IndexHtmlModel(HttpServletRequest request) { + this.request = request; + } + + public String getContextPath() { + return request.getContextPath(); + } + + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/ForwardingPushStateDispatcherTest.java b/scm-webapp/src/test/java/sonia/scm/ForwardingPushStateDispatcherTest.java deleted file mode 100644 index c5a42d2346..0000000000 --- a/scm-webapp/src/test/java/sonia/scm/ForwardingPushStateDispatcherTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package sonia.scm; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; - -import javax.servlet.RequestDispatcher; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; - -import static org.junit.Assert.*; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@RunWith(MockitoJUnitRunner.class) -public class ForwardingPushStateDispatcherTest { - - @Mock - private HttpServletRequest request; - - @Mock - private RequestDispatcher requestDispatcher; - - @Mock - private HttpServletResponse response; - - private ForwardingPushStateDispatcher dispatcher = new ForwardingPushStateDispatcher(); - - @Test - public void testDispatch() throws ServletException, IOException { - when(request.getContextPath()).thenReturn(""); - when(request.getRequestDispatcher("/index.html")).thenReturn(requestDispatcher); - - dispatcher.dispatch(request, response, "/something"); - - verify(requestDispatcher).forward(request, response); - } - - @Test - public void testDispatchWithContextPath() throws ServletException, IOException { - when(request.getContextPath()).thenReturn("/scm"); - when(request.getRequestDispatcher("/scm/index.html")).thenReturn(requestDispatcher); - - dispatcher.dispatch(request, response, "/something"); - - verify(requestDispatcher).forward(request, response); - } - - @Test(expected = IOException.class) - public void testWrapServletException() throws ServletException, IOException { - when(request.getContextPath()).thenReturn(""); - when(request.getRequestDispatcher("/index.html")).thenReturn(requestDispatcher); - doThrow(ServletException.class).when(requestDispatcher).forward(request, response); - - dispatcher.dispatch(request, response, "/something"); - } - -} diff --git a/scm-webapp/src/test/java/sonia/scm/PushStateDispatcherProviderTest.java b/scm-webapp/src/test/java/sonia/scm/PushStateDispatcherProviderTest.java index 31e5f7c6dc..4316d9bc06 100644 --- a/scm-webapp/src/test/java/sonia/scm/PushStateDispatcherProviderTest.java +++ b/scm-webapp/src/test/java/sonia/scm/PushStateDispatcherProviderTest.java @@ -1,14 +1,23 @@ package sonia.scm; +import com.google.inject.util.Providers; import org.assertj.core.api.Assertions; import org.junit.After; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import sonia.scm.template.TemplateEngine; -import static org.junit.Assert.*; - +@RunWith(MockitoJUnitRunner.class) public class PushStateDispatcherProviderTest { - private PushStateDispatcherProvider provider = new PushStateDispatcherProvider(); + @Mock + private TemplateEngine templateEngine; + + private PushStateDispatcherProvider provider = new PushStateDispatcherProvider( + Providers.of(new TemplatingPushStateDispatcher(templateEngine)) + ); @Test public void testGetProxyPushStateWithPropertySet() { @@ -20,7 +29,7 @@ public class PushStateDispatcherProviderTest { @Test public void testGetProxyPushStateWithoutProperty() { PushStateDispatcher dispatcher = provider.get(); - Assertions.assertThat(dispatcher).isInstanceOf(ForwardingPushStateDispatcher.class); + Assertions.assertThat(dispatcher).isInstanceOf(TemplatingPushStateDispatcher.class); } @After diff --git a/scm-webapp/src/test/java/sonia/scm/TemplatingPushStateDispatcherTest.java b/scm-webapp/src/test/java/sonia/scm/TemplatingPushStateDispatcherTest.java new file mode 100644 index 0000000000..126ba9ac0f --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/TemplatingPushStateDispatcherTest.java @@ -0,0 +1,66 @@ +package sonia.scm; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import sonia.scm.template.Template; +import sonia.scm.template.TemplateEngine; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class TemplatingPushStateDispatcherTest { + + @Mock + private HttpServletRequest request; + + @Mock + private HttpServletResponse response; + + @Mock + private TemplateEngine templateEngine; + + @Mock + private Template template; + + private TemplatingPushStateDispatcher dispatcher; + + @Before + public void setUpMocks() { + dispatcher = new TemplatingPushStateDispatcher(templateEngine); + } + + @Test + public void testDispatch() throws IOException { + when(request.getContextPath()).thenReturn("/scm"); + when(templateEngine.getTemplate(TemplatingPushStateDispatcher.TEMPLATE)).thenReturn(template); + + when(response.getWriter()).thenReturn(new PrintWriter(new StringWriter())); + + dispatcher.dispatch(request, response, "/someurl"); + + verify(response).setContentType("text/html"); + verify(response).setCharacterEncoding("UTF-8"); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Object.class); + + verify(template).execute(any(Writer.class), captor.capture()); + + TemplatingPushStateDispatcher.IndexHtmlModel model = (TemplatingPushStateDispatcher.IndexHtmlModel) captor.getValue(); + assertEquals("/scm", model.getContextPath()); + } + +} From 78f19ecfa3bf2f46f5b0e9c9f9c12c0365753f42 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 10 Sep 2018 15:11:51 +0200 Subject: [PATCH 15/21] update ui-bundler to version 0.0.15 --- scm-plugins/scm-git-plugin/package.json | 2 +- scm-plugins/scm-git-plugin/yarn.lock | 11 ++++++++--- scm-plugins/scm-hg-plugin/package.json | 2 +- scm-plugins/scm-hg-plugin/yarn.lock | 11 ++++++++--- scm-plugins/scm-svn-plugin/package.json | 2 +- scm-plugins/scm-svn-plugin/yarn.lock | 11 ++++++++--- scm-ui-components/packages/ui-components/package.json | 2 +- scm-ui-components/packages/ui-components/yarn.lock | 11 ++++++++--- scm-ui-components/packages/ui-types/package.json | 2 +- scm-ui-components/packages/ui-types/yarn.lock | 11 ++++++++--- scm-ui/package.json | 2 +- scm-ui/yarn.lock | 11 ++++++++--- 12 files changed, 54 insertions(+), 24 deletions(-) diff --git a/scm-plugins/scm-git-plugin/package.json b/scm-plugins/scm-git-plugin/package.json index 7ad3eeb799..93ec098597 100644 --- a/scm-plugins/scm-git-plugin/package.json +++ b/scm-plugins/scm-git-plugin/package.json @@ -9,6 +9,6 @@ "@scm-manager/ui-extensions": "^0.0.7" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.14" + "@scm-manager/ui-bundler": "^0.0.15" } } diff --git a/scm-plugins/scm-git-plugin/yarn.lock b/scm-plugins/scm-git-plugin/yarn.lock index 978800d63d..702e28711f 100644 --- a/scm-plugins/scm-git-plugin/yarn.lock +++ b/scm-plugins/scm-git-plugin/yarn.lock @@ -707,9 +707,9 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.14": - version "0.0.14" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.14.tgz#f5a2f0a5b0a4e527c60e38ec53ce40ce7d3b4c6f" +"@scm-manager/ui-bundler@^0.0.15": + version "0.0.15" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.15.tgz#8ed4a557d5ae38d6b99493b29608fd6a4c9cd917" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -740,6 +740,7 @@ gulp-uglify "^3.0.1" jest "^23.5.0" jest-junit "^5.1.0" + mustache "^2.3.2" node-mkdirs "^0.0.1" pom-parser "^1.1.1" prettier "^1.14.2" @@ -4978,6 +4979,10 @@ multipipe@^0.1.2: dependencies: duplexer2 "0.0.2" +mustache@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.2.tgz#a6d4d9c3f91d13359ab889a812954f9230a3d0c5" + mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" diff --git a/scm-plugins/scm-hg-plugin/package.json b/scm-plugins/scm-hg-plugin/package.json index 7d44b2df04..c5907d38bc 100644 --- a/scm-plugins/scm-hg-plugin/package.json +++ b/scm-plugins/scm-hg-plugin/package.json @@ -9,6 +9,6 @@ "@scm-manager/ui-extensions": "^0.0.7" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.14" + "@scm-manager/ui-bundler": "^0.0.15" } } diff --git a/scm-plugins/scm-hg-plugin/yarn.lock b/scm-plugins/scm-hg-plugin/yarn.lock index 27562e7150..8822bd5e57 100644 --- a/scm-plugins/scm-hg-plugin/yarn.lock +++ b/scm-plugins/scm-hg-plugin/yarn.lock @@ -641,9 +641,9 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.14": - version "0.0.14" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.14.tgz#f5a2f0a5b0a4e527c60e38ec53ce40ce7d3b4c6f" +"@scm-manager/ui-bundler@^0.0.15": + version "0.0.15" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.15.tgz#8ed4a557d5ae38d6b99493b29608fd6a4c9cd917" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -674,6 +674,7 @@ gulp-uglify "^3.0.1" jest "^23.5.0" jest-junit "^5.1.0" + mustache "^2.3.2" node-mkdirs "^0.0.1" pom-parser "^1.1.1" prettier "^1.14.2" @@ -4891,6 +4892,10 @@ multipipe@^0.1.2: dependencies: duplexer2 "0.0.2" +mustache@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.2.tgz#a6d4d9c3f91d13359ab889a812954f9230a3d0c5" + mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" diff --git a/scm-plugins/scm-svn-plugin/package.json b/scm-plugins/scm-svn-plugin/package.json index 7183dad38a..118108c882 100644 --- a/scm-plugins/scm-svn-plugin/package.json +++ b/scm-plugins/scm-svn-plugin/package.json @@ -9,6 +9,6 @@ "@scm-manager/ui-extensions": "^0.0.7" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.14" + "@scm-manager/ui-bundler": "^0.0.15" } } diff --git a/scm-plugins/scm-svn-plugin/yarn.lock b/scm-plugins/scm-svn-plugin/yarn.lock index 27562e7150..8822bd5e57 100644 --- a/scm-plugins/scm-svn-plugin/yarn.lock +++ b/scm-plugins/scm-svn-plugin/yarn.lock @@ -641,9 +641,9 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.14": - version "0.0.14" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.14.tgz#f5a2f0a5b0a4e527c60e38ec53ce40ce7d3b4c6f" +"@scm-manager/ui-bundler@^0.0.15": + version "0.0.15" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.15.tgz#8ed4a557d5ae38d6b99493b29608fd6a4c9cd917" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -674,6 +674,7 @@ gulp-uglify "^3.0.1" jest "^23.5.0" jest-junit "^5.1.0" + mustache "^2.3.2" node-mkdirs "^0.0.1" pom-parser "^1.1.1" prettier "^1.14.2" @@ -4891,6 +4892,10 @@ multipipe@^0.1.2: dependencies: duplexer2 "0.0.2" +mustache@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.2.tgz#a6d4d9c3f91d13359ab889a812954f9230a3d0c5" + mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" diff --git a/scm-ui-components/packages/ui-components/package.json b/scm-ui-components/packages/ui-components/package.json index 308a6f02b5..e515002728 100644 --- a/scm-ui-components/packages/ui-components/package.json +++ b/scm-ui-components/packages/ui-components/package.json @@ -12,7 +12,7 @@ "eslint-fix": "eslint src --fix" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.14", + "@scm-manager/ui-bundler": "^0.0.15", "create-index": "^2.3.0", "enzyme": "^3.5.0", "enzyme-adapter-react-16": "^1.3.1", diff --git a/scm-ui-components/packages/ui-components/yarn.lock b/scm-ui-components/packages/ui-components/yarn.lock index 3df28a0731..bab2b75068 100644 --- a/scm-ui-components/packages/ui-components/yarn.lock +++ b/scm-ui-components/packages/ui-components/yarn.lock @@ -728,9 +728,9 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.14": - version "0.0.14" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.14.tgz#f5a2f0a5b0a4e527c60e38ec53ce40ce7d3b4c6f" +"@scm-manager/ui-bundler@^0.0.15": + version "0.0.15" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.15.tgz#8ed4a557d5ae38d6b99493b29608fd6a4c9cd917" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -761,6 +761,7 @@ gulp-uglify "^3.0.1" jest "^23.5.0" jest-junit "^5.1.0" + mustache "^2.3.2" node-mkdirs "^0.0.1" pom-parser "^1.1.1" prettier "^1.14.2" @@ -5529,6 +5530,10 @@ multipipe@^0.1.2: dependencies: duplexer2 "0.0.2" +mustache@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.2.tgz#a6d4d9c3f91d13359ab889a812954f9230a3d0c5" + mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" diff --git a/scm-ui-components/packages/ui-types/package.json b/scm-ui-components/packages/ui-types/package.json index 5037199cbf..9c06fc7740 100644 --- a/scm-ui-components/packages/ui-types/package.json +++ b/scm-ui-components/packages/ui-types/package.json @@ -14,7 +14,7 @@ "check": "flow check" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.14" + "@scm-manager/ui-bundler": "^0.0.15" }, "browserify": { "transform": [ diff --git a/scm-ui-components/packages/ui-types/yarn.lock b/scm-ui-components/packages/ui-types/yarn.lock index 2f7468447e..24213220d9 100644 --- a/scm-ui-components/packages/ui-types/yarn.lock +++ b/scm-ui-components/packages/ui-types/yarn.lock @@ -707,9 +707,9 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.14": - version "0.0.14" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.14.tgz#f5a2f0a5b0a4e527c60e38ec53ce40ce7d3b4c6f" +"@scm-manager/ui-bundler@^0.0.15": + version "0.0.15" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.15.tgz#8ed4a557d5ae38d6b99493b29608fd6a4c9cd917" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -740,6 +740,7 @@ gulp-uglify "^3.0.1" jest "^23.5.0" jest-junit "^5.1.0" + mustache "^2.3.2" node-mkdirs "^0.0.1" pom-parser "^1.1.1" prettier "^1.14.2" @@ -4938,6 +4939,10 @@ multipipe@^0.1.2: dependencies: duplexer2 "0.0.2" +mustache@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.2.tgz#a6d4d9c3f91d13359ab889a812954f9230a3d0c5" + mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" diff --git a/scm-ui/package.json b/scm-ui/package.json index c940d769c1..50ae39cd29 100644 --- a/scm-ui/package.json +++ b/scm-ui/package.json @@ -42,7 +42,7 @@ "pre-commit": "jest && flow && eslint src" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.14", + "@scm-manager/ui-bundler": "^0.0.15", "copyfiles": "^2.0.0", "enzyme": "^3.3.0", "enzyme-adapter-react-16": "^1.1.1", diff --git a/scm-ui/yarn.lock b/scm-ui/yarn.lock index 157fcccc12..26bfec8070 100644 --- a/scm-ui/yarn.lock +++ b/scm-ui/yarn.lock @@ -732,9 +732,9 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.14": - version "0.0.14" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.14.tgz#f5a2f0a5b0a4e527c60e38ec53ce40ce7d3b4c6f" +"@scm-manager/ui-bundler@^0.0.15": + version "0.0.15" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.15.tgz#8ed4a557d5ae38d6b99493b29608fd6a4c9cd917" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -765,6 +765,7 @@ gulp-uglify "^3.0.1" jest "^23.5.0" jest-junit "^5.1.0" + mustache "^2.3.2" node-mkdirs "^0.0.1" pom-parser "^1.1.1" prettier "^1.14.2" @@ -5766,6 +5767,10 @@ multipipe@^0.1.2: dependencies: duplexer2 "0.0.2" +mustache@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.2.tgz#a6d4d9c3f91d13359ab889a812954f9230a3d0c5" + mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" From 2387c17e5e01480f49916bfdaa3cf435093a471d Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Wed, 12 Sep 2018 07:40:51 +0000 Subject: [PATCH 16/21] Close branch feature/file_history_endpoint_v2 From de5dc77cec4689c64b895771f8fe806daf89af3d Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Wed, 12 Sep 2018 10:03:29 +0200 Subject: [PATCH 17/21] Fixed avatar file paths for the Subversion- and Git-Plugin --- scm-plugins/scm-hg-plugin/src/main/js/HgAvatar.js | 2 +- scm-plugins/scm-svn-plugin/src/main/js/SvnAvatar.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scm-plugins/scm-hg-plugin/src/main/js/HgAvatar.js b/scm-plugins/scm-hg-plugin/src/main/js/HgAvatar.js index 27bc10808e..b2348b54cc 100644 --- a/scm-plugins/scm-hg-plugin/src/main/js/HgAvatar.js +++ b/scm-plugins/scm-hg-plugin/src/main/js/HgAvatar.js @@ -8,7 +8,7 @@ type Props = { class HgAvatar extends React.Component { render() { - return Mercurial Logo; + return Mercurial Logo; } } diff --git a/scm-plugins/scm-svn-plugin/src/main/js/SvnAvatar.js b/scm-plugins/scm-svn-plugin/src/main/js/SvnAvatar.js index 06fc2454db..9996cbbfe0 100644 --- a/scm-plugins/scm-svn-plugin/src/main/js/SvnAvatar.js +++ b/scm-plugins/scm-svn-plugin/src/main/js/SvnAvatar.js @@ -8,7 +8,7 @@ type Props = { class SvnAvatar extends React.Component { render() { - return Subversion Logo; + return Subversion Logo; } } From bfa323dcaddfb254125838068009e6f34c8a4970 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Wed, 12 Sep 2018 08:36:19 +0000 Subject: [PATCH 18/21] Close branch feature/ui-for-production From 7fc78e7c32296406a3b90af036a0ba2a645d26a8 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Wed, 12 Sep 2018 17:57:48 +0200 Subject: [PATCH 19/21] Maven: Downgrade to animal-sniffer Prepares migration to oss.cloudogu.com. There, builds failed every now and then (when a lot of builds ran in parallel) with the problem described in the link of the comment in pom.xml --- pom.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8ca4318230..944ebb6eb6 100644 --- a/pom.xml +++ b/pom.xml @@ -430,7 +430,9 @@ org.codehaus.mojo animal-sniffer-maven-plugin - 1.17 + + 1.16 org.codehaus.mojo.signature From f9a9a94a3f7f8425424355e04ecad960c9454b95 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Wed, 12 Sep 2018 17:59:03 +0200 Subject: [PATCH 20/21] Jenkins: Run maven build in docker. Prepares migration to oss.cloudogu.com. There, we're only running on one build node, where concurrent builds result in failing integration tests, because they all bind to port 8081. --- .mvn/wrapper/maven-wrapper.properties | 1 + Jenkinsfile | 40 ++++++++++++--------------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 9dda3b659b..8b4bf8dee1 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1 +1,2 @@ +# Keep this version number in sync with Jenkinsfile distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip diff --git a/Jenkinsfile b/Jenkinsfile index cd02d15487..2c9a4e18e5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -17,34 +17,29 @@ node() { // No specific label catchError { Maven mvn = setupMavenBuild() - // Maven build specified it must be 1.8.0-101 or newer - def javaHome = tool 'JDK-1.8.0-101+' - withEnv(["JAVA_HOME=${javaHome}", "PATH=${env.JAVA_HOME}/bin:${env.PATH}"]) { + stage('Checkout') { + checkout scm + } - stage('Checkout') { - checkout scm - } + stage('Build') { + mvn 'clean install -DskipTests' + } - stage('Build') { - mvn 'clean install -DskipTests' - } + stage('Unit Test') { + mvn 'test -Dsonia.scm.test.skip.hg=true -Dmaven.test.failure.ignore=true' + } - stage('Unit Test') { - mvn 'test -Dsonia.scm.test.skip.hg=true -Dmaven.test.failure.ignore=true' - } + stage('Integration Test') { + mvn 'verify -Pit -pl :scm-webapp,:scm-it -Dmaven.test.failure.ignore=true' + } - stage('Integration Test') { - mvn 'verify -Pit -pl :scm-webapp,:scm-it -Dmaven.test.failure.ignore=true' - } + stage('SonarQube') { - stage('SonarQube') { + analyzeWith(mvn) - analyzeWith(mvn) - - if (!waitForQualityGateWebhookToBeCalled()) { - currentBuild.result = 'UNSTABLE' - } + if (!waitForQualityGateWebhookToBeCalled()) { + currentBuild.result = 'UNSTABLE' } } } @@ -61,7 +56,8 @@ node() { // No specific label String mainBranch Maven setupMavenBuild() { - Maven mvn = new MavenWrapper(this) + // Keep this version number in sync with .mvn/maven-wrapper.properties + Maven mvn = new MavenInDocker(this, "3.5.2-jdk-8") if (mainBranch.equals(env.BRANCH_NAME)) { // Release starts javadoc, which takes very long, so do only for certain branches From 2c752f9e8b51c6b000d68fd9b91a3a22673086fb Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Wed, 12 Sep 2018 18:01:00 +0200 Subject: [PATCH 21/21] Jenkins: Add a global build timeout Prepares migration to oss.cloudogu.com. There, we're recognized some builds that hang for hours after some errors. --- Jenkinsfile | 57 ++++++++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 2c9a4e18e5..50a2374544 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -11,46 +11,49 @@ node() { // No specific label properties([ // Keep only the last 10 build to preserve space - buildDiscarder(logRotator(numToKeepStr: '10')), + buildDiscarder(logRotator(numToKeepStr: '10')) ]) - catchError { + timeout(activity: true, time: 20, unit: 'MINUTES') { - Maven mvn = setupMavenBuild() + catchError { - stage('Checkout') { - checkout scm - } + Maven mvn = setupMavenBuild() - stage('Build') { - mvn 'clean install -DskipTests' - } + stage('Checkout') { + checkout scm + } - stage('Unit Test') { - mvn 'test -Dsonia.scm.test.skip.hg=true -Dmaven.test.failure.ignore=true' - } + stage('Build') { + mvn 'clean install -DskipTests' + } - stage('Integration Test') { - mvn 'verify -Pit -pl :scm-webapp,:scm-it -Dmaven.test.failure.ignore=true' - } + stage('Unit Test') { + mvn 'test -Dsonia.scm.test.skip.hg=true -Dmaven.test.failure.ignore=true' + } - stage('SonarQube') { + stage('Integration Test') { + mvn 'verify -Pit -pl :scm-webapp,:scm-it -Dmaven.test.failure.ignore=true' + } - analyzeWith(mvn) + stage('SonarQube') { - if (!waitForQualityGateWebhookToBeCalled()) { - currentBuild.result = 'UNSTABLE' + analyzeWith(mvn) + + if (!waitForQualityGateWebhookToBeCalled()) { + currentBuild.result = 'UNSTABLE' + } } } + + // Archive Unit and integration test results, if any + junit allowEmptyResults: true, testResults: '**/target/failsafe-reports/TEST-*.xml,**/target/surefire-reports/TEST-*.xml,**/target/jest-reports/TEST-*.xml' + + // Find maven warnings and visualize in job + warnings consoleParsers: [[parserName: 'Maven']], canRunOnFailed: true + + mailIfStatusChanged(commitAuthorEmail) } - - // Archive Unit and integration test results, if any - junit allowEmptyResults: true, testResults: '**/target/failsafe-reports/TEST-*.xml,**/target/surefire-reports/TEST-*.xml,**/target/jest-reports/TEST-*.xml' - - // Find maven warnings and visualize in job - warnings consoleParsers: [[parserName: 'Maven']], canRunOnFailed: true - - mailIfStatusChanged(commitAuthorEmail) } String mainBranch