Feature Partial Diff (#1581)

With this pull request, diffs for Git are loaded in chunks. This means, that for diffs with a lot of files only a part of them are loaded. In the UI a button will be displayed to load more. In the REST API, the number of files can be specified. This only works for diffs, that are delivered as "parsed" diffs. Currently, this is only available for Git.

Co-authored-by: Sebastian Sdorra <sebastian.sdorra@cloudogu.com>
This commit is contained in:
René Pfeuffer
2021-03-12 13:52:17 +01:00
committed by GitHub
parent e66553705f
commit 84c8e02bf1
37 changed files with 900 additions and 223 deletions

View File

@@ -42,6 +42,7 @@ public class DiffResultDto extends HalRepresentation {
}
private List<FileDto> files;
private boolean partial;
@Data
@EqualsAndHashCode(callSuper = false)

View File

@@ -39,6 +39,7 @@ import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Link.linkBuilder;
import static de.otto.edison.hal.Links.linkingTo;
@@ -55,23 +56,57 @@ class DiffResultToDiffResultDtoMapper {
}
public DiffResultDto mapForIncoming(Repository repository, DiffResult result, String source, String target) {
DiffResultDto dto = new DiffResultDto(linkingTo().self(resourceLinks.incoming().diffParsed(repository.getNamespace(), repository.getName(), source, target)).build());
String baseLink = resourceLinks.incoming().diffParsed(repository.getNamespace(), repository.getName(), source, target);
Links.Builder links = linkingTo().self(createSelfLink(result, baseLink));
appendNextChunkLinkIfNeeded(links, result, baseLink);
DiffResultDto dto = new DiffResultDto(links.build());
setFiles(result, dto, repository, source);
return dto;
}
public DiffResultDto mapForRevision(Repository repository, DiffResult result, String revision) {
DiffResultDto dto = new DiffResultDto(linkingTo().self(resourceLinks.diff().parsed(repository.getNamespace(), repository.getName(), revision)).build());
String baseLink = resourceLinks.diff().parsed(repository.getNamespace(), repository.getName(), revision);
Links.Builder links = linkingTo().self(createSelfLink(result, baseLink));
appendNextChunkLinkIfNeeded(links, result, baseLink);
DiffResultDto dto = new DiffResultDto(links.build());
setFiles(result, dto, repository, revision);
return dto;
}
private String createSelfLink(DiffResult result, String baseLink) {
if (result.getOffset() > 0 || result.getLimit().isPresent()) {
return createLinkWithLimitAndOffset(baseLink, result.getOffset(), result.getLimit().orElse(null));
} else {
return baseLink;
}
}
private void appendNextChunkLinkIfNeeded(Links.Builder links, DiffResult result, String baseLink) {
if (result.isPartial()) {
Optional<Integer> limit = result.getLimit();
if (limit.isPresent()) {
links.single(link("next", createLinkWithLimitAndOffset(baseLink, result.getOffset() + limit.get(), limit.get())));
} else {
throw new IllegalStateException("a result cannot be partial without a limit");
}
}
}
private String createLinkWithLimitAndOffset(String baseLink, int offset, Integer limit) {
if (limit == null) {
return String.format("%s?offset=%s", baseLink, offset);
} else {
return String.format("%s?offset=%s&limit=%s", baseLink, offset, limit);
}
}
private void setFiles(DiffResult result, DiffResultDto dto, Repository repository, String revision) {
List<DiffResultDto.FileDto> files = new ArrayList<>();
for (DiffFile file : result) {
files.add(mapFile(file, repository, revision));
}
dto.setFiles(files);
dto.setPartial(result.isPartial());
}
private DiffResultDto.FileDto mapFile(DiffFile file, Repository repository, String revision) {
@@ -119,7 +154,6 @@ class DiffResultToDiffResultDtoMapper {
dto.setOldPath(oldPath);
dto.setOldRevision(file.getOldRevision());
Optional<Language> language = ContentTypeResolver.resolve(path).getLanguage();
language.ifPresent(value -> dto.setLanguage(ProgrammingLanguages.getValue(value)));

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.api.v2.resources;
import io.swagger.v3.oas.annotations.Operation;
@@ -39,6 +39,7 @@ import sonia.scm.util.HttpUtil;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.validation.constraints.Min;
import javax.validation.constraints.Pattern;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
@@ -142,10 +143,18 @@ public class DiffRootResource {
schema = @Schema(implementation = ErrorDto.class)
)
)
public DiffResultDto getParsed(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision) throws IOException {
public DiffResultDto getParsed(@PathParam("namespace") String namespace,
@PathParam("name") String name,
@PathParam("revision") String revision,
@QueryParam("limit") @Min(1) Integer limit,
@QueryParam("offset") @Min(0) Integer offset) throws IOException {
HttpUtil.checkForCRLFInjection(revision);
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
DiffResult diffResult = repositoryService.getDiffResultCommand().setRevision(revision).getDiffResult();
DiffResult diffResult = repositoryService.getDiffResultCommand()
.setRevision(revision)
.setLimit(limit)
.setOffset(offset)
.getDiffResult();
return parsedDiffMapper.mapForRevision(repositoryService.getRepository(), diffResult, revision);
}
}

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.api.v2.resources;
import com.google.inject.Inject;
@@ -43,6 +43,7 @@ import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.util.HttpUtil;
import sonia.scm.web.VndMediaType;
import javax.validation.constraints.Min;
import javax.validation.constraints.Pattern;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
@@ -240,15 +241,19 @@ public class IncomingRootResource {
schema = @Schema(implementation = ErrorDto.class)
))
public Response incomingDiffParsed(@PathParam("namespace") String namespace,
@PathParam("name") String name,
@PathParam("source") String source,
@PathParam("target") String target) throws IOException {
@PathParam("name") String name,
@PathParam("source") String source,
@PathParam("target") String target,
@QueryParam("limit") @Min(1) Integer limit,
@QueryParam("offset") @Min(0) Integer offset) throws IOException {
HttpUtil.checkForCRLFInjection(source);
HttpUtil.checkForCRLFInjection(target);
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
DiffResult diffResult = repositoryService.getDiffResultCommand()
.setRevision(source)
.setAncestorChangeset(target)
.setLimit(limit)
.setOffset(offset)
.getDiffResult();
return Response.ok(parsedDiffMapper.mapForIncoming(repositoryService.getRepository(), diffResult, source, target)).build();
}