added diff endpoint which returns a parsed diff as json

This commit is contained in:
Sebastian Sdorra
2020-01-22 15:49:50 +01:00
parent fd15c68ca0
commit fe8e4db10b
10 changed files with 424 additions and 16 deletions

View File

@@ -53,6 +53,12 @@ public abstract class DefaultChangesetToChangesetDtoMapper extends HalAppenderMa
Embedded.Builder embeddedBuilder = embeddedBuilder();
Links.Builder linksBuilder = linkingTo()
.self(resourceLinks.changeset().self(repository.getNamespace(), repository.getName(), source.getId()))
.single(link("diff", resourceLinks.diff().self(namespace, name, source.getId())))
.single(link("sources", resourceLinks.source().self(namespace, name, source.getId())))
.single(link("modifications", resourceLinks.modifications().self(namespace, name, source.getId())));
try (RepositoryService repositoryService = serviceFactory.create(repository)) {
if (repositoryService.isSupported(Command.TAGS)) {
embeddedBuilder.with("tags", tagCollectionToDtoMapper.getTagDtoList(namespace, name,
@@ -62,16 +68,13 @@ public abstract class DefaultChangesetToChangesetDtoMapper extends HalAppenderMa
embeddedBuilder.with("branches", branchCollectionToDtoMapper.getBranchDtoList(namespace, name,
getListOfObjects(source.getBranches(), branchName -> Branch.normalBranch(branchName, source.getId()))));
}
if (repositoryService.isSupported(Command.DIFF_RESULT)) {
linksBuilder.single(link("diffParsed", resourceLinks.diff().parsed(namespace, name, source.getId())));
}
}
embeddedBuilder.with("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(), source.getId()))
.single(link("diff", resourceLinks.diff().self(namespace, name, source.getId())))
.single(link("sources", resourceLinks.source().self(namespace, name, source.getId())))
.single(link("modifications", resourceLinks.modifications().self(namespace, name, source.getId())));
applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), source, repository);
return new ChangesetDto(linksBuilder.build(), embeddedBuilder.build());

View File

@@ -0,0 +1,64 @@
package sonia.scm.api.v2.resources;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import de.otto.edison.hal.HalRepresentation;
import lombok.Data;
import java.util.List;
@Data
public class DiffResultDto extends HalRepresentation {
private List<FileDto> files;
@Data
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
public static class FileDto {
private String oldPath;
private String newPath;
private boolean oldEndingNewLine;
private boolean newEndingNewLine;
private String oldRevision;
private String newRevision;
private String newMode;
private String oldMode;
private String type;
private String language;
private List<HunkDto> hunks;
}
@Data
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
public static class HunkDto {
private String content;
private int oldStart;
private int newStart;
private int oldLines;
private int newLines;
private List<ChangeDto> changes;
}
@Data
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
public static class ChangeDto {
private String content;
private String type;
@JsonProperty("isNormal")
private boolean isNormal;
@JsonProperty("isInsert")
private boolean isInsert;
@JsonProperty("isDelete")
private boolean isDelete;
private int lineNumber;
private int oldLineNumber;
private int newLineNumber;
}
}

View File

@@ -0,0 +1,132 @@
package sonia.scm.api.v2.resources;
import com.github.sdorra.spotter.ContentTypes;
import com.github.sdorra.spotter.Language;
import com.google.common.base.Strings;
import sonia.scm.repository.api.DiffFile;
import sonia.scm.repository.api.DiffLine;
import sonia.scm.repository.api.DiffResult;
import sonia.scm.repository.api.Hunk;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
/**
* TODO conflicts, copy and rename
*/
final class DiffResultToDiffResultDtoMapper {
static final DiffResultToDiffResultDtoMapper INSTANCE = new DiffResultToDiffResultDtoMapper();
private DiffResultToDiffResultDtoMapper() {
}
public DiffResultDto map(DiffResult result) {
List<DiffResultDto.FileDto> files = new ArrayList<>();
for (DiffFile file : result) {
files.add(mapFile(file));
}
DiffResultDto dto = new DiffResultDto();
dto.setFiles(files);
return dto;
}
private DiffResultDto.FileDto mapFile(DiffFile file) {
DiffResultDto.FileDto dto = new DiffResultDto.FileDto();
// ???
dto.setOldEndingNewLine(true);
dto.setNewEndingNewLine(true);
String newPath = file.getNewPath();
String oldPath = file.getOldPath();
String path;
if (isFilePath(newPath) && isFileNull(oldPath)) {
path = newPath;
dto.setType("add");
} else if (isFileNull(newPath) && isFilePath(oldPath)) {
path = oldPath;
dto.setType("delete");
} else if (isFilePath(newPath) && isFilePath(oldPath)) {
path = newPath;
dto.setType("modify");
} else {
// TODO copy and rename?
throw new IllegalStateException("no file without path");
}
dto.setNewPath(newPath);
dto.setNewRevision(file.getNewRevision());
dto.setOldPath(oldPath);
dto.setOldRevision(file.getOldRevision());
Optional<Language> language = ContentTypes.detect(path).getLanguage();
language.ifPresent(value -> dto.setLanguage(value.getName()));
List<DiffResultDto.HunkDto> hunks = new ArrayList<>();
for (Hunk hunk : file) {
hunks.add(mapHunk(hunk));
}
dto.setHunks(hunks);
return dto;
}
private boolean isFilePath(String path) {
return !isFileNull(path);
}
private boolean isFileNull(String path) {
return Strings.isNullOrEmpty(path) || "/dev/null".equals(path);
}
private DiffResultDto.HunkDto mapHunk(Hunk hunk) {
DiffResultDto.HunkDto dto = new DiffResultDto.HunkDto();
dto.setContent(hunk.getRawHeader());
dto.setNewStart(hunk.getNewStart());
dto.setNewLines(hunk.getNewLineCount());
dto.setOldStart(hunk.getOldStart());
dto.setOldLines(hunk.getOldLineCount());
List<DiffResultDto.ChangeDto> changes = new ArrayList<>();
for (DiffLine line : hunk) {
changes.add(mapLine(line));
}
dto.setChanges(changes);
return dto;
}
private DiffResultDto.ChangeDto mapLine(DiffLine line) {
DiffResultDto.ChangeDto dto = new DiffResultDto.ChangeDto();
dto.setContent(line.getContent());
OptionalInt newLineNumber = line.getNewLineNumber();
OptionalInt oldLineNumber = line.getOldLineNumber();
if (newLineNumber.isPresent() && !oldLineNumber.isPresent()) {
dto.setType("insert");
dto.setInsert(true);
dto.setLineNumber(newLineNumber.getAsInt());
} else if (!newLineNumber.isPresent() && oldLineNumber.isPresent()) {
dto.setType("delete");
dto.setDelete(true);
dto.setLineNumber(oldLineNumber.getAsInt());
} else if (newLineNumber.isPresent() && oldLineNumber.isPresent()) {
dto.setType("normal");
dto.setNormal(true);
dto.setNewLineNumber(newLineNumber.getAsInt());
dto.setOldLineNumber(oldLineNumber.getAsInt());
} else {
throw new IllegalStateException("line without line number");
}
return dto;
}
}

View File

@@ -6,6 +6,7 @@ import sonia.scm.NotFoundException;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.api.DiffCommandBuilder;
import sonia.scm.repository.api.DiffFormat;
import sonia.scm.repository.api.DiffResult;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.util.HttpUtil;
@@ -70,4 +71,23 @@ public class DiffRootResource {
.build();
}
}
@GET
@Path("{revision}.json")
@Produces(VndMediaType.DIFF_PARSED)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 400, condition = "Bad Request"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the diff"),
@ResponseCode(code = 404, condition = "not found, no revision with the specified param for the repository available or repository not found"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response getParsed(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision) throws IOException {
HttpUtil.checkForCRLFInjection(revision);
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
DiffResult diffResult = repositoryService.getDiffResultCommand().setRevision(revision).getDiffResult();
return Response.ok(DiffResultToDiffResultDtoMapper.INSTANCE.map(diffResult)).build();
}
}
}

View File

@@ -362,6 +362,10 @@ class ResourceLinks {
return diffLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("diff").parameters().method("get").parameters(id).href();
}
String parsed(String namespace, String name, String id) {
return diffLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("diff").parameters().method("getParsed").parameters(id).href();
}
String all(String namespace, String name) {
return diffLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("diff").parameters().method("getAll").parameters().href();
}