From e429616f38de07272a51d7a9ed4202d9361b3e00 Mon Sep 17 00:00:00 2001 From: Mohamed Karray Date: Wed, 29 Aug 2018 11:52:05 +0200 Subject: [PATCH 01/10] #8630 add the changeset endpoint getAll --- .../scm/api/v2/resources/RepositoryResource.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) 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 3ee27f5f84..9066642167 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 @@ -37,6 +37,7 @@ public class RepositoryResource { private final Provider sourceRootResource; private final Provider contentResource; private final Provider permissionRootResource; + private final Provider diffRootResource; @Inject public RepositoryResource( @@ -45,7 +46,9 @@ public class RepositoryResource { Provider tagRootResource, Provider branchRootResource, Provider changesetRootResource, - Provider sourceRootResource, Provider contentResource, Provider permissionRootResource) { + Provider sourceRootResource, Provider contentResource, + Provider permissionRootResource, + Provider diffRootResource) { this.dtoToRepositoryMapper = dtoToRepositoryMapper; this.manager = manager; this.repositoryToDtoMapper = repositoryToDtoMapper; @@ -56,6 +59,7 @@ public class RepositoryResource { this.sourceRootResource = sourceRootResource; this.contentResource = contentResource; this.permissionRootResource = permissionRootResource; + this.diffRootResource = diffRootResource; } /** @@ -144,6 +148,11 @@ public class RepositoryResource { return tagRootResource.get(); } + @Path("diff/") + public DiffRootResource diff() { + return diffRootResource.get(); + } + @Path("branches/") public BranchRootResource branches(@PathParam("namespace") String namespace, @PathParam("name") String name) { return branchRootResource.get(); From c6203082e35ae7727a400d984bb259f55bd8a0e8 Mon Sep 17 00:00:00 2001 From: Mohamed Karray Date: Wed, 29 Aug 2018 12:03:56 +0200 Subject: [PATCH 02/10] #8630 add the changeset endpoint getAll --- .../java/sonia/scm/repository/Changeset.java | 166 +++++++++--------- .../java/sonia/scm/repository/Person.java | 14 +- .../main/java/sonia/scm/web/VndMediaType.java | 2 + .../resources/BasicCollectionToDtoMapper.java | 24 +-- .../BranchCollectionToDtoMapper.java | 7 +- .../ChangesetCollectionToDtoMapper.java | 42 +++++ .../scm/api/v2/resources/ChangesetDto.java | 49 ++++++ .../v2/resources/ChangesetRootResource.java | 73 +++++++- .../ChangesetToChangesetDtoMapper.java | 78 ++++++++ .../resources/ChangesetToParentDtoMapper.java | 38 ++++ .../api/v2/resources/DiffRootResource.java | 27 +++ .../resources/GroupCollectionToDtoMapper.java | 2 +- .../scm/api/v2/resources/MapperModule.java | 5 + .../api/v2/resources/ParentChangesetDto.java | 27 +++ .../sonia/scm/api/v2/resources/PersonDto.java | 22 +++ .../RepositoryCollectionToDtoMapper.java | 2 +- .../RepositoryToRepositoryDtoMapper.java | 4 +- .../scm/api/v2/resources/ResourceLinks.java | 38 +++- .../v2/resources/TagCollectionResource.java | 18 -- .../resources/TagCollectionToDtoMapper.java | 49 ++++++ .../sonia/scm/api/v2/resources/TagDto.java | 24 +++ .../scm/api/v2/resources/TagRootResource.java | 27 +-- .../api/v2/resources/TagToTagDtoMapper.java | 31 ++++ .../resources/UserCollectionToDtoMapper.java | 2 +- .../v2/resources/BranchRootResourceTest.java | 2 +- .../resources/PermissionRootResourceTest.java | 13 +- .../resources/RepositoryRootResourceTest.java | 2 +- .../api/v2/resources/ResourceLinksMock.java | 2 +- .../api/v2/resources/ResourceLinksTest.java | 4 +- 29 files changed, 637 insertions(+), 157 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetCollectionToDtoMapper.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetDto.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToParentDtoMapper.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/ParentChangesetDto.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/PersonDto.java delete mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagCollectionResource.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagCollectionToDtoMapper.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagDto.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java diff --git a/scm-core/src/main/java/sonia/scm/repository/Changeset.java b/scm-core/src/main/java/sonia/scm/repository/Changeset.java index fecfd74cc9..ca2c9ac5db 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Changeset.java +++ b/scm-core/src/main/java/sonia/scm/repository/Changeset.java @@ -33,27 +33,20 @@ package sonia.scm.repository; -//~--- non-JDK imports -------------------------------------------------------- - import com.google.common.base.Objects; - import sonia.scm.BasicPropertiesAware; -import sonia.scm.Validateable; +import sonia.scm.ModelObject; import sonia.scm.util.Util; import sonia.scm.util.ValidationUtil; -//~--- JDK imports ------------------------------------------------------------ - -import java.io.Serializable; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + /** * Represents a changeset/commit of a repository. @@ -62,43 +55,58 @@ import javax.xml.bind.annotation.XmlRootElement; */ @XmlRootElement(name = "changeset") @XmlAccessorType(XmlAccessType.FIELD) -public class Changeset extends BasicPropertiesAware - implements Validateable, Serializable -{ +public class Changeset extends BasicPropertiesAware implements ModelObject { - /** Field description */ private static final long serialVersionUID = -8373308448928993039L; - //~--- constructors --------------------------------------------------------- + /** + * The author of the changeset + */ + private Person author; /** - * Constructs a new instance of changeset. - * + * The name of the branches on which the changeset was committed. */ + private List branches; + + /** + * The date when the changeset was committed + */ + private Long date; + + /** + * The text of the changeset description + */ + private String description; + + /** + * The changeset identification string + */ + private String id; + + /** + * List of files changed by this changeset + */ + @XmlElement(name = "modifications") + private Modifications modifications; + + /** + * parent changeset ids + */ + private List parents; + + /** + * The tags associated with the changeset + */ + private List tags; + public Changeset() {} - /** - * Constructs a new instance of changeset. - * - * - * @param id id of the changeset - * @param date date of the changeset - * @param author author of the changeset - */ public Changeset(String id, Long date, Person author) { this(id, date, author, null); } - /** - * Constructs a new instance of changeset. - * - * - * @param id id of the changeset - * @param date date of the changeset - * @param author author of the changeset - * @param description description of the changeset - */ public Changeset(String id, Long date, Person author, String description) { this.id = id; @@ -107,16 +115,6 @@ public class Changeset extends BasicPropertiesAware this.description = description; } - //~--- methods -------------------------------------------------------------- - - /** - * {@inheritDoc} - * - * - * @param obj - * - * @return - */ @Override public boolean equals(Object obj) { @@ -125,15 +123,14 @@ public class Changeset extends BasicPropertiesAware return false; } - if (getClass() != obj.getClass()) - { + if (getClass() != obj.getClass()) { return false; } final Changeset other = (Changeset) obj; //J- - return Objects.equal(id, other.id) + return Objects.equal(id, other.id) && Objects.equal(date, other.date) && Objects.equal(author, other.author) && Objects.equal(description, other.description) @@ -195,7 +192,21 @@ public class Changeset extends BasicPropertiesAware return out.toString(); } - //~--- get methods ---------------------------------------------------------- + + /** + * Returns a timestamp of the creation date of the {@link Changeset}. + * + * @return a timestamp of the creation date of the {@link Changeset} + */ + public Long getCreationDate() { + return getDate(); + } + + @Override + public void setCreationDate(Long timestamp) { + this.setDate(timestamp); + } + /** * Returns the author of the changeset. @@ -203,14 +214,13 @@ public class Changeset extends BasicPropertiesAware * * @return author of the changeset */ - public Person getAuthor() - { + public Person getAuthor() { return author; } /** - * Returns the branches of the changeset. In the most cases a changeset is - * only related to one branch, but in the case of receive hooks it is possible + * Returns the branches of the changeset. In the most cases a changeset is + * only related to one branch, but in the case of receive hooks it is possible * that a changeset is related to more than a branch. * * @@ -254,11 +264,27 @@ public class Changeset extends BasicPropertiesAware * * @return id of the changeset */ - public String getId() - { + @Override + public String getId() { return id; } + @Override + public void setLastModified(Long timestamp) { + this.setDate(timestamp); + } + + @Override + public Long getLastModified() { + return getCreationDate(); + } + + @Override + public String getType() { + return "Changeset"; + } + + /** * Returns the file modifications, which was done with this changeset. * @@ -321,8 +347,6 @@ public class Changeset extends BasicPropertiesAware && (date != null); } - //~--- set methods ---------------------------------------------------------- - /** * Sets the author of the changeset. * @@ -412,30 +436,4 @@ public class Changeset extends BasicPropertiesAware this.tags = tags; } - //~--- fields --------------------------------------------------------------- - - /** The author of the changeset */ - private Person author; - - /** The name of the branches on which the changeset was committed. */ - private List branches; - - /** The date when the changeset was committed */ - private Long date; - - /** The text of the changeset description */ - private String description; - - /** The changeset identification string */ - private String id; - - /** List of files changed by this changeset */ - @XmlElement(name = "modifications") - private Modifications modifications; - - /** parent changeset ids */ - private List parents; - - /** The tags associated with the changeset */ - private List tags; } diff --git a/scm-core/src/main/java/sonia/scm/repository/Person.java b/scm-core/src/main/java/sonia/scm/repository/Person.java index b3bf15c49a..8b5e66f61a 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Person.java +++ b/scm-core/src/main/java/sonia/scm/repository/Person.java @@ -36,18 +36,16 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- import com.google.common.base.Objects; - import sonia.scm.Validateable; import sonia.scm.util.Util; import sonia.scm.util.ValidationUtil; -//~--- JDK imports ------------------------------------------------------------ - -import java.io.Serializable; - import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +//~--- JDK imports ------------------------------------------------------------ /** * The {@link Person} (author) of a changeset. @@ -251,9 +249,11 @@ public class Person implements Validateable, Serializable //~--- fields --------------------------------------------------------------- - /** name of the person */ + /** mail address of the person */ private String mail; - /** mail address of the person */ + /** + * name of the person + */ private String name; } 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 afb1417670..7541dd9766 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -17,6 +17,8 @@ public class VndMediaType { public static final String GROUP = PREFIX + "group" + SUFFIX; public static final String REPOSITORY = PREFIX + "repository" + SUFFIX; public static final String PERMISSION = PREFIX + "permission" + SUFFIX; + 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 USER_COLLECTION = PREFIX + "userCollection" + SUFFIX; public static final String GROUP_COLLECTION = PREFIX + "groupCollection" + SUFFIX; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BasicCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BasicCollectionToDtoMapper.java index 4700d22841..4787452003 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BasicCollectionToDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BasicCollectionToDtoMapper.java @@ -11,6 +11,7 @@ import sonia.scm.PageResult; import javax.inject.Inject; import java.util.EnumSet; import java.util.List; +import java.util.function.Function; import static com.damnhandy.uri.template.UriTemplate.fromTemplate; import static de.otto.edison.hal.Embedded.embeddedBuilder; @@ -19,25 +20,28 @@ import static de.otto.edison.hal.Links.linkingTo; import static de.otto.edison.hal.paging.NumberedPaging.zeroBasedNumberedPaging; import static java.util.stream.Collectors.toList; -abstract class BasicCollectionToDtoMapper { +abstract class BasicCollectionToDtoMapper> { private final String collectionName; - private final BaseMapper entityToDtoMapper; + + protected final M entityToDtoMapper; @Inject - public BasicCollectionToDtoMapper(String collectionName, BaseMapper entityToDtoMapper) { + public BasicCollectionToDtoMapper(String collectionName, M entityToDtoMapper) { this.collectionName = collectionName; this.entityToDtoMapper = entityToDtoMapper; } public CollectionDto map(int pageNumber, int pageSize, PageResult pageResult) { - NumberedPaging paging = zeroBasedNumberedPaging(pageNumber, pageSize, pageResult.getOverallCount()); - List dtos = pageResult.getEntities().stream().map(entityToDtoMapper::map).collect(toList()); + return map(pageNumber, pageSize, pageResult, entityToDtoMapper::map, createSelfLink()); + } + public CollectionDto map(int pageNumber, int pageSize, PageResult pageResult, Function mapper, String selfLink) { + NumberedPaging paging = zeroBasedNumberedPaging(pageNumber, pageSize, pageResult.getOverallCount()); + List dtos = pageResult.getEntities().stream().map(mapper).collect(toList()); CollectionDto collectionDto = new CollectionDto( - createLinks(paging), - embedDtos(dtos) - ); + createLinks(paging, selfLink), + embedDtos(dtos)); collectionDto.setPage(pageNumber); collectionDto.setPageTotal(computePageTotal(pageSize, pageResult)); return collectionDto; @@ -51,9 +55,7 @@ abstract class BasicCollectionToDtoMapper branches) { - List dtos = branches.stream().map(branch -> branchToDtoMapper.map(branch, new NamespaceAndName(namespace, name))).collect(toList()); - return new HalRepresentation(createLinks(namespace, name), embedDtos(dtos)); + return new HalRepresentation(createLinks(namespace, name), embedDtos(getBranchDtoList(namespace, name, branches))); + } + + public List getBranchDtoList(String namespace, String name, Collection branches) { + return branches.stream().map(branch -> branchToDtoMapper.map(branch, new NamespaceAndName(namespace, name))).collect(toList()); } private Links createLinks(String namespace, String name) { 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 new file mode 100644 index 0000000000..93eb372fe9 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetCollectionToDtoMapper.java @@ -0,0 +1,42 @@ +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 ChangesetCollectionToDtoMapper extends BasicCollectionToDtoMapper { + + private final ResourceLinks resourceLinks; + + @Inject + public ChangesetCollectionToDtoMapper(ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper, ResourceLinks resourceLinks) { + super("changesets", changesetToChangesetDtoMapper); + this.resourceLinks = resourceLinks; + } + + public CollectionDto map(int pageNumber, int pageSize, PageResult pageResult, Repository repository) { + return super.map(pageNumber, pageSize, pageResult, changeset -> super.entityToDtoMapper.map(changeset, repository), createSelfLink(repository)); + } + + + @Override + String createCreateLink() { + return null; + } + + @Override + String createSelfLink() { + return null; + } + + private String createSelfLink(Repository repository) { + return resourceLinks.changeset().all(repository.getNamespace(), repository.getName()); + } + + @Override + boolean isCreatePermitted() { + return false; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetDto.java new file mode 100644 index 0000000000..162d5d6699 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetDto.java @@ -0,0 +1,49 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; +import de.otto.edison.hal.Links; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.Instant; +import java.util.List; + +@Getter +@Setter +@NoArgsConstructor +public class ChangesetDto extends HalRepresentation { + + /** + * The changeset identification string + */ + private String id; + + /** + * The author of the changeset + */ + private PersonDto author; + + /** + * The date when the changeset was committed + */ + private Instant date; + + /** + * The text of the changeset description + */ + private String description; + + @Override + @SuppressWarnings("squid:S1185") // We want to have this method available in this package + protected HalRepresentation add(Links links) { + return super.add(links); + } + + @SuppressWarnings("squid:S1185") // We want to have this method available in this package + protected HalRepresentation withEmbedded(String rel, List halRepresentations) { + return super.withEmbedded(rel, halRepresentations); + } + + +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java index 42ffbdd6c1..aa3aa5924b 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java @@ -1,25 +1,84 @@ 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.NamespaceAndName; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryException; +import sonia.scm.repository.RepositoryNotFoundException; +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 ChangesetRootResource { + private final RepositoryServiceFactory serviceFactory; + + private final ChangesetCollectionToDtoMapper changesetCollectionToDtoMapper; + + @Inject + public ChangesetRootResource(RepositoryServiceFactory serviceFactory, ChangesetCollectionToDtoMapper changesetCollectionToDtoMapper) { + this.serviceFactory = serviceFactory; + this.changesetCollectionToDtoMapper = changesetCollectionToDtoMapper; + } + @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(); + @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 changeset with the specified name 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, @DefaultValue("0") @QueryParam("page") int page, + @DefaultValue("10") @QueryParam("pageSize") int pageSize) { + try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { + ChangesetPagingResult changesets; + Repository repository = repositoryService.getRepository(); + changesets = repositoryService.getLogCommand() + .setPagingStart(page) + .setPagingLimit(pageSize) + .getChangesets(); + if (changesets != null) { + PageResult pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal()); + return Response.ok(changesetCollectionToDtoMapper.map(page, pageSize, pageResult, repository)).build(); + } else { + return Response.ok().build(); + } + } catch (RepositoryNotFoundException e) { + log.debug("Not found in repository {}/{}", namespace, name, e); + return Response.status(Response.Status.NOT_FOUND).build(); + } catch (RepositoryException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + return Response.ok().build(); } @GET - @Path("{revision}") - public Response get() { + @Path("{id}") + public Response get(@PathParam("id") String id) { throw new UnsupportedOperationException(); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java new file mode 100644 index 0000000000..f047e3bbaa --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java @@ -0,0 +1,78 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.Links; +import org.mapstruct.AfterMapping; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import sonia.scm.repository.Branch; +import sonia.scm.repository.Changeset; +import sonia.scm.repository.Repository; +import sonia.scm.repository.Tag; +import sonia.scm.repository.api.Command; +import sonia.scm.repository.api.RepositoryService; +import sonia.scm.repository.api.RepositoryServiceFactory; + +import javax.inject.Inject; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static de.otto.edison.hal.Link.link; +import static de.otto.edison.hal.Links.linkingTo; + +@Mapper +public abstract class ChangesetToChangesetDtoMapper extends BaseMapper { + + @Inject + private RepositoryServiceFactory serviceFactory; + + @Inject + private ResourceLinks resourceLinks; + + + @Inject + private BranchCollectionToDtoMapper branchCollectionToDtoMapper; + + @Inject + private ChangesetToParentDtoMapper changesetToParentDtoMapper; + + @Inject + private TagCollectionToDtoMapper tagCollectionToDtoMapper; + + + @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes + public abstract ChangesetDto map(Changeset changeset, @Context Repository repository); + + + @AfterMapping + void appendLinks(Changeset source, @MappingTarget ChangesetDto target, @Context Repository repository) { + String namespace = repository.getNamespace(); + String name = repository.getName(); + + try (RepositoryService repositoryService = serviceFactory.create(repository)) { + if (repositoryService.isSupported(Command.TAGS)) { + target.withEmbedded("tags", tagCollectionToDtoMapper.getTagDtoList(namespace, name, + getListOfObjects(source.getTags(), tagName -> new Tag(tagName, source.getId())))); + } + if (repositoryService.isSupported(Command.BRANCHES)) { + target.withEmbedded("branches", branchCollectionToDtoMapper.getBranchDtoList(namespace, name, + getListOfObjects(source.getBranches(), branchName -> new Branch(branchName, source.getId())))); + } + } + target.withEmbedded("parents", getListOfObjects(source.getParents(), parent -> changesetToParentDtoMapper.map(new Changeset(parent, 0L, null), repository))); + + Links.Builder linksBuilder = linkingTo() + .self(resourceLinks.changeset().self(repository.getNamespace(), repository.getName(), target.getId())) + .single(link("diff", resourceLinks.diff().self(namespace, name, target.getId()))); + target.add(linksBuilder.build()); + } + + private List getListOfObjects(List list, Function mapFunction) { + return list + .stream() + .map(mapFunction) + .collect(Collectors.toList()); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToParentDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToParentDtoMapper.java new file mode 100644 index 0000000000..611f5e6cbb --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToParentDtoMapper.java @@ -0,0 +1,38 @@ +package sonia.scm.api.v2.resources; + + +import de.otto.edison.hal.Links; +import org.mapstruct.AfterMapping; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import sonia.scm.repository.Changeset; +import sonia.scm.repository.Repository; + +import javax.inject.Inject; + +import static de.otto.edison.hal.Link.link; +import static de.otto.edison.hal.Links.linkingTo; + +@Mapper +public abstract class ChangesetToParentDtoMapper extends BaseMapper { + + @Inject + private ResourceLinks resourceLinks; + + @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes + public abstract ParentChangesetDto map(Changeset changeset, @Context Repository repository); + + + @AfterMapping + void appendLinks(@MappingTarget ParentChangesetDto target, @Context Repository repository) { + String namespace = repository.getNamespace(); + String name = repository.getName(); + Links.Builder linksBuilder = linkingTo() + .self(resourceLinks.changeset().self(repository.getNamespace(), repository.getName(), target.getId())) + .single(link("diff", resourceLinks.diff().self(namespace, name, target.getId()))); + target.add(linksBuilder.build()); + } + +} 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 new file mode 100644 index 0000000000..176a86dda7 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java @@ -0,0 +1,27 @@ +package sonia.scm.api.v2.resources; + +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; + +public class DiffRootResource { + + + @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(); + } + + @GET + @Path("{id}") + public Response get(String id) { + throw new UnsupportedOperationException(); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionToDtoMapper.java index 415f04bfaf..8d13662b10 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionToDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionToDtoMapper.java @@ -5,7 +5,7 @@ import sonia.scm.group.GroupPermissions; import javax.inject.Inject; -public class GroupCollectionToDtoMapper extends BasicCollectionToDtoMapper { +public class GroupCollectionToDtoMapper extends BasicCollectionToDtoMapper { private final ResourceLinks resourceLinks; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java index 0605d943e7..9c7a390597 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java @@ -28,6 +28,11 @@ public class MapperModule extends AbstractModule { bind(PermissionDtoToPermissionMapper.class).to(Mappers.getMapper(PermissionDtoToPermissionMapper.class).getClass()); bind(PermissionToPermissionDtoMapper.class).to(Mappers.getMapper(PermissionToPermissionDtoMapper.class).getClass()); + bind(ChangesetToChangesetDtoMapper.class).to(Mappers.getMapper(ChangesetToChangesetDtoMapper.class).getClass()); + bind(ChangesetToParentDtoMapper.class).to(Mappers.getMapper(ChangesetToParentDtoMapper.class).getClass()); + + bind(TagToTagDtoMapper.class).to(Mappers.getMapper(TagToTagDtoMapper.class).getClass()); + bind(UriInfoStore.class).in(ServletScopes.REQUEST); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ParentChangesetDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ParentChangesetDto.java new file mode 100644 index 0000000000..17dbc0df22 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ParentChangesetDto.java @@ -0,0 +1,27 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; +import de.otto.edison.hal.Links; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class ParentChangesetDto extends HalRepresentation { + + /** + * the id of the parent changeset + */ + private String id; + + @Override + @SuppressWarnings("squid:S1185") // We want to have this method available in this package + protected HalRepresentation add(Links links) { + return super.add(links); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PersonDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PersonDto.java new file mode 100644 index 0000000000..ce35b507c8 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PersonDto.java @@ -0,0 +1,22 @@ +package sonia.scm.api.v2.resources; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class PersonDto { + + /** + * mail address of the person + */ + private String mail; + + /** + * name of the person + */ + private String name; + +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionToDtoMapper.java index a1cf0218e4..9a5fbb1429 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionToDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionToDtoMapper.java @@ -7,7 +7,7 @@ import javax.inject.Inject; // Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. @SuppressWarnings("squid:S3306") -public class RepositoryCollectionToDtoMapper extends BasicCollectionToDtoMapper { +public class RepositoryCollectionToDtoMapper extends BasicCollectionToDtoMapper { private final ResourceLinks resourceLinks; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java index 0263b81048..84ad8f57bc 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java @@ -40,13 +40,13 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper tags) { + return new HalRepresentation(createLinks(namespace, name), embedDtos(getTagDtoList(namespace, name, tags))); + } + + public List getTagDtoList(String namespace, String name, Collection tags) { + return tags.stream().map(tag -> tagToTagDtoMapper.map(tag, new NamespaceAndName(namespace, name))).collect(toList()); + } + + private Links createLinks(String namespace, String name) { + return + linkingTo() + .self(resourceLinks.tag().all(namespace, name)) + .build(); + } + + private Embedded embedDtos(List dtos) { + return embeddedBuilder() + .with("tags", dtos) + .build(); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagDto.java new file mode 100644 index 0000000000..8af036f5a3 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagDto.java @@ -0,0 +1,24 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; +import de.otto.edison.hal.Links; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class TagDto extends HalRepresentation { + + private String name; + + private String revision; + + @Override + @SuppressWarnings("squid:S1185") // We want to have this method available in this package + protected HalRepresentation add(Links links) { + return super.add(links); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagRootResource.java index 29a2a922ca..10b4607fbc 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagRootResource.java @@ -1,20 +1,27 @@ package sonia.scm.api.v2.resources; -import javax.inject.Inject; -import javax.inject.Provider; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; public class TagRootResource { - private final Provider tagCollectionResource; - - @Inject - public TagRootResource(Provider tagCollectionResource) { - this.tagCollectionResource = tagCollectionResource; - } + @GET @Path("") - public TagCollectionResource getTagCollectionResource() { - return tagCollectionResource.get(); + 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(); } + + @GET + @Path("{id}") + public Response get(String id) { + throw new UnsupportedOperationException(); + } + } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java new file mode 100644 index 0000000000..ada0fa2887 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java @@ -0,0 +1,31 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.Links; +import org.mapstruct.AfterMapping; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import sonia.scm.repository.NamespaceAndName; +import sonia.scm.repository.Tag; + +import javax.inject.Inject; + +import static de.otto.edison.hal.Links.linkingTo; + +@Mapper +public abstract class TagToTagDtoMapper { + + @Inject + private ResourceLinks resourceLinks; + + @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes + public abstract TagDto map(Tag tag, @Context NamespaceAndName namespaceAndName); + + @AfterMapping + void appendLinks(@MappingTarget TagDto target, @Context NamespaceAndName namespaceAndName) { + Links.Builder linksBuilder = linkingTo() + .self(resourceLinks.tag().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getName())); + target.add(linksBuilder.build()); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionToDtoMapper.java index db2a9afc9f..01517d63e7 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionToDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionToDtoMapper.java @@ -7,7 +7,7 @@ import javax.inject.Inject; // Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. @SuppressWarnings("squid:S3306") -public class UserCollectionToDtoMapper extends BasicCollectionToDtoMapper { +public class UserCollectionToDtoMapper extends BasicCollectionToDtoMapper { private final ResourceLinks resourceLinks; 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 1b23b18f27..6ace303b21 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 @@ -45,7 +45,7 @@ public class BranchRootResourceTest { public void prepareEnvironment() throws Exception { BranchCollectionToDtoMapper branchCollectionToDtoMapper = new BranchCollectionToDtoMapper(branchToDtoMapper, resourceLinks); BranchRootResource branchRootResource = new BranchRootResource(serviceFactory, branchToDtoMapper, branchCollectionToDtoMapper); - RepositoryRootResource repositoryRootResource = new RepositoryRootResource(MockProvider.of(new RepositoryResource(null, null, null, null, MockProvider.of(branchRootResource), 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); 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/PermissionRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PermissionRootResourceTest.java index 3736f99301..8f7e40425a 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 @@ -28,7 +28,11 @@ import org.junit.jupiter.api.TestFactory; import org.mockito.InjectMocks; import org.mockito.Mock; import sonia.scm.api.rest.AuthorizationExceptionMapper; -import sonia.scm.repository.*; +import sonia.scm.repository.NamespaceAndName; +import sonia.scm.repository.Permission; +import sonia.scm.repository.PermissionType; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryManager; import sonia.scm.web.VndMediaType; import java.io.IOException; @@ -46,7 +50,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.mockito.Matchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; @Slf4j @@ -129,7 +136,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); + .of(new RepositoryResource(null, null, null, null, null, null, null, null, MockProvider.of(permissionRootResource), null)), null); subjectThreadState.bind(); ThreadContext.bind(subject); dispatcher.getRegistry().addSingletonResource(repositoryRootResource); 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 31cacf2d72..e7912c55bc 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 @@ -80,7 +80,7 @@ public class RepositoryRootResourceTest { @Before public void prepareEnvironment() { initMocks(this); - RepositoryResource repositoryResource = new RepositoryResource(repositoryToDtoMapper, dtoToRepositoryMapper, repositoryManager, null, null, null, null, null, null); + RepositoryResource repositoryResource = new RepositoryResource(repositoryToDtoMapper, dtoToRepositoryMapper, repositoryManager, 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 6df5ac1a7a..6be360c585 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 @@ -19,7 +19,7 @@ public class ResourceLinksMock { when(resourceLinks.groupCollection()).thenReturn(new ResourceLinks.GroupCollectionLinks(uriInfo)); when(resourceLinks.repository()).thenReturn(new ResourceLinks.RepositoryLinks(uriInfo)); when(resourceLinks.repositoryCollection()).thenReturn(new ResourceLinks.RepositoryCollectionLinks(uriInfo)); - when(resourceLinks.tagCollection()).thenReturn(new ResourceLinks.TagCollectionLinks(uriInfo)); + 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.source()).thenReturn(new ResourceLinks.SourceLinks(uriInfo)); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksTest.java index 000e628f26..b65b516902 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksTest.java @@ -105,7 +105,7 @@ public class ResourceLinksTest { @Test public void shouldCreateCorrectTagCollectionUrl() { - String url = resourceLinks.tagCollection().self("space", "repo"); + String url = resourceLinks.tag().all("space", "repo"); assertEquals(BASE_URL + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/tags/", url); } @@ -141,7 +141,7 @@ public class ResourceLinksTest { @Test public void shouldCreateCorrectChangesetCollectionUrl() { - String url = resourceLinks.changeset().self("space", "repo"); + String url = resourceLinks.changeset().all("space", "repo"); assertEquals(BASE_URL + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/changesets/", url); } From 2bd3fb5a4ff37f4b122cb3f6de551ef9eb743939 Mon Sep 17 00:00:00 2001 From: Mohamed Karray Date: Thu, 30 Aug 2018 12:11:24 +0200 Subject: [PATCH 03/10] #8630 implement the get changeset endpoint --- .../api/v2/resources/BranchRootResource.java | 50 +++++- .../v2/resources/ChangesetRootResource.java | 69 +++++--- .../v2/resources/BranchRootResourceTest.java | 88 +++++++++- .../resources/ChangesetRootResourceTest.java | 165 ++++++++++++++++++ .../api/v2/resources/ResourceLinksMock.java | 1 + 5 files changed, 335 insertions(+), 38 deletions(-) create mode 100644 scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java index b06b5b9eba..a521f2b116 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java @@ -3,34 +3,44 @@ 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 sonia.scm.PageResult; import sonia.scm.repository.Branches; +import sonia.scm.repository.Changeset; +import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.NamespaceAndName; +import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryException; import sonia.scm.repository.RepositoryNotFoundException; +import sonia.scm.repository.RepositoryPermissions; import sonia.scm.repository.api.CommandNotSupportedException; 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; public class BranchRootResource { - private final RepositoryServiceFactory servicefactory; + private final RepositoryServiceFactory serviceFactory; private final BranchToBranchDtoMapper branchToDtoMapper; private final BranchCollectionToDtoMapper branchCollectionToDtoMapper; + private final ChangesetCollectionToDtoMapper changesetCollectionToDtoMapper; + @Inject - public BranchRootResource(RepositoryServiceFactory servicefactory, BranchToBranchDtoMapper branchToDtoMapper, BranchCollectionToDtoMapper branchCollectionToDtoMapper) { - this.servicefactory = servicefactory; + public BranchRootResource(RepositoryServiceFactory serviceFactory, BranchToBranchDtoMapper branchToDtoMapper, BranchCollectionToDtoMapper branchCollectionToDtoMapper, ChangesetCollectionToDtoMapper changesetCollectionToDtoMapper) { + this.serviceFactory = serviceFactory; this.branchToDtoMapper = branchToDtoMapper; this.branchCollectionToDtoMapper = branchCollectionToDtoMapper; + this.changesetCollectionToDtoMapper = changesetCollectionToDtoMapper; } /** @@ -56,7 +66,7 @@ public class BranchRootResource { @ResponseCode(code = 500, condition = "internal server error") }) public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("branch") String branchName) throws IOException, RepositoryException { - try (RepositoryService repositoryService = servicefactory.create(new NamespaceAndName(namespace, name))) { + try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { Branches branches = repositoryService.getBranchesCommand().getBranches(); return branches.getBranches() .stream() @@ -75,8 +85,34 @@ public class BranchRootResource { @Path("{branch}/changesets/") @GET - public Response history(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("branch") String branchName) { - throw new UnsupportedOperationException(); + @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 history(@PathParam("namespace") String namespace, + @PathParam("name") String name, + @PathParam("branch") String branchName, + @DefaultValue("0") @QueryParam("page") int page, + @DefaultValue("10") @QueryParam("pageSize") int pageSize) throws Exception { + RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name)); + Repository repository = repositoryService.getRepository(); + RepositoryPermissions.read(repository).check(); + ChangesetPagingResult changesets = repositoryService.getLogCommand() + .setPagingStart(page) + .setPagingLimit(pageSize) + .setBranch(branchName) + .getChangesets(); + if (changesets != null && changesets.getChangesets() != null) { + PageResult pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal()); + return Response.ok(changesetCollectionToDtoMapper.map(page, pageSize, pageResult, repository)).build(); + } else { + return Response.ok().build(); + } } /** @@ -101,7 +137,7 @@ public class BranchRootResource { @ResponseCode(code = 500, condition = "internal server error") }) public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name) throws IOException, RepositoryException { - try (RepositoryService repositoryService = servicefactory.create(new NamespaceAndName(namespace, name))) { + try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { Branches branches = repositoryService.getBranchesCommand().getBranches(); return Response.ok(branchCollectionToDtoMapper.map(namespace, name, branches.getBranches())).build(); } catch (CommandNotSupportedException ex) { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java index aa3aa5924b..57194d5851 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java @@ -10,7 +10,7 @@ import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryException; -import sonia.scm.repository.RepositoryNotFoundException; +import sonia.scm.repository.RepositoryPermissions; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; import sonia.scm.web.VndMediaType; @@ -33,10 +33,13 @@ public class ChangesetRootResource { private final ChangesetCollectionToDtoMapper changesetCollectionToDtoMapper; + private final ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper; + @Inject - public ChangesetRootResource(RepositoryServiceFactory serviceFactory, ChangesetCollectionToDtoMapper changesetCollectionToDtoMapper) { + public ChangesetRootResource(RepositoryServiceFactory serviceFactory, ChangesetCollectionToDtoMapper changesetCollectionToDtoMapper, ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper) { this.serviceFactory = serviceFactory; this.changesetCollectionToDtoMapper = changesetCollectionToDtoMapper; + this.changesetToChangesetDtoMapper = changesetToChangesetDtoMapper; } @GET @@ -45,40 +48,52 @@ public class ChangesetRootResource { @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 changeset with the specified name available in the repository"), + @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, @DefaultValue("0") @QueryParam("page") int page, - @DefaultValue("10") @QueryParam("pageSize") int pageSize) { - try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { - ChangesetPagingResult changesets; - Repository repository = repositoryService.getRepository(); - changesets = repositoryService.getLogCommand() - .setPagingStart(page) - .setPagingLimit(pageSize) - .getChangesets(); - if (changesets != null) { - PageResult pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal()); - return Response.ok(changesetCollectionToDtoMapper.map(page, pageSize, pageResult, repository)).build(); - } else { - return Response.ok().build(); - } - } catch (RepositoryNotFoundException e) { - log.debug("Not found in repository {}/{}", namespace, name, e); - return Response.status(Response.Status.NOT_FOUND).build(); - } catch (RepositoryException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); + @DefaultValue("10") @QueryParam("pageSize") int pageSize) throws IOException, RepositoryException { + RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name)); + Repository repository = repositoryService.getRepository(); + RepositoryPermissions.read(repository).check(); + ChangesetPagingResult changesets = repositoryService.getLogCommand() + .setPagingStart(page) + .setPagingLimit(pageSize) + .getChangesets(); + if (changesets != null && changesets.getChangesets() != null) { + PageResult pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal()); + return Response.ok(changesetCollectionToDtoMapper.map(page, pageSize, pageResult, repository)).build(); + } else { + return Response.ok().build(); } - return Response.ok().build(); } @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 changeset with the specified id is available in the repository"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @Produces(VndMediaType.CHANGESET) + @TypeHint(ChangesetDto.class) @Path("{id}") - public Response get(@PathParam("id") String id) { - throw new UnsupportedOperationException(); + public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("id") String id) throws RepositoryException, IOException { + RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name)); + Repository repository = repositoryService.getRepository(); + RepositoryPermissions.read(repository).check(); + ChangesetPagingResult changesets = repositoryService.getLogCommand() + .setStartChangeset(id) + .setEndChangeset(id) + .getChangesets(); + if (changesets != null && changesets.getChangesets() != null && changesets.getChangesets().size() == 1) { + PageResult pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal()); + return Response.ok(changesetToChangesetDtoMapper.map(changesets.getChangesets().get(0), repository)).build(); + } else { + return Response.status(Response.Status.NOT_FOUND).build(); + } } } 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 6ace303b21..c7f0b128df 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 @@ -1,5 +1,11 @@ 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; @@ -12,20 +18,35 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.Branch; import sonia.scm.repository.Branches; +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.BranchesCommandBuilder; +import sonia.scm.repository.api.LogCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; import java.net.URI; +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.anyString; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.Silent.class) +@Slf4j public class BranchRootResourceTest { + public static final String BRANCH_PATH = "space/repo/branches/master"; + public static final String BRANCH_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + BRANCH_PATH; private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); private final URI baseUri = URI.create("/"); @@ -38,25 +59,57 @@ public class BranchRootResourceTest { @Mock private BranchesCommandBuilder branchesCommandBuilder; + @Mock + private LogCommandBuilder logCommandBuilder; + @InjectMocks private BranchToBranchDtoMapperImpl branchToDtoMapper; + private ChangesetCollectionToDtoMapper changesetCollectionToDtoMapper; + + private BranchRootResource branchRootResource; + + @Mock + private BranchCollectionToDtoMapper branchCollectionToDtoMapper; + + @Mock + private ChangesetToParentDtoMapper changesetToParentDtoMapper; + + @Mock + private TagCollectionToDtoMapper tagCollectionToDtoMapper; + + + @InjectMocks + private ChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper; + + private final Subject subject = mock(Subject.class); + private final ThreadState subjectThreadState = new SubjectThreadState(subject); + + @Before public void prepareEnvironment() throws Exception { + changesetCollectionToDtoMapper = new ChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks); BranchCollectionToDtoMapper branchCollectionToDtoMapper = new BranchCollectionToDtoMapper(branchToDtoMapper, resourceLinks); - BranchRootResource branchRootResource = new BranchRootResource(serviceFactory, branchToDtoMapper, branchCollectionToDtoMapper); + 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); 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")); + when(service.getBranchesCommand()).thenReturn(branchesCommandBuilder); + when(service.getLogCommand()).thenReturn(logCommandBuilder); + subjectThreadState.bind(); + ThreadContext.bind(subject); + when(subject.isPermitted(any(String.class))).thenReturn(true); } @Test public void shouldHandleMissingBranch() throws Exception { when(branchesCommandBuilder.getBranches()).thenReturn(new Branches()); - MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/branches/master"); + MockHttpRequest request = MockHttpRequest.get(BRANCH_URL); MockHttpResponse response = new MockHttpResponse(); dispatcher.invoke(request, response); @@ -68,13 +121,40 @@ public class BranchRootResourceTest { public void shouldFindExistingBranch() throws Exception { when(branchesCommandBuilder.getBranches()).thenReturn(new Branches(new Branch("master", "revision"))); - MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/branches/master"); + MockHttpRequest request = MockHttpRequest.get(BRANCH_URL); MockHttpResponse response = new MockHttpResponse(); dispatcher.invoke(request, response); assertEquals(200, response.getStatus()); - System.out.println(response.getContentAsString()); + log.info("Response :{}", response.getContentAsString()); assertTrue(response.getContentAsString().contains("\"revision\":\"revision\"")); } + + @Test + public void shouldFindHistory() 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 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.setBranch(anyString())).thenReturn(logCommandBuilder); + when(logCommandBuilder.getChangesets()).thenReturn(changesetPagingResult); + MockHttpRequest request = MockHttpRequest.get(BRANCH_URL + "/changesets/"); + 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))); + assertTrue(response.getContentAsString().contains(String.format("\"description\":\"%s\"", commit))); + } } 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 new file mode 100644 index 0000000000..4e7215c8eb --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java @@ -0,0 +1,165 @@ +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.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.NamespaceAndName; +import sonia.scm.repository.Person; +import sonia.scm.repository.Repository; +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.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.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.Silent.class) +@Slf4j +public class ChangesetRootResourceTest { + + + public static final String CHANGESET_PATH = "space/repo/changesets/"; + public static final String CHANGESET_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + CHANGESET_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 ChangesetCollectionToDtoMapper changesetCollectionToDtoMapper; + + @Mock + private BranchCollectionToDtoMapper branchCollectionToDtoMapper; + + @Mock + private ChangesetToParentDtoMapperImpl changesetToParentDtoMapper; + + @Mock + private TagCollectionToDtoMapper tagCollectionToDtoMapper; + + + @InjectMocks + private ChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper; + + private ChangesetRootResource changesetRootResource; + + + private final Subject subject = mock(Subject.class); + private final ThreadState subjectThreadState = new SubjectThreadState(subject); + + + @Before + public void prepareEnvironment() throws Exception { + changesetCollectionToDtoMapper = new ChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks); + 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); + 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(RepositoryNotFoundExceptionMapper.class); + dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class); + when(service.getLogCommand()).thenReturn(logCommandBuilder); + subjectThreadState.bind(); + ThreadContext.bind(subject); + when(subject.isPermitted(any(String.class))).thenReturn(true); + } + + @Test + public void shouldGetChangeSets() 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 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.setBranch(anyString())).thenReturn(logCommandBuilder); + when(logCommandBuilder.getChangesets()).thenReturn(changesetPagingResult); + MockHttpRequest request = MockHttpRequest + .get(CHANGESET_URL) + .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))); + assertTrue(response.getContentAsString().contains(String.format("\"description\":\"%s\"", commit))); + } + + @Test + public void shouldGetChangeSet() 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 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.setEndChangeset(anyString())).thenReturn(logCommandBuilder); + when(logCommandBuilder.setStartChangeset(anyString())).thenReturn(logCommandBuilder); + when(logCommandBuilder.getChangesets()).thenReturn(changesetPagingResult); + MockHttpRequest request = MockHttpRequest + .get(CHANGESET_URL + "id") + .accept(VndMediaType.CHANGESET); + 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))); + assertTrue(response.getContentAsString().contains(String.format("\"description\":\"%s\"", commit))); + } + +} 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 6be360c585..ff729a6478 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 @@ -26,6 +26,7 @@ public class ResourceLinksMock { when(resourceLinks.permission()).thenReturn(new ResourceLinks.PermissionLinks(uriInfo)); when(resourceLinks.config()).thenReturn(new ResourceLinks.ConfigLinks(uriInfo)); when(resourceLinks.branch()).thenReturn(new ResourceLinks.BranchLinks(uriInfo)); + when(resourceLinks.diff()).thenReturn(new ResourceLinks.DiffLinks(uriInfo)); when(resourceLinks.repositoryType()).thenReturn(new ResourceLinks.RepositoryTypeLinks(uriInfo)); when(resourceLinks.repositoryTypeCollection()).thenReturn(new ResourceLinks.RepositoryTypeCollectionLinks(uriInfo)); From 2420c9141c4608295925c5cb7e5806380f39b0f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 31 Aug 2018 08:46:56 +0200 Subject: [PATCH 04/10] Unbind subjects after unit tests --- .../sonia/scm/api/v2/resources/BranchRootResourceTest.java | 6 ++++++ .../scm/api/v2/resources/ChangesetRootResourceTest.java | 6 ++++++ 2 files changed, 12 insertions(+) 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 c7f0b128df..e23d3dc39b 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 @@ -10,6 +10,7 @@ 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; @@ -105,6 +106,11 @@ public class BranchRootResourceTest { when(subject.isPermitted(any(String.class))).thenReturn(true); } + @After + public void cleanupContext() { + ThreadContext.unbindSubject(); + } + @Test public void shouldHandleMissingBranch() throws Exception { when(branchesCommandBuilder.getBranches()).thenReturn(new Branches()); 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 cb9c545041..8be8866395 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 @@ -11,6 +11,7 @@ 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; @@ -103,6 +104,11 @@ public class ChangesetRootResourceTest { when(subject.isPermitted(any(String.class))).thenReturn(true); } + @After + public void cleanupContext() { + ThreadContext.unbindSubject(); + } + @Test public void shouldGetChangeSets() throws Exception { String id = "revision_123"; From ed98d04be4060b9a7cb74078efa67384aa9aaae2 Mon Sep 17 00:00:00 2001 From: Mohamed Karray Date: Fri, 31 Aug 2018 09:55:45 +0200 Subject: [PATCH 05/10] #8630 use try-with-resources to close used resources --- .../repository/api/BundleCommandBuilder.java | 2 +- .../scm/repository/GitSubModuleParser.java | 103 +++++------------- .../api/v2/resources/BranchRootResource.java | 35 +++--- .../v2/resources/ChangesetRootResource.java | 50 +++++---- .../sonia/scm/plugin/PluginProcessor.java | 22 ++-- 5 files changed, 80 insertions(+), 132 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/api/BundleCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/BundleCommandBuilder.java index 4fed0a7b60..1f4defbd8b 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/BundleCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/BundleCommandBuilder.java @@ -140,7 +140,7 @@ public final class BundleCommandBuilder throws IOException { checkNotNull(sink, "byte sink is required"); - logger.info("bundle {} to byte sink"); + logger.info("bundle {} to byte sink", sink); return bundleCommand.bundle(new BundleCommandRequest(sink)); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitSubModuleParser.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitSubModuleParser.java index 7244a1f2bb..82e2919996 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitSubModuleParser.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitSubModuleParser.java @@ -1,19 +1,19 @@ /** * Copyright (c) 2010, Sebastian Sdorra * All rights reserved. - * + *

* Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: - * + *

* 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. + * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. * 3. Neither the name of SCM-Manager; nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + *

* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -24,96 +24,49 @@ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * + *

* http://bitbucket.org/sdorra/scm-manager - * */ - package sonia.scm.repository; -//~--- non-JDK imports -------------------------------------------------------- - import sonia.scm.util.Util; -//~--- JDK imports ------------------------------------------------------------ - import java.util.HashMap; import java.util.Map; import java.util.Scanner; /** - * * @author Sebastian Sdorra */ -public final class GitSubModuleParser -{ +public final class GitSubModuleParser { - /** - * Constructs ... - * - */ - private GitSubModuleParser() {} + private GitSubModuleParser() { + } - //~--- methods -------------------------------------------------------------- - - /** - * //~--- methods -------------------------------------------------------------- - * - * - * Method description - * - * - * @param content - * - * @return - */ - public static Map parse(String content) - { - Map subRepositories = new HashMap(); - Scanner scanner = new Scanner(content); - SubRepository repository = null; - - while (scanner.hasNextLine()) - { - String line = scanner.nextLine(); - - if (Util.isNotEmpty(line)) - { - line = line.trim(); - - if (line.startsWith("[") && line.endsWith("]")) - { - repository = new SubRepository(); - } - else if (line.startsWith("path")) - { - subRepositories.put(getValue(line), repository); - } - else if (line.startsWith("url")) - { - repository.setRepositoryUrl(getValue(line)); + public static Map parse(String content) { + Map subRepositories = new HashMap<>(); + try (Scanner scanner = new Scanner(content)) { + SubRepository repository = null; + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + if (Util.isNotEmpty(line)) { + line = line.trim(); + if (line.startsWith("[") && line.endsWith("]")) { + repository = new SubRepository(); + } else if (line.startsWith("path")) { + subRepositories.put(getValue(line), repository); + } else if (line.startsWith("url")) { + repository.setRepositoryUrl(getValue(line)); + } } } } - return subRepositories; } - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param line - * - * @return - */ - private static String getValue(String line) - { + private static String getValue(String line) { return line.split("=")[1].trim(); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java index fb094ea6ff..9763f11def 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java @@ -47,10 +47,9 @@ public class BranchRootResource { * * Note: This method requires "repository" privilege. * - * @param namespace the namespace of the repository - * @param name the name of the repository + * @param namespace the namespace of the repository + * @param name the name of the repository * @param branchName the name of the branch - * */ @GET @Path("{branch}") @@ -98,19 +97,20 @@ public class BranchRootResource { @PathParam("branch") String branchName, @DefaultValue("0") @QueryParam("page") int page, @DefaultValue("10") @QueryParam("pageSize") int pageSize) throws Exception { - RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name)); - Repository repository = repositoryService.getRepository(); - RepositoryPermissions.read(repository).check(); - ChangesetPagingResult changesets = repositoryService.getLogCommand() - .setPagingStart(page) - .setPagingLimit(pageSize) - .setBranch(branchName) - .getChangesets(); - if (changesets != null && changesets.getChangesets() != null) { - PageResult pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal()); - return Response.ok(changesetCollectionToDtoMapper.map(page, pageSize, pageResult, repository)).build(); - } else { - return Response.ok().build(); + try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { + Repository repository = repositoryService.getRepository(); + RepositoryPermissions.read(repository).check(); + ChangesetPagingResult changesets = repositoryService.getLogCommand() + .setPagingStart(page) + .setPagingLimit(pageSize) + .setBranch(branchName) + .getChangesets(); + if (changesets != null && changesets.getChangesets() != null) { + PageResult pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal()); + return Response.ok(changesetCollectionToDtoMapper.map(page, pageSize, pageResult, repository)).build(); + } else { + return Response.ok().build(); + } } } @@ -120,8 +120,7 @@ public class BranchRootResource { * Note: This method requires "repository" privilege. * * @param namespace the namespace of the repository - * @param name the name of the repository - * + * @param name the name of the repository */ @GET @Path("") diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java index cb827e1546..c3fa71f8ed 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java @@ -56,18 +56,19 @@ public class ChangesetRootResource { @TypeHint(CollectionDto.class) public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name, @DefaultValue("0") @QueryParam("page") int page, @DefaultValue("10") @QueryParam("pageSize") int pageSize) throws IOException, RevisionNotFoundException, RepositoryNotFoundException { - RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name)); - Repository repository = repositoryService.getRepository(); - RepositoryPermissions.read(repository).check(); - ChangesetPagingResult changesets = repositoryService.getLogCommand() - .setPagingStart(page) - .setPagingLimit(pageSize) - .getChangesets(); - if (changesets != null && changesets.getChangesets() != null) { - PageResult pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal()); - return Response.ok(changesetCollectionToDtoMapper.map(page, pageSize, pageResult, repository)).build(); - } else { - return Response.ok().build(); + try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { + Repository repository = repositoryService.getRepository(); + RepositoryPermissions.read(repository).check(); + ChangesetPagingResult changesets = repositoryService.getLogCommand() + .setPagingStart(page) + .setPagingLimit(pageSize) + .getChangesets(); + if (changesets != null && changesets.getChangesets() != null) { + PageResult pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal()); + return Response.ok(changesetCollectionToDtoMapper.map(page, pageSize, pageResult, repository)).build(); + } else { + return Response.ok().build(); + } } } @@ -83,18 +84,19 @@ public class ChangesetRootResource { @TypeHint(ChangesetDto.class) @Path("{id}") public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("id") String id) throws IOException, RevisionNotFoundException, RepositoryNotFoundException { - RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name)); - Repository repository = repositoryService.getRepository(); - RepositoryPermissions.read(repository).check(); - ChangesetPagingResult changesets = repositoryService.getLogCommand() - .setStartChangeset(id) - .setEndChangeset(id) - .getChangesets(); - if (changesets != null && changesets.getChangesets() != null && changesets.getChangesets().size() == 1) { - PageResult pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal()); - return Response.ok(changesetToChangesetDtoMapper.map(changesets.getChangesets().get(0), repository)).build(); - } else { - return Response.status(Response.Status.NOT_FOUND).build(); + try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { + Repository repository = repositoryService.getRepository(); + RepositoryPermissions.read(repository).check(); + ChangesetPagingResult changesets = repositoryService.getLogCommand() + .setStartChangeset(id) + .setEndChangeset(id) + .getChangesets(); + if (changesets != null && changesets.getChangesets() != null && changesets.getChangesets().size() == 1) { + PageResult pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal()); + return Response.ok(changesetToChangesetDtoMapper.map(changesets.getChangesets().get(0), repository)).build(); + } else { + return Response.status(Response.Status.NOT_FOUND).build(); + } } } } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java index 2de00a0f58..4e4e97591a 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java @@ -39,34 +39,27 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.common.hash.Hashing; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sonia.scm.plugin.ExplodedSmp.PathTransformer; -//~--- JDK imports ------------------------------------------------------------ - +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; - import java.net.URL; - import java.nio.file.DirectoryStream; import java.nio.file.DirectoryStream.Filter; import java.nio.file.Files; import java.nio.file.Path; - import java.text.SimpleDateFormat; - import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Set; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBException; +//~--- JDK imports ------------------------------------------------------------ /** * @@ -370,9 +363,10 @@ public final class PluginProcessor if (Files.exists(libDir)) { - for (Path f : Files.newDirectoryStream(libDir, GLOB_JAR)) - { - urls.add(f.toUri().toURL()); + try (DirectoryStream pathDirectoryStream = Files.newDirectoryStream(libDir, GLOB_JAR)) { + for (Path f : pathDirectoryStream) { + urls.add(f.toUri().toURL()); + } } } @@ -656,7 +650,7 @@ public final class PluginProcessor break; } } - + logger.debug("move installed archive to {}", installed); Files.move(archive, installed); From d4a8c05c2513d6ad3f1f176d43b949573ac912f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 31 Aug 2018 12:05:05 +0200 Subject: [PATCH 06/10] Remove dead code --- .../scm/api/v2/resources/ChangesetRootResource.java | 1 - .../api/v2/resources/ChangesetRootResourceTest.java | 10 ---------- 2 files changed, 11 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java index c3fa71f8ed..717b8d7198 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java @@ -92,7 +92,6 @@ public class ChangesetRootResource { .setEndChangeset(id) .getChangesets(); if (changesets != null && changesets.getChangesets() != null && changesets.getChangesets().size() == 1) { - PageResult pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal()); return Response.ok(changesetToChangesetDtoMapper.map(changesets.getChangesets().get(0), repository)).build(); } else { return Response.status(Response.Status.NOT_FOUND).build(); 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 8be8866395..570830d651 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 @@ -65,16 +65,6 @@ public class ChangesetRootResourceTest { private ChangesetCollectionToDtoMapper changesetCollectionToDtoMapper; - @Mock - private BranchCollectionToDtoMapper branchCollectionToDtoMapper; - - @Mock - private ChangesetToParentDtoMapperImpl changesetToParentDtoMapper; - - @Mock - private TagCollectionToDtoMapper tagCollectionToDtoMapper; - - @InjectMocks private ChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper; From 6fa2ba7f61a7e9d7451a9180f293d59dd2a6c825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 31 Aug 2018 12:05:39 +0200 Subject: [PATCH 07/10] Recognize missing revisions in git --- .../scm/repository/spi/GitLogCommand.java | 60 ++++++---------- .../scm/repository/spi/GitLogCommandTest.java | 72 +++---------------- 2 files changed, 33 insertions(+), 99 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java index 65a6f9daf8..beb79cb921 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java @@ -37,6 +37,7 @@ package sonia.scm.repository.spi; import com.google.common.base.Strings; import com.google.common.collect.Lists; +import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; @@ -51,6 +52,7 @@ import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.GitChangesetConverter; import sonia.scm.repository.GitUtil; import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.util.IOUtil; import java.io.IOException; @@ -156,15 +158,11 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand * @return * * @throws IOException - * @throws RepositoryException */ @Override @SuppressWarnings("unchecked") - public ChangesetPagingResult getChangesets(LogCommandRequest request) - throws IOException - { - if (logger.isDebugEnabled()) - { + public ChangesetPagingResult getChangesets(LogCommandRequest request) throws RevisionNotFoundException { + if (logger.isDebugEnabled()) { logger.debug("fetch changesets for request: {}", request); } @@ -172,17 +170,13 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand GitChangesetConverter converter = null; RevWalk revWalk = null; - try (org.eclipse.jgit.lib.Repository gr = open()) - { - if (!gr.getAllRefs().isEmpty()) - { + try (org.eclipse.jgit.lib.Repository gr = open()) { + if (!gr.getAllRefs().isEmpty()) { int counter = 0; int start = request.getPagingStart(); - if (start < 0) - { - if (logger.isErrorEnabled()) - { + if (start < 0) { + if (logger.isErrorEnabled()) { logger.error("start parameter is negative, reset to 0"); } @@ -193,15 +187,13 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand int limit = request.getPagingLimit(); ObjectId startId = null; - if (!Strings.isNullOrEmpty(request.getStartChangeset())) - { + if (!Strings.isNullOrEmpty(request.getStartChangeset())) { startId = gr.resolve(request.getStartChangeset()); } ObjectId endId = null; - if (!Strings.isNullOrEmpty(request.getEndChangeset())) - { + if (!Strings.isNullOrEmpty(request.getEndChangeset())) { endId = gr.resolve(request.getEndChangeset()); } @@ -209,57 +201,51 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand converter = new GitChangesetConverter(gr, revWalk); - if (!Strings.isNullOrEmpty(request.getPath())) - { + if (!Strings.isNullOrEmpty(request.getPath())) { revWalk.setTreeFilter( AndTreeFilter.create( PathFilter.create(request.getPath()), TreeFilter.ANY_DIFF)); } - + ObjectId head = getBranchOrDefault(gr, request.getBranch()); - if (head != null) - { - if (startId != null) - { + if (head != null) { + if (startId != null) { revWalk.markStart(revWalk.lookupCommit(startId)); - } - else - { + } else { revWalk.markStart(revWalk.lookupCommit(head)); } Iterator iterator = revWalk.iterator(); - while (iterator.hasNext()) - { + while (iterator.hasNext()) { RevCommit commit = iterator.next(); if ((counter >= start) - && ((limit < 0) || (counter < start + limit))) - { + && ((limit < 0) || (counter < start + limit))) { changesetList.add(converter.createChangeset(commit)); } counter++; - if ((endId != null) && commit.getId().equals(endId)) - { + if ((endId != null) && commit.getId().equals(endId)) { break; } } } changesets = new ChangesetPagingResult(counter, changesetList); - } - else if (logger.isWarnEnabled()) - { + } else if (logger.isWarnEnabled()) { logger.warn("the repository {} seems to be empty", repository.getName()); changesets = new ChangesetPagingResult(0, Collections.EMPTY_LIST); } } + catch (MissingObjectException e) + { + throw new RevisionNotFoundException(e.getObjectId().name()); + } catch (Exception ex) { throw new InternalRepositoryException("could not create change log", ex); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java index 1d78485ae3..51df1298e5 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java @@ -35,15 +35,12 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- -import org.eclipse.jgit.api.errors.GitAPIException; import org.junit.Test; import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.GitConstants; import sonia.scm.repository.Modifications; -import java.io.IOException; - import static org.hamcrest.Matchers.contains; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -55,7 +52,7 @@ import static org.junit.Assert.assertTrue; /** * Unit tests for {@link GitLogCommand}. - * + * * @author Sebastian Sdorra */ public class GitLogCommandTest extends AbstractGitCommandTestBase @@ -63,13 +60,9 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase /** * Tests log command with the usage of a default branch. - * - * @throws IOException - * @throws GitAPIException - * @ */ @Test - public void testGetDefaultBranch() throws IOException, GitAPIException { + public void testGetDefaultBranch() throws Exception { // without default branch, the repository head should be used ChangesetPagingResult result = createCommand().getChangesets(new LogCommandRequest()); @@ -79,10 +72,10 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase assertEquals("86a6645eceefe8b9a247db5eb16e3d89a7e6e6d1", result.getChangesets().get(1).getId()); assertEquals("592d797cd36432e591416e8b2b98154f4f163411", result.getChangesets().get(2).getId()); assertEquals("435df2f061add3589cb326cc64be9b9c3897ceca", result.getChangesets().get(3).getId()); - + // set default branch and fetch again repository.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "test-branch"); - + result = createCommand().getChangesets(new LogCommandRequest()); assertNotNull(result); @@ -91,16 +84,9 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase assertEquals("592d797cd36432e591416e8b2b98154f4f163411", result.getChangesets().get(1).getId()); assertEquals("435df2f061add3589cb326cc64be9b9c3897ceca", result.getChangesets().get(2).getId()); } - - /** - * Method description - * - * - * @throws IOException - * @throws RepositoryException - */ + @Test - public void testGetAll() throws IOException + public void testGetAll() throws Exception { ChangesetPagingResult result = createCommand().getChangesets(new LogCommandRequest()); @@ -110,15 +96,8 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase assertEquals(4, result.getChangesets().size()); } - /** - * Method description - * - * - * @throws IOException - * @throws RepositoryException - */ @Test - public void testGetAllByPath() throws IOException + public void testGetAllByPath() throws Exception { LogCommandRequest request = new LogCommandRequest(); @@ -133,15 +112,8 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase assertEquals("435df2f061add3589cb326cc64be9b9c3897ceca", result.getChangesets().get(1).getId()); } - /** - * Method description - * - * - * @throws IOException - * @throws RepositoryException - */ @Test - public void testGetAllWithLimit() throws IOException + public void testGetAllWithLimit() throws Exception { LogCommandRequest request = new LogCommandRequest(); @@ -164,15 +136,8 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase assertEquals("86a6645eceefe8b9a247db5eb16e3d89a7e6e6d1", c2.getId()); } - /** - * Method description - * - * - * @throws IOException - * @throws RepositoryException - */ @Test - public void testGetAllWithPaging() throws IOException + public void testGetAllWithPaging() throws Exception { LogCommandRequest request = new LogCommandRequest(); @@ -196,10 +161,6 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase assertEquals("592d797cd36432e591416e8b2b98154f4f163411", c2.getId()); } - /** - * Method description - * - */ @Test public void testGetCommit() { @@ -224,15 +185,8 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase assertThat(mods.getAdded(), contains("a.txt", "b.txt")); } - /** - * Method description - * - * - * @throws IOException - * @throws RepositoryException - */ @Test - public void testGetRange() throws IOException + public void testGetRange() throws Exception { LogCommandRequest request = new LogCommandRequest(); @@ -254,12 +208,6 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase assertEquals("435df2f061add3589cb326cc64be9b9c3897ceca", c2.getId()); } - /** - * Method description - * - * - * @return - */ private GitLogCommand createCommand() { return new GitLogCommand(createContext(), repository); From b4ae92d12368da0862577bbe6f5b084786aadf50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 29 Aug 2018 13:34:53 +0200 Subject: [PATCH 08/10] Add simple changeset integration test --- .../sonia/scm/it/RepositoryAccessITCase.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) 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 a7ee8da41d..a9139f18c8 100644 --- a/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java @@ -14,7 +14,9 @@ import sonia.scm.repository.client.api.RepositoryClient; import java.io.File; 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.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertNotNull; @@ -124,4 +126,31 @@ public class RepositoryAccessITCase { .statusCode(HttpStatus.SC_OK) .body(equalTo("sub-a")); } + + @Test + public void shouldFindChangesets() 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"); + + List changesets = given() + .when() + .get(changesetsUrl) + .then() + .statusCode(HttpStatus.SC_OK) + .extract() + .path("_embedded.changesets.id"); + + assertThat(changesets).size().isBetween(2, 3); // svn has an implicit root revision '0' that is extra to the two commits + } } + From 715ac0dab4ec7d6b1213e7324e71239d8258aad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 31 Aug 2018 15:59:50 +0200 Subject: [PATCH 09/10] Do not modify changesets --- scm-core/src/main/java/sonia/scm/repository/Changeset.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/Changeset.java b/scm-core/src/main/java/sonia/scm/repository/Changeset.java index ca2c9ac5db..18ede493f6 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Changeset.java +++ b/scm-core/src/main/java/sonia/scm/repository/Changeset.java @@ -271,12 +271,12 @@ public class Changeset extends BasicPropertiesAware implements ModelObject { @Override public void setLastModified(Long timestamp) { - this.setDate(timestamp); + throw new UnsupportedOperationException("changesets are immutable"); } @Override public Long getLastModified() { - return getCreationDate(); + return null; } @Override From 87044d0a92d3fda7a079eb4dfbc94389eb4db17d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 30 Aug 2018 09:19:10 +0200 Subject: [PATCH 10/10] Mind LSP for sub classes --- .../resources/BasicCollectionToDtoMapper.java | 29 +++++++------------ .../ChangesetCollectionToDtoMapper.java | 21 +++----------- .../resources/GroupCollectionToDtoMapper.java | 22 +++++++------- .../RepositoryCollectionToDtoMapper.java | 20 +++++++------ .../resources/UserCollectionToDtoMapper.java | 20 +++++++------ 5 files changed, 49 insertions(+), 63 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BasicCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BasicCollectionToDtoMapper.java index 4787452003..b189a22d1c 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BasicCollectionToDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BasicCollectionToDtoMapper.java @@ -11,6 +11,7 @@ import sonia.scm.PageResult; import javax.inject.Inject; import java.util.EnumSet; import java.util.List; +import java.util.Optional; import java.util.function.Function; import static com.damnhandy.uri.template.UriTemplate.fromTemplate; @@ -24,7 +25,7 @@ abstract class BasicCollectionToDtoMapper pageResult) { - return map(pageNumber, pageSize, pageResult, entityToDtoMapper::map, createSelfLink()); + CollectionDto map(int pageNumber, int pageSize, PageResult pageResult, String selfLink, Optional createLink) { + return map(pageNumber, pageSize, pageResult, selfLink, createLink, entityToDtoMapper::map); } - public CollectionDto map(int pageNumber, int pageSize, PageResult pageResult, Function mapper, String selfLink) { + CollectionDto map(int pageNumber, int pageSize, PageResult pageResult, String selfLink, Optional createLink, Function mapper) { NumberedPaging paging = zeroBasedNumberedPaging(pageNumber, pageSize, pageResult.getOverallCount()); - List dtos = pageResult.getEntities().stream().map(mapper).collect(toList()); + List dtos = pageResult.getEntities().stream().map(mapper).collect(toList()); CollectionDto collectionDto = new CollectionDto( - createLinks(paging, selfLink), + createLinks(paging, selfLink, createLink), embedDtos(dtos)); collectionDto.setPage(pageNumber); collectionDto.setPageTotal(computePageTotal(pageSize, pageResult)); @@ -55,24 +56,16 @@ abstract class BasicCollectionToDtoMapper createLink) { Links.Builder linksBuilder = linkingTo() .with(page.links( - fromTemplate(baseUrl + "{?page,pageSize}"), + fromTemplate(selfLink + "{?page,pageSize}"), EnumSet.allOf(PagingRel.class))); - if (isCreatePermitted()) { - linksBuilder.single(link("create", createCreateLink())); - } + createLink.ifPresent(link -> linksBuilder.single(link("create", link))); return linksBuilder.build(); } - abstract boolean isCreatePermitted(); - - abstract String createCreateLink(); - - abstract String createSelfLink(); - - private Embedded embedDtos(List dtos) { + private Embedded embedDtos(List dtos) { return embeddedBuilder() .with(collectionName, dtos) .build(); 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 93eb372fe9..fcc4085486 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 @@ -5,38 +5,25 @@ import sonia.scm.repository.Changeset; import sonia.scm.repository.Repository; import javax.inject.Inject; +import java.util.Optional; public class ChangesetCollectionToDtoMapper extends BasicCollectionToDtoMapper { + private final ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper; private final ResourceLinks resourceLinks; @Inject public ChangesetCollectionToDtoMapper(ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper, ResourceLinks resourceLinks) { super("changesets", changesetToChangesetDtoMapper); + this.changesetToChangesetDtoMapper = changesetToChangesetDtoMapper; this.resourceLinks = resourceLinks; } public CollectionDto map(int pageNumber, int pageSize, PageResult pageResult, Repository repository) { - return super.map(pageNumber, pageSize, pageResult, changeset -> super.entityToDtoMapper.map(changeset, repository), createSelfLink(repository)); - } - - - @Override - String createCreateLink() { - return null; - } - - @Override - String createSelfLink() { - return null; + return super.map(pageNumber, pageSize, pageResult, createSelfLink(repository), Optional.empty(), changeset -> changesetToChangesetDtoMapper.map(changeset, repository)); } private String createSelfLink(Repository repository) { return resourceLinks.changeset().all(repository.getNamespace(), repository.getName()); } - - @Override - boolean isCreatePermitted() { - return false; - } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionToDtoMapper.java index 8d13662b10..1550e57ce2 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionToDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionToDtoMapper.java @@ -1,9 +1,14 @@ package sonia.scm.api.v2.resources; +import sonia.scm.PageResult; import sonia.scm.group.Group; import sonia.scm.group.GroupPermissions; import javax.inject.Inject; +import java.util.Optional; + +import static java.util.Optional.empty; +import static java.util.Optional.of; public class GroupCollectionToDtoMapper extends BasicCollectionToDtoMapper { @@ -15,18 +20,15 @@ public class GroupCollectionToDtoMapper extends BasicCollectionToDtoMapper pageResult) { + return map(pageNumber, pageSize, pageResult, this.createSelfLink(), this.createCreateLink()); } - @Override - String createSelfLink() { + private Optional createCreateLink() { + return GroupPermissions.create().isPermitted() ? of(resourceLinks.groupCollection().create()): empty(); + } + + private String createSelfLink() { return resourceLinks.groupCollection().self(); } - - @Override - boolean isCreatePermitted() { - return GroupPermissions.create().isPermitted(); - } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionToDtoMapper.java index 9a5fbb1429..77674270d7 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionToDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionToDtoMapper.java @@ -1,9 +1,14 @@ package sonia.scm.api.v2.resources; +import sonia.scm.PageResult; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryPermissions; import javax.inject.Inject; +import java.util.Optional; + +import static java.util.Optional.empty; +import static java.util.Optional.of; // Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. @SuppressWarnings("squid:S3306") @@ -17,18 +22,15 @@ public class RepositoryCollectionToDtoMapper extends BasicCollectionToDtoMapper< this.resourceLinks = resourceLinks; } - @Override - String createCreateLink() { - return resourceLinks.repositoryCollection().create(); + public CollectionDto map(int pageNumber, int pageSize, PageResult pageResult) { + return map(pageNumber, pageSize, pageResult, this.createSelfLink(), this.createCreateLink()); + } + + Optional createCreateLink() { + return RepositoryPermissions.create().isPermitted() ? of(resourceLinks.repositoryCollection().create()): empty(); } - @Override String createSelfLink() { return resourceLinks.repositoryCollection().self(); } - - @Override - boolean isCreatePermitted() { - return RepositoryPermissions.create().isPermitted(); - } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionToDtoMapper.java index 01517d63e7..c70bd268ff 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionToDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionToDtoMapper.java @@ -1,9 +1,14 @@ package sonia.scm.api.v2.resources; +import sonia.scm.PageResult; import sonia.scm.user.User; import sonia.scm.user.UserPermissions; import javax.inject.Inject; +import java.util.Optional; + +import static java.util.Optional.empty; +import static java.util.Optional.of; // Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. @SuppressWarnings("squid:S3306") @@ -17,18 +22,15 @@ public class UserCollectionToDtoMapper extends BasicCollectionToDtoMapper pageResult) { + return map(pageNumber, pageSize, pageResult, this.createSelfLink(), this.createCreateLink()); + } + + Optional createCreateLink() { + return UserPermissions.create().isPermitted() ? of(resourceLinks.userCollection().create()): empty(); } - @Override String createSelfLink() { return resourceLinks.userCollection().self(); } - - @Override - boolean isCreatePermitted() { - return UserPermissions.create().isPermitted(); - } }