merge with branch feature/incomming_endpoint

This commit is contained in:
Sebastian Sdorra
2018-12-10 14:35:21 +01:00
17 changed files with 546 additions and 60 deletions

View File

@@ -45,5 +45,10 @@ public enum Feature
* The default branch of the repository is a combined branch of all
* repository branches.
*/
COMBINED_DEFAULT_BRANCH
COMBINED_DEFAULT_BRANCH,
/**
* The repository supports computation of incoming changes (either diff or list of changesets) of one branch
* in respect to another target branch.
*/
INCOMING_REVISION
}

View File

@@ -38,6 +38,8 @@ package sonia.scm.repository.api;
import com.google.common.base.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.NotSupportedFeatureException;
import sonia.scm.repository.Feature;
import sonia.scm.repository.spi.DiffCommand;
import sonia.scm.repository.spi.DiffCommandRequest;
import sonia.scm.util.IOUtil;
@@ -45,6 +47,7 @@ import sonia.scm.util.IOUtil;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Set;
//~--- JDK imports ------------------------------------------------------------
@@ -85,10 +88,12 @@ public final class DiffCommandBuilder
* only be called from the {@link RepositoryService}.
*
* @param diffCommand implementation of {@link DiffCommand}
* @param supportedFeatures The supported features of the provider
*/
DiffCommandBuilder(DiffCommand diffCommand)
DiffCommandBuilder(DiffCommand diffCommand, Set<Feature> supportedFeatures)
{
this.diffCommand = diffCommand;
this.supportedFeatures = supportedFeatures;
}
//~--- methods --------------------------------------------------------------
@@ -174,7 +179,8 @@ public final class DiffCommandBuilder
}
/**
* Show the difference only for the given revision.
* Show the difference only for the given revision or (using {@link #setAncestorChangeset(String)}) between this
* and another revision.
*
*
* @param revision revision for difference
@@ -187,6 +193,22 @@ public final class DiffCommandBuilder
return this;
}
/**
* Compute the incoming changes of the branch set with {@link #setRevision(String)} in respect to the changeset given
* here. In other words: What changes would be new to the ancestor changeset given here when the branch would
* be merged into it. Requires feature {@link sonia.scm.repository.Feature#INCOMING_REVISION}!
*
* @return {@code this}
*/
public DiffCommandBuilder setAncestorChangeset(String revision)
{
if (!supportedFeatures.contains(Feature.INCOMING_REVISION)) {
throw new NotSupportedFeatureException(Feature.INCOMING_REVISION.name());
}
request.setAncestorChangeset(revision);
return this;
}
//~--- get methods ----------------------------------------------------------
@@ -215,6 +237,7 @@ public final class DiffCommandBuilder
/** implementation of the diff command */
private final DiffCommand diffCommand;
private Set<Feature> supportedFeatures;
/** request for the diff command implementation */
private final DiffCommandRequest request = new DiffCommandRequest();

View File

@@ -39,10 +39,12 @@ import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.NotSupportedFeatureException;
import sonia.scm.cache.Cache;
import sonia.scm.cache.CacheManager;
import sonia.scm.repository.Changeset;
import sonia.scm.repository.ChangesetPagingResult;
import sonia.scm.repository.Feature;
import sonia.scm.repository.PreProcessorUtil;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryCacheKey;
@@ -51,6 +53,7 @@ import sonia.scm.repository.spi.LogCommandRequest;
import java.io.IOException;
import java.io.Serializable;
import java.util.Set;
//~--- JDK imports ------------------------------------------------------------
@@ -104,19 +107,20 @@ public final class LogCommandBuilder
/**
* Constructs a new {@link LogCommandBuilder}, this constructor should
* only be called from the {@link RepositoryService}.
*
* @param cacheManager cache manager
* @param cacheManager cache manager
* @param logCommand implementation of the {@link LogCommand}
* @param repository repository to query
* @param preProcessorUtil
* @param supportedFeatures The supported features of the provider
*/
LogCommandBuilder(CacheManager cacheManager, LogCommand logCommand,
Repository repository, PreProcessorUtil preProcessorUtil)
Repository repository, PreProcessorUtil preProcessorUtil, Set<Feature> supportedFeatures)
{
this.cache = cacheManager.getCache(CACHE_NAME);
this.logCommand = logCommand;
this.repository = repository;
this.preProcessorUtil = preProcessorUtil;
this.supportedFeatures = supportedFeatures;
}
//~--- methods --------------------------------------------------------------
@@ -397,7 +401,17 @@ public final class LogCommandBuilder
return this;
}
/**
* Compute the incoming changes of the branch set with {@link #setBranch(String)} in respect to the changeset given
* here. In other words: What changesets would be new to the ancestor changeset given here when the branch would
* be merged into it. Requires feature {@link sonia.scm.repository.Feature#INCOMING_REVISION}!
*
* @return {@code this}
*/
public LogCommandBuilder setAncestorChangeset(String ancestorChangeset) {
if (!supportedFeatures.contains(Feature.INCOMING_REVISION)) {
throw new NotSupportedFeatureException(Feature.INCOMING_REVISION.name());
}
request.setAncestorChangeset(ancestorChangeset);
return this;
}
@@ -527,6 +541,7 @@ public final class LogCommandBuilder
/** Field description */
private final PreProcessorUtil preProcessorUtil;
private Set<Feature> supportedFeatures;
/** repository to query */
private final Repository repository;

View File

@@ -221,7 +221,7 @@ public final class RepositoryService implements Closeable {
logger.debug("create diff command for repository {}",
repository.getNamespaceAndName());
return new DiffCommandBuilder(provider.getDiffCommand());
return new DiffCommandBuilder(provider.getDiffCommand(), provider.getSupportedFeatures());
}
/**
@@ -253,7 +253,7 @@ public final class RepositoryService implements Closeable {
repository.getNamespaceAndName());
return new LogCommandBuilder(cacheManager, provider.getLogCommand(),
repository, preProcessorUtil);
repository, preProcessorUtil, provider.getSupportedFeatures());
}
/**

View File

@@ -43,7 +43,6 @@ import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.SCMContextProvider;
import sonia.scm.io.FileSystem;
import sonia.scm.plugin.Extension;
import sonia.scm.repository.spi.GitRepositoryServiceProvider;
import sonia.scm.schedule.Scheduler;
@@ -97,7 +96,7 @@ public class GitRepositoryHandler
private final GitWorkdirFactory workdirFactory;
private Task task;
//~--- constructors ---------------------------------------------------------
@Inject
@@ -126,7 +125,7 @@ public class GitRepositoryHandler
scheduleGc(config.getGcExpression());
super.setConfig(config);
}
private void scheduleGc(String expression)
{
synchronized (LOCK){
@@ -142,7 +141,7 @@ public class GitRepositoryHandler
}
}
}
/**
* Method description
*

View File

@@ -34,11 +34,13 @@
package sonia.scm.repository.spi;
import com.google.common.collect.ImmutableSet;
import sonia.scm.repository.Feature;
import sonia.scm.repository.GitRepositoryHandler;
import sonia.scm.repository.Repository;
import sonia.scm.repository.api.Command;
import java.io.IOException;
import java.util.EnumSet;
import java.util.Set;
//~--- JDK imports ------------------------------------------------------------
@@ -66,6 +68,7 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
Command.PULL,
Command.MERGE
);
protected static final Set<Feature> FEATURES = EnumSet.of(Feature.INCOMING_REVISION);
//J+
//~--- constructors ---------------------------------------------------------
@@ -246,6 +249,10 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
return new GitMergeCommand(context, repository, handler.getWorkdirFactory());
}
@Override
public Set<Feature> getSupportedFeatures() {
return FEATURES;
}
//~--- fields ---------------------------------------------------------------
/** Field description */

View File

@@ -128,49 +128,6 @@ public class BranchRootResource {
}
}
@Path("{branch}/diffchangesets/{otherBranchName}")
@GET
@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 changesetDiff(@PathParam("namespace") String namespace,
@PathParam("name") String name,
@PathParam("branch") String branchName,
@PathParam("otherBranchName") String otherBranchName,
@DefaultValue("0") @QueryParam("page") int page,
@DefaultValue("10") @QueryParam("pageSize") int pageSize) throws Exception {
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
List<Branch> allBranches = repositoryService.getBranchesCommand().getBranches().getBranches();
if (allBranches.stream().noneMatch(branch -> branchName.equals(branch.getName()))) {
throw new NotFoundException("branch", branchName);
}
if (allBranches.stream().noneMatch(branch -> otherBranchName.equals(branch.getName()))) {
throw new NotFoundException("branch", otherBranchName);
}
Repository repository = repositoryService.getRepository();
RepositoryPermissions.read(repository).check();
ChangesetPagingResult changesets = new PagedLogCommandBuilder(repositoryService)
.page(page)
.pageSize(pageSize)
.create()
.setBranch(branchName)
.setAncestorChangeset(otherBranchName)
.getChangesets();
if (changesets != null && changesets.getChangesets() != null) {
PageResult<Changeset> pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal());
return Response.ok(branchChangesetCollectionToDtoMapper.map(page, pageSize, pageResult, repository, branchName)).build();
} else {
return Response.ok().build();
}
}
}
/**
* Returns the branches for a repository.
*

View File

@@ -28,7 +28,6 @@ public abstract class BranchToBranchDtoMapper {
Links.Builder linksBuilder = linkingTo()
.self(resourceLinks.branch().self(namespaceAndName, target.getName()))
.single(linkBuilder("history", resourceLinks.branch().history(namespaceAndName, target.getName())).build())
.single(linkBuilder("changesetDiff", resourceLinks.branch().changesetDiff(namespaceAndName, target.getName())).build())
.single(linkBuilder("changeset", resourceLinks.changeset().changeset(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())).build())
.single(linkBuilder("source", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())).build());
target.add(linksBuilder.build());

View File

@@ -25,7 +25,7 @@ public class DiffRootResource {
public static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition";
private static final String DIFF_FORMAT_VALUES_REGEX = "NATIVE|GIT|UNIFIED";
static final String DIFF_FORMAT_VALUES_REGEX = "NATIVE|GIT|UNIFIED";
private final RepositoryServiceFactory serviceFactory;

View File

@@ -0,0 +1,29 @@
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 IncomingChangesetCollectionToDtoMapper extends ChangesetCollectionToDtoMapper {
private final ResourceLinks resourceLinks;
@Inject
public IncomingChangesetCollectionToDtoMapper(ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper, ResourceLinks resourceLinks) {
super(changesetToChangesetDtoMapper, resourceLinks);
this.resourceLinks = resourceLinks;
}
public CollectionDto map(int pageNumber, int pageSize, PageResult<Changeset> pageResult, Repository repository, String source, String target) {
return super.map(pageNumber, pageSize, pageResult, repository, () -> createSelfLink(repository, source, target));
}
private String createSelfLink(Repository repository, String source, String target) {
return resourceLinks.incoming().changesets(repository.getNamespace(), repository.getName(), source, target);
}
}

View File

@@ -0,0 +1,153 @@
package sonia.scm.api.v2.resources;
import com.google.inject.Inject;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.PageResult;
import sonia.scm.repository.Changeset;
import sonia.scm.repository.ChangesetPagingResult;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryPermissions;
import sonia.scm.repository.api.DiffFormat;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.util.HttpUtil;
import sonia.scm.web.VndMediaType;
import javax.validation.constraints.Pattern;
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 javax.ws.rs.core.StreamingOutput;
import java.io.IOException;
import static sonia.scm.api.v2.resources.DiffRootResource.DIFF_FORMAT_VALUES_REGEX;
import static sonia.scm.api.v2.resources.DiffRootResource.HEADER_CONTENT_DISPOSITION;
public class IncomingRootResource {
private final RepositoryServiceFactory serviceFactory;
private final IncomingChangesetCollectionToDtoMapper mapper;
@Inject
public IncomingRootResource(RepositoryServiceFactory serviceFactory, IncomingChangesetCollectionToDtoMapper incomingChangesetCollectionToDtoMapper) {
this.serviceFactory = serviceFactory;
this.mapper = incomingChangesetCollectionToDtoMapper;
}
/**
* Get the incoming changesets from <code>source</code> to <code>target</code>
* <p>
* Example:
* <p>
* - master
* - |
* - _______________ ° m1
* - e |
* - | ° m2
* - ° e1 |
* - ______|_______ |
* - | | b
* - f a |
* - | | ° b1
* - ° f1 ° a1 |
* - ° b2
* -
* <p>
* - /incoming/a/master/changesets -> a1 , e1
* - /incoming/b/master/changesets -> b1 , b2
* - /incoming/b/f/changesets -> b1 , b2, m2
* - /incoming/f/b/changesets -> f1 , e1
* - /incoming/a/b/changesets -> a1 , e1
* - /incoming/a/b/changesets -> a1 , e1
*
* @param namespace
* @param name
* @param source can be a changeset id or a branch name
* @param target can be a changeset id or a branch name
* @param page
* @param pageSize
* @return
* @throws Exception
*/
@Path("{source}/{target}/changesets")
@GET
@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 incomingChangesets(@PathParam("namespace") String namespace,
@PathParam("name") String name,
@PathParam("source") String source,
@PathParam("target") String target,
@DefaultValue("0") @QueryParam("page") int page,
@DefaultValue("10") @QueryParam("pageSize") int pageSize) throws IOException {
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
Repository repository = repositoryService.getRepository();
RepositoryPermissions.read(repository).check();
ChangesetPagingResult changesets = new PagedLogCommandBuilder(repositoryService)
.page(page)
.pageSize(pageSize)
.create()
.setStartChangeset(source)
.setAncestorChangeset(target)
.getChangesets();
if (changesets != null && changesets.getChangesets() != null) {
PageResult<Changeset> pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal());
return Response.ok(mapper.map(page, pageSize, pageResult, repository, source, target)).build();
} else {
return Response.ok().build();
}
}
}
@Path("{source}/{target}/diff")
@GET
@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.DIFF)
@TypeHint(CollectionDto.class)
public Response incomingDiff(@PathParam("namespace") String namespace,
@PathParam("name") String name,
@PathParam("source") String source,
@PathParam("target") String target,
@Pattern(regexp = DIFF_FORMAT_VALUES_REGEX) @DefaultValue("NATIVE") @QueryParam("format") String format) throws IOException {
HttpUtil.checkForCRLFInjection(source);
HttpUtil.checkForCRLFInjection(target);
DiffFormat diffFormat = DiffFormat.valueOf(format);
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
StreamingOutput responseEntry = output ->
repositoryService.getDiffCommand()
.setRevision(source)
.setAncestorChangeset(target)
.setFormat(diffFormat)
.retrieveContent(output);
return Response.ok(responseEntry)
.header(HEADER_CONTENT_DISPOSITION, HttpUtil.createContentDispositionAttachmentHeader(String.format("%s-%s.diff", name, source)))
.build();
}
}
}

View File

@@ -44,6 +44,7 @@ public class RepositoryResource {
private final Provider<ModificationsRootResource> modificationsRootResource;
private final Provider<FileHistoryRootResource> fileHistoryRootResource;
private final Provider<MergeResource> mergeResource;
private final Provider<IncomingRootResource> incomingRootResource;
@Inject
public RepositoryResource(
@@ -57,6 +58,7 @@ public class RepositoryResource {
Provider<DiffRootResource> diffRootResource,
Provider<ModificationsRootResource> modificationsRootResource,
Provider<FileHistoryRootResource> fileHistoryRootResource,
Provider<IncomingRootResource> incomingRootResource,
Provider<MergeResource> mergeResource) {
this.dtoToRepositoryMapper = dtoToRepositoryMapper;
this.manager = manager;
@@ -72,6 +74,7 @@ public class RepositoryResource {
this.modificationsRootResource = modificationsRootResource;
this.fileHistoryRootResource = fileHistoryRootResource;
this.mergeResource = mergeResource;
this.incomingRootResource = incomingRootResource;
}
/**
@@ -196,7 +199,14 @@ public class RepositoryResource {
}
@Path("modifications/")
public ModificationsRootResource modifications() {return modificationsRootResource.get(); }
public ModificationsRootResource modifications() {
return modificationsRootResource.get();
}
@Path("incoming/")
public IncomingRootResource incoming() {
return incomingRootResource.get();
}
@Path("merge/")
public MergeResource merge() {return mergeResource.get(); }

View File

@@ -6,6 +6,7 @@ import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import sonia.scm.repository.Feature;
import sonia.scm.repository.HealthCheckFailure;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryPermissions;
@@ -55,6 +56,10 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
if (repositoryService.isSupported(Command.BRANCHES)) {
linksBuilder.single(link("branches", resourceLinks.branchCollection().self(target.getNamespace(), target.getName())));
}
if (repositoryService.isSupported(Feature.INCOMING_REVISION)) {
linksBuilder.single(link("incomingChangesets", resourceLinks.incoming().changesets(target.getNamespace(), target.getName())));
linksBuilder.single(link("incomingDiff", resourceLinks.incoming().diff(target.getNamespace(), target.getName())));
}
if (repositoryService.isSupported(Command.MERGE)) {
linksBuilder.single(link("merge", resourceLinks.merge().merge(target.getNamespace(), target.getName())));
linksBuilder.single(link("mergeDryRun", resourceLinks.merge().dryRun(target.getNamespace(), target.getName())));

View File

@@ -323,8 +323,34 @@ class ResourceLinks {
return branchLinkBuilder.method("getRepositoryResource").parameters(namespaceAndName.getNamespace(), namespaceAndName.getName()).method("branches").parameters().method("history").parameters(branch).href();
}
public String changesetDiff(NamespaceAndName namespaceAndName, String branch) {
return branchLinkBuilder.method("getRepositoryResource").parameters(namespaceAndName.getNamespace(), namespaceAndName.getName()).method("branches").parameters().method("changesetDiff").parameters(branch, "").href() + "{otherBranch}";
}
public IncomingLinks incoming() {
return new IncomingLinks(scmPathInfoStore.get());
}
static class IncomingLinks {
private final LinkBuilder incomingLinkBuilder;
IncomingLinks(ScmPathInfo pathInfo) {
incomingLinkBuilder = new LinkBuilder(pathInfo, RepositoryRootResource.class, RepositoryResource.class, IncomingRootResource.class);
}
public String changesets(String namespace, String name) {
return toTemplateParams(incomingLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("incoming").parameters().method("incomingChangesets").parameters("source","target").href());
}
public String changesets(String namespace, String name, String source, String target) {
return incomingLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("incoming").parameters().method("incomingChangesets").parameters(source,target).href();
}
public String diff(String namespace, String name) {
return toTemplateParams(incomingLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("incoming").parameters().method("incomingDiff").parameters("source", "target").href());
}
public String toTemplateParams(String href) {
return href.replace("source", "{source}").replace("target", "{target}");
}
}

View File

@@ -0,0 +1,255 @@
package sonia.scm.api.v2.resources;
import com.google.inject.util.Providers;
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.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.NotFoundException;
import sonia.scm.repository.Changeset;
import sonia.scm.repository.ChangesetPagingResult;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Person;
import sonia.scm.repository.Repository;
import sonia.scm.repository.api.DiffCommandBuilder;
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.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
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 IncomingRootResourceTest extends RepositoryTestBase {
public static final String INCOMING_PATH = "space/repo/incoming/";
public static final String INCOMING_CHANGESETS_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + INCOMING_PATH;
public static final String INCOMING_DIFF_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + INCOMING_PATH;
private Dispatcher dispatcher;
private final URI baseUri = URI.create("/");
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
@Mock
private RepositoryServiceFactory serviceFactory;
@Mock
private RepositoryService repositoryService;
@Mock
private LogCommandBuilder logCommandBuilder;
@Mock
private DiffCommandBuilder diffCommandBuilder;
private IncomingChangesetCollectionToDtoMapper incomingChangesetCollectionToDtoMapper;
@InjectMocks
private ChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper;
private IncomingRootResource incomingRootResource;
private final Subject subject = mock(Subject.class);
private final ThreadState subjectThreadState = new SubjectThreadState(subject);
@Before
public void prepareEnvironment() {
incomingChangesetCollectionToDtoMapper = new IncomingChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks);
incomingRootResource = new IncomingRootResource(serviceFactory, incomingChangesetCollectionToDtoMapper);
super.incomingRootResource = Providers.of(incomingRootResource);
dispatcher = DispatcherMock.createDispatcher(getRepositoryRootResource());
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(repositoryService);
when(serviceFactory.create(any(Repository.class))).thenReturn(repositoryService);
when(repositoryService.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
when(repositoryService.getLogCommand()).thenReturn(logCommandBuilder);
when(repositoryService.getDiffCommand()).thenReturn(diffCommandBuilder);
dispatcher.getProviderFactory().registerProvider(CRLFInjectionExceptionMapper.class);
subjectThreadState.bind();
ThreadContext.bind(subject);
when(subject.isPermitted(any(String.class))).thenReturn(true);
}
@After
public void cleanupContext() {
ThreadContext.unbindSubject();
}
@Test
public void shouldGetIncomingChangesets() throws Exception {
String id = "revision_123";
Instant creationDate = Instant.now();
String authorName = "name";
String authorEmail = "em@i.l";
String commit = "my branch commit";
ChangesetPagingResult changesetPagingResult = mock(ChangesetPagingResult.class);
List<Changeset> 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(0)).thenReturn(logCommandBuilder);
when(logCommandBuilder.setPagingLimit(10)).thenReturn(logCommandBuilder);
when(logCommandBuilder.setStartChangeset(anyString())).thenReturn(logCommandBuilder);
when(logCommandBuilder.setAncestorChangeset(anyString())).thenReturn(logCommandBuilder);
when(logCommandBuilder.getChangesets()).thenReturn(changesetPagingResult);
MockHttpRequest request = MockHttpRequest
.get(INCOMING_CHANGESETS_URL + "src_changeset_id/target_changeset_id/changesets")
.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 shouldGetSinglePageOfIncomingChangesets() throws Exception {
String id = "revision_123";
Instant creationDate = Instant.now();
String authorName = "name";
String authorEmail = "em@i.l";
String commit = "my branch commit";
ChangesetPagingResult changesetPagingResult = mock(ChangesetPagingResult.class);
List<Changeset> 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(20)).thenReturn(logCommandBuilder);
when(logCommandBuilder.setPagingLimit(10)).thenReturn(logCommandBuilder);
when(logCommandBuilder.setStartChangeset(anyString())).thenReturn(logCommandBuilder);
when(logCommandBuilder.setAncestorChangeset(anyString())).thenReturn(logCommandBuilder);
when(logCommandBuilder.getChangesets()).thenReturn(changesetPagingResult);
MockHttpRequest request = MockHttpRequest
.get(INCOMING_CHANGESETS_URL + "src_changeset_id/target_changeset_id/changesets?page=2")
.accept(VndMediaType.CHANGESET_COLLECTION);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(200, response.getStatus());
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 shouldGetDiffs() throws Exception {
when(diffCommandBuilder.setRevision(anyString())).thenReturn(diffCommandBuilder);
when(diffCommandBuilder.setAncestorChangeset(anyString())).thenReturn(diffCommandBuilder);
when(diffCommandBuilder.setFormat(any())).thenReturn(diffCommandBuilder);
when(diffCommandBuilder.retrieveContent(any())).thenReturn(diffCommandBuilder);
MockHttpRequest request = MockHttpRequest
.get(INCOMING_DIFF_URL + "src_changeset_id/target_changeset_id/diff")
.accept(VndMediaType.DIFF);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertThat(response.getStatus())
.isEqualTo(200);
String expectedHeader = "Content-Disposition";
String expectedValue = "attachment; filename=\"repo-src_changeset_id.diff\"; filename*=utf-8''repo-src_changeset_id.diff";
assertThat(response.getOutputHeaders().containsKey(expectedHeader)).isTrue();
assertThat((String) response.getOutputHeaders().get("Content-Disposition").get(0))
.contains(expectedValue);
}
@Test
public void shouldGet404OnMissingRepository() throws URISyntaxException {
when(serviceFactory.create(any(NamespaceAndName.class))).thenThrow(new NotFoundException("Text", "x"));
MockHttpRequest request = MockHttpRequest
.get(INCOMING_DIFF_URL + "src_changeset_id/target_changeset_id/diff")
.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.setAncestorChangeset(anyString())).thenReturn(diffCommandBuilder);
when(diffCommandBuilder.setFormat(any())).thenReturn(diffCommandBuilder);
when(diffCommandBuilder.retrieveContent(any())).thenThrow(new NotFoundException("Text", "x"));
MockHttpRequest request = MockHttpRequest
.get(INCOMING_DIFF_URL + "src_changeset_id/target_changeset_id/diff")
.accept(VndMediaType.DIFF);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(404, response.getStatus());
}
@Test
public void shouldGet400OnCrlfInjection() throws Exception {
when(diffCommandBuilder.setRevision(anyString())).thenReturn(diffCommandBuilder);
when(diffCommandBuilder.setAncestorChangeset(anyString())).thenReturn(diffCommandBuilder);
when(diffCommandBuilder.setFormat(any())).thenReturn(diffCommandBuilder);
when(diffCommandBuilder.retrieveContent(any())).thenThrow(new NotFoundException("Text", "x"));
MockHttpRequest request = MockHttpRequest
.get(INCOMING_DIFF_URL + "ny%0D%0ASet-cookie:%20Tamper=3079675143472450634/ny%0D%0ASet-cookie:%20Tamper=3079675143472450634/diff")
.accept(VndMediaType.DIFF);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(400, response.getStatus());
assertThat(response.getContentAsString()).contains("parameter contains an illegal character");
}
@Test
public void shouldGet400OnUnknownFormat() throws Exception {
when(diffCommandBuilder.setRevision(anyString())).thenReturn(diffCommandBuilder);
when(diffCommandBuilder.setAncestorChangeset(anyString())).thenReturn(diffCommandBuilder);
when(diffCommandBuilder.setFormat(any())).thenReturn(diffCommandBuilder);
when(diffCommandBuilder.retrieveContent(any())).thenThrow(new NotFoundException("Test", "test"));
MockHttpRequest request = MockHttpRequest
.get(INCOMING_DIFF_URL + "src_changeset_id/target_changeset_id/diff?format=Unknown")
.accept(VndMediaType.DIFF);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(400, response.getStatus());
}
}

View File

@@ -21,6 +21,7 @@ public abstract class RepositoryTestBase {
protected Provider<ModificationsRootResource> modificationsRootResource;
protected Provider<FileHistoryRootResource> fileHistoryRootResource;
protected Provider<RepositoryCollectionResource> repositoryCollectionResource;
protected Provider<IncomingRootResource> incomingRootResource;
protected Provider<MergeResource> mergeResource;
@@ -38,6 +39,7 @@ public abstract class RepositoryTestBase {
diffRootResource,
modificationsRootResource,
fileHistoryRootResource,
incomingRootResource,
mergeResource)), repositoryCollectionResource);
}

View File

@@ -20,6 +20,7 @@ public class ResourceLinksMock {
when(resourceLinks.group()).thenReturn(new ResourceLinks.GroupLinks(uriInfo));
when(resourceLinks.groupCollection()).thenReturn(new ResourceLinks.GroupCollectionLinks(uriInfo));
when(resourceLinks.repository()).thenReturn(new ResourceLinks.RepositoryLinks(uriInfo));
when(resourceLinks.incoming()).thenReturn(new ResourceLinks.IncomingLinks(uriInfo));
when(resourceLinks.repositoryCollection()).thenReturn(new ResourceLinks.RepositoryCollectionLinks(uriInfo));
when(resourceLinks.tag()).thenReturn(new ResourceLinks.TagCollectionLinks(uriInfo));
when(resourceLinks.branchCollection()).thenReturn(new ResourceLinks.BranchCollectionLinks(uriInfo));