add the Incoming Resource to get incoming changesets and diff

This commit is contained in:
Mohamed Karray
2018-12-06 08:46:22 +01:00
parent 553d564836
commit 6b2125687e
12 changed files with 492 additions and 50 deletions

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<DiffRootResource> diffRootResource;
private final Provider<ModificationsRootResource> modificationsRootResource;
private final Provider<FileHistoryRootResource> fileHistoryRootResource;
private final Provider<IncomingRootResource> incomingRootResource;
@Inject
public RepositoryResource(
@@ -56,8 +57,8 @@ public class RepositoryResource {
Provider<PermissionRootResource> permissionRootResource,
Provider<DiffRootResource> diffRootResource,
Provider<ModificationsRootResource> modificationsRootResource,
Provider<FileHistoryRootResource> fileHistoryRootResource
) {
Provider<FileHistoryRootResource> fileHistoryRootResource,
Provider<IncomingRootResource> incomingRootResource) {
this.dtoToRepositoryMapper = dtoToRepositoryMapper;
this.manager = manager;
this.repositoryToDtoMapper = repositoryToDtoMapper;
@@ -71,6 +72,7 @@ public class RepositoryResource {
this.diffRootResource = diffRootResource;
this.modificationsRootResource = modificationsRootResource;
this.fileHistoryRootResource = fileHistoryRootResource;
this.incomingRootResource = incomingRootResource;
}
/**
@@ -197,6 +199,9 @@ public class RepositoryResource {
@Path("modifications/")
public ModificationsRootResource modifications() {return modificationsRootResource.get(); }
@Path("incoming/")
public IncomingRootResource incoming() {return incomingRootResource.get(); }
private Optional<Response> handleNotArchived(Throwable throwable) {
if (throwable instanceof RepositoryIsNotArchivedException) {
return Optional.of(Response.status(Response.Status.PRECONDITION_FAILED).build());

View File

@@ -58,6 +58,8 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
}
linksBuilder.single(link("changesets", resourceLinks.changeset().all(target.getNamespace(), target.getName())));
linksBuilder.single(link("sources", resourceLinks.source().selfWithoutRevision(target.getNamespace(), target.getName())));
linksBuilder.single(link("incomingChangesets", resourceLinks.incoming().changesets(target.getNamespace(), target.getName())));
linksBuilder.single(link("incomingDiff", resourceLinks.incoming().diff(target.getNamespace(), target.getName())));
target.add(linksBuilder.build());
}

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}");
}
}