Merged in feature/ui_diff (pull request #94)

Feature/ui diff
This commit is contained in:
René Pfeuffer
2018-11-01 11:10:15 +00:00
37 changed files with 6980 additions and 286 deletions

View File

@@ -0,0 +1,265 @@
package sonia.scm.it;
import org.apache.http.HttpStatus;
import org.assertj.core.util.Lists;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import sonia.scm.it.utils.RepositoryUtil;
import sonia.scm.it.utils.ScmRequests;
import sonia.scm.it.utils.TestData;
import sonia.scm.repository.Changeset;
import sonia.scm.repository.client.api.RepositoryClient;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static sonia.scm.it.utils.RestUtil.ADMIN_PASSWORD;
import static sonia.scm.it.utils.RestUtil.ADMIN_USERNAME;
public class DiffITCase {
@Rule
public TemporaryFolder tempFolder = new TemporaryFolder();
private RepositoryClient svnRepositoryClient;
private RepositoryClient gitRepositoryClient;
private RepositoryClient hgRepositoryClient;
private ScmRequests.RepositoryResponse<ScmRequests.IndexResponse> svnRepositoryResponse;
private ScmRequests.RepositoryResponse<ScmRequests.IndexResponse> hgRepositoryResponse;
private ScmRequests.RepositoryResponse<ScmRequests.IndexResponse> gitRepositoryResponse;
private File svnFolder;
private File gitFolder;
private File hgFolder;
@Before
public void init() throws IOException {
TestData.createDefault();
String namespace = ADMIN_USERNAME;
String repo = TestData.getDefaultRepoName("svn");
svnRepositoryResponse =
ScmRequests.start()
.requestIndexResource(ADMIN_USERNAME, ADMIN_PASSWORD)
.requestRepository(namespace, repo)
.assertStatusCode(HttpStatus.SC_OK);
svnFolder = tempFolder.newFolder("svn");
svnRepositoryClient = RepositoryUtil.createRepositoryClient("svn", svnFolder);
repo = TestData.getDefaultRepoName("git");
gitRepositoryResponse =
ScmRequests.start()
.requestIndexResource(ADMIN_USERNAME, ADMIN_PASSWORD)
.requestRepository(namespace, repo)
.assertStatusCode(HttpStatus.SC_OK);
gitFolder = tempFolder.newFolder("git");
gitRepositoryClient = RepositoryUtil.createRepositoryClient("git", gitFolder);
repo = TestData.getDefaultRepoName("hg");
hgRepositoryResponse =
ScmRequests.start()
.requestIndexResource(ADMIN_USERNAME, ADMIN_PASSWORD)
.requestRepository(namespace, repo)
.assertStatusCode(HttpStatus.SC_OK);
hgFolder = tempFolder.newFolder("hg");
hgRepositoryClient = RepositoryUtil.createRepositoryClient("hg", hgFolder);
}
@Test
public void shouldFindDiffsInGitFormat() throws IOException {
String svnDiff = getDiff(RepositoryUtil.createAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, "a.txt", "content of a"), svnRepositoryResponse);
String gitDiff = getDiff(RepositoryUtil.createAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, "a.txt", "content of a"), gitRepositoryResponse);
String hgDiff = getDiff(RepositoryUtil.createAndCommitFile(hgRepositoryClient, ADMIN_USERNAME, "a.txt", "content of a"), hgRepositoryResponse);
assertThat(Lists.newArrayList(svnDiff, gitDiff, hgDiff))
.allSatisfy(diff -> assertThat(diff)
.contains("diff --git "));
}
@Test
public void svnAddFileDiffShouldBeConvertedToGitDiff() throws IOException {
String svnDiff = getDiff(RepositoryUtil.createAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, "a.txt", "content of a"), svnRepositoryResponse);
String gitDiff = getDiff(RepositoryUtil.createAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, "a.txt", "content of a"), gitRepositoryResponse);
String expected = getGitDiffWithoutIndexLine(gitDiff);
assertThat(svnDiff)
.isEqualTo(expected);
}
@Test
public void svnDeleteFileDiffShouldBeConvertedToGitDiff() throws IOException {
RepositoryUtil.createAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, "a.txt", "content of a");
RepositoryUtil.createAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, "a.txt", "content of a");
String svnDiff = getDiff(RepositoryUtil.removeAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, "a.txt"), svnRepositoryResponse);
String gitDiff = getDiff(RepositoryUtil.removeAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, "a.txt"), gitRepositoryResponse);
String expected = getGitDiffWithoutIndexLine(gitDiff);
assertThat(svnDiff)
.isEqualTo(expected);
}
@Test
public void svnUpdateFileDiffShouldBeConvertedToGitDiff() throws IOException {
RepositoryUtil.createAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, "a.txt", "content of a");
RepositoryUtil.createAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, "a.txt", "content of a");
String svnDiff = getDiff(RepositoryUtil.updateAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, "a.txt", "the updated content of a"), svnRepositoryResponse);
String gitDiff = getDiff(RepositoryUtil.updateAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, "a.txt", "the updated content of a"), gitRepositoryResponse);
String expected = getGitDiffWithoutIndexLine(gitDiff);
assertThat(svnDiff)
.isEqualTo(expected);
}
@Test
public void svnMultipleChangesDiffShouldBeConvertedToGitDiff() throws IOException {
String svnDiff = getDiff(applyMultipleChanges(svnRepositoryClient, "fileToBeDeleted.txt", "fileToBeUpdated.txt", "addedFile.txt"), svnRepositoryResponse);
String gitDiff = getDiff(applyMultipleChanges(gitRepositoryClient, "fileToBeDeleted.txt", "fileToBeUpdated.txt", "addedFile.txt"), gitRepositoryResponse);
String endOfDiffPart = "\\ No newline at end of file\n";
String[] gitDiffs = gitDiff.split(endOfDiffPart);
List<String> expected = Arrays.stream(gitDiffs)
.map(this::getGitDiffWithoutIndexLine)
.collect(Collectors.toList());
assertThat(svnDiff.split(endOfDiffPart))
.containsExactlyInAnyOrderElementsOf(expected);
}
@Test
public void svnMultipleSubFolderChangesDiffShouldBeConvertedToGitDiff() throws IOException {
String svnDiff = getDiff(applyMultipleChanges(svnRepositoryClient, "a/b/fileToBeDeleted.txt", "a/c/fileToBeUpdated.txt", "a/d/addedFile.txt"), svnRepositoryResponse);
String gitDiff = getDiff(applyMultipleChanges(gitRepositoryClient, "a/b/fileToBeDeleted.txt", "a/c/fileToBeUpdated.txt", "a/d/addedFile.txt"), gitRepositoryResponse);
String endOfDiffPart = "\\ No newline at end of file\n";
String[] gitDiffs = gitDiff.split(endOfDiffPart);
List<String> expected = Arrays.stream(gitDiffs)
.map(this::getGitDiffWithoutIndexLine)
.collect(Collectors.toList());
assertThat(svnDiff.split(endOfDiffPart))
.containsExactlyInAnyOrderElementsOf(expected);
}
@Test
public void svnLargeChangesDiffShouldBeConvertedToGitDiff() throws IOException, URISyntaxException {
String fileName = "SvnDiffGenerator_forTest";
RepositoryUtil.createAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, fileName, "");
RepositoryUtil.createAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, fileName, "");
String fileContent = getFileContent("/diff/largefile/original/SvnDiffGenerator_forTest.java");
String svnDiff = getDiff(RepositoryUtil.updateAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, fileName, fileContent), svnRepositoryResponse);
String gitDiff = getDiff(RepositoryUtil.updateAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, fileName, fileContent), gitRepositoryResponse);
assertThat(svnDiff)
.isEqualTo(getGitDiffWithoutIndexLine(gitDiff));
fileContent = getFileContent("/diff/largefile/modified/v1/SvnDiffGenerator_forTest");
svnDiff = getDiff(RepositoryUtil.updateAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, fileName, fileContent), svnRepositoryResponse);
gitDiff = getDiff(RepositoryUtil.updateAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, fileName, fileContent), gitRepositoryResponse);
assertThat(svnDiff)
.isEqualTo(getGitDiffWithoutIndexLine(gitDiff));
fileContent = getFileContent("/diff/largefile/modified/v2/SvnDiffGenerator_forTest");
svnDiff = getDiff(RepositoryUtil.updateAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, fileName, fileContent), svnRepositoryResponse);
gitDiff = getDiff(RepositoryUtil.updateAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, fileName, fileContent), gitRepositoryResponse);
assertThat(svnDiff)
.isEqualTo(getGitDiffWithoutIndexLine(gitDiff));
}
/**
* FIXME: the binary Git Diff output is not GIT conform
*/
@Test
@Ignore
@SuppressWarnings("squid:S1607")
public void svnBinaryChangesDiffShouldBeConvertedToGitDiff() throws IOException, URISyntaxException {
String fileName = "binary";
File file = new File(svnRepositoryClient.getWorkingCopy(), fileName);
Files.copy(Paths.get(getClass().getResource("/diff/binaryfile/echo").toURI()), Paths.get(file.toURI()));
Changeset commit = RepositoryUtil.addFileAndCommit(svnRepositoryClient, fileName, ADMIN_USERNAME, "");
file = new File(gitRepositoryClient.getWorkingCopy(), fileName);
Files.copy(Paths.get(getClass().getResource("/diff/binaryfile/echo").toURI()), Paths.get(file.toURI()));
Changeset commit1 = RepositoryUtil.addFileAndCommit(gitRepositoryClient, fileName, ADMIN_USERNAME, "");
String svnDiff = getDiff(commit, svnRepositoryResponse);
String gitDiff = getDiff(commit1, gitRepositoryResponse);
assertThat(svnDiff)
.isEqualTo(getGitDiffWithoutIndexLine(gitDiff));
}
@Test
public void svnRenameChangesDiffShouldBeConvertedToGitDiff() throws IOException, URISyntaxException {
String fileName = "a.txt";
RepositoryUtil.createAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, fileName, "content of a");
RepositoryUtil.createAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, fileName, "content of a");
String newFileName = "renamed_a.txt";
File file = new File(svnRepositoryClient.getWorkingCopy(), fileName);
file.renameTo(new File(svnRepositoryClient.getWorkingCopy(), newFileName));
String svnDiff = getDiff(RepositoryUtil.addFileAndCommit(svnRepositoryClient, newFileName, ADMIN_USERNAME, "renamed file"), svnRepositoryResponse);
file = new File(gitRepositoryClient.getWorkingCopy(), fileName);
file.renameTo(new File(gitRepositoryClient.getWorkingCopy(), newFileName));
String gitDiff = getDiff(RepositoryUtil.addFileAndCommit(gitRepositoryClient, newFileName, ADMIN_USERNAME, "renamed file"), gitRepositoryResponse);
String expected = getGitDiffWithoutIndexLine(gitDiff);
assertThat(svnDiff)
.isEqualTo(expected);
}
public String getFileContent(String name) throws URISyntaxException, IOException {
Path path;
path = Paths.get(getClass().getResource(name).toURI());
Stream<String> lines = Files.lines(path);
String data = lines.collect(Collectors.joining("\n"));
lines.close();
return data;
}
/**
* The index line is not provided from the svn git formatter and it is not needed in the ui diff view
* for more details about the git diff format: https://git-scm.com/docs/git-diff
*
* @param gitDiff
* @return diff without the index line
*/
private String getGitDiffWithoutIndexLine(String gitDiff) {
return gitDiff.replaceAll(".*(index.*\n)", "");
}
private String getDiff(Changeset svnChangeset, ScmRequests.RepositoryResponse<ScmRequests.IndexResponse> svnRepositoryResponse) {
return svnRepositoryResponse.requestChangesets()
.requestDiffInGitFormat(svnChangeset.getId())
.getResponse()
.body()
.asString();
}
private Changeset applyMultipleChanges(RepositoryClient repositoryClient, String fileToBeDeleted, final String fileToBeUpdated, final String addedFile) throws IOException {
RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, fileToBeDeleted, "file to be deleted");
RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, fileToBeUpdated, "file to be updated");
Map<String, String> addedFiles = new HashMap<String, String>() {{
put(addedFile, "content");
}};
Map<String, String> modifiedFiles = new HashMap<String, String>() {{
put(fileToBeUpdated, "the updated content");
}};
ArrayList<String> removedFiles = Lists.newArrayList(fileToBeDeleted);
return RepositoryUtil.commitMultipleFileModifications(repositoryClient, ADMIN_USERNAME, addedFiles, modifiedFiles, removedFiles);
}
}

View File

@@ -1,5 +1,6 @@
package sonia.scm.it;
import groovy.util.logging.Slf4j;
import io.restassured.response.ExtractableResponse;
import io.restassured.response.Response;
import org.apache.http.HttpStatus;
@@ -37,6 +38,7 @@ import static sonia.scm.it.utils.RestUtil.given;
import static sonia.scm.it.utils.ScmTypes.availableScmTypes;
@RunWith(Parameterized.class)
@Slf4j
public class RepositoryAccessITCase {
@Rule
@@ -63,9 +65,9 @@ public class RepositoryAccessITCase {
String repo = TestData.getDefaultRepoName(repositoryType);
repositoryResponse =
ScmRequests.start()
.requestIndexResource(ADMIN_USERNAME, ADMIN_PASSWORD)
.requestRepository(namespace, repo)
.assertStatusCode(HttpStatus.SC_OK);
.requestIndexResource(ADMIN_USERNAME, ADMIN_PASSWORD)
.requestRepository(namespace, repo)
.assertStatusCode(HttpStatus.SC_OK);
}
@Test
@@ -175,6 +177,7 @@ public class RepositoryAccessITCase {
}
@Test
@SuppressWarnings("squid:S2925")
public void shouldReadContent() throws IOException, InterruptedException {
RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder);
RepositoryUtil.createAndCommitFile(repositoryClient, "scmadmin", "a.txt", "a");
@@ -262,40 +265,6 @@ public class RepositoryAccessITCase {
assertThat(changesets).size().isBetween(2, 3); // svn has an implicit root revision '0' that is extra to the two commits
}
@Test
public void shouldFindDiffs() 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");
String diffUrl = given()
.when()
.get(changesetsUrl)
.then()
.statusCode(HttpStatus.SC_OK)
.extract()
.path("_embedded.changesets[0]._links.diff.href");
given()
.when()
.get(diffUrl)
.then()
.statusCode(HttpStatus.SC_OK)
.extract()
.body()
.asString()
.contains("diff");
}
@Test
@SuppressWarnings("unchecked")
@@ -393,12 +362,10 @@ public class RepositoryAccessITCase {
RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, "b.txt", "b");
RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, "c.txt", "c");
RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, "d.txt", "d");
Map<String, String> addedFiles = new HashMap<String, String>()
{{
Map<String, String> addedFiles = new HashMap<String, String>() {{
put("a.txt", "bla bla");
}};
Map<String, String> modifiedFiles = new HashMap<String, String>()
{{
Map<String, String> modifiedFiles = new HashMap<String, String>() {{
put("b.txt", "new content");
}};
ArrayList<String> removedFiles = Lists.newArrayList("c.txt", "d.txt");
@@ -414,7 +381,7 @@ public class RepositoryAccessITCase {
.assertAdded(a -> assertThat(a)
.hasSize(1)
.containsExactly("a.txt"))
.assertModified(m-> assertThat(m)
.assertModified(m -> assertThat(m)
.hasSize(1)
.containsExactly("b.txt"))
.assertRemoved(r -> assertThat(r)

View File

@@ -80,6 +80,11 @@ public class RepositoryUtil {
return file;
}
public static Changeset updateAndCommitFile(RepositoryClient repositoryClient, String username, String fileName, String content) throws IOException {
writeAndAddFile(repositoryClient, fileName, content);
return commit(repositoryClient, username, "updated " + fileName);
}
public static Changeset removeAndCommitFile(RepositoryClient repositoryClient, String username, String fileName) throws IOException {
deleteFileAndApplyRemoveCommand(repositoryClient, fileName);
return commit(repositoryClient, username, "removed " + fileName);
@@ -102,11 +107,21 @@ public class RepositoryUtil {
} else {
path = thisName;
}
repositoryClient.getAddCommand().add(path);
addFile(repositoryClient, path);
return path;
}
static Changeset commit(RepositoryClient repositoryClient, String username, String message) throws IOException {
public static Changeset addFileAndCommit(RepositoryClient repositoryClient, String path, String username, String message) throws IOException {
repositoryClient.getAddCommand().add(path);
return commit(repositoryClient, username, message);
}
public static void addFile(RepositoryClient repositoryClient, String path) throws IOException {
repositoryClient.getAddCommand().add(path);
}
public static Changeset commit(RepositoryClient repositoryClient, String username, String message) throws IOException {
LOG.info("user: {} try to commit with message: {}", username, message);
Changeset changeset = repositoryClient.getCommitCommand().commit(new Person(username, username + "@scm-manager.org"), message);
if (repositoryClient.isCommandSupported(ClientCommand.PUSH)) {

View File

@@ -234,8 +234,8 @@ public class ScmRequests {
return this;
}
public DiffResponse<ChangesetsResponse> requestDiff(String revision) {
return new DiffResponse<>(applyGETRequestFromLink(response, "_embedded.changesets.find{it.id=='" + revision + "'}._links.diff.href"), this);
public DiffResponse<ChangesetsResponse> requestDiffInGitFormat(String revision) {
return new DiffResponse<>(applyGETRequestFromLinkWithParams(response, "_embedded.changesets.find{it.id=='" + revision + "'}._links.diff.href", "?format=GIT"), this);
}
public ModificationsResponse<ChangesetsResponse> requestModifications(String revision) {
@@ -362,6 +362,10 @@ public class ScmRequests {
this.previousResponse = previousResponse;
}
public Response getResponse(){
return response;
}
public PREV returnToPrevious() {
return previousResponse;
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +1,19 @@
/**
/*
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* <p>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* <p>
* 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.
* <p>
* 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,13 +24,11 @@
* 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.
*
* <p>
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
@@ -41,14 +39,12 @@ import org.slf4j.LoggerFactory;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.wc.DefaultSVNDiffGenerator;
import org.tmatesoft.svn.core.wc.ISVNDiffGenerator;
import org.tmatesoft.svn.core.internal.wc2.ng.SvnNewDiffGenerator;
import org.tmatesoft.svn.core.wc.SVNClientManager;
import org.tmatesoft.svn.core.wc.SVNDiffClient;
import org.tmatesoft.svn.core.wc.SVNRevision;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RevisionNotFoundException;
import sonia.scm.repository.SvnUtil;
import sonia.scm.repository.api.DiffFormat;
import sonia.scm.util.Util;
@@ -61,8 +57,7 @@ import java.io.OutputStream;
*
* @author Sebastian Sdorra
*/
public class SvnDiffCommand extends AbstractSvnCommand implements DiffCommand
{
public class SvnDiffCommand extends AbstractSvnCommand implements DiffCommand {
/**
* the logger for SvnDiffCommand
@@ -70,46 +65,26 @@ public class SvnDiffCommand extends AbstractSvnCommand implements DiffCommand
private static final Logger logger =
LoggerFactory.getLogger(SvnDiffCommand.class);
public SvnDiffCommand(SvnContext context, Repository repository)
{
public SvnDiffCommand(SvnContext context, Repository repository) {
super(context, repository);
}
@Override
public void getDiffResult(DiffCommandRequest request, OutputStream output) throws RevisionNotFoundException {
if (logger.isDebugEnabled())
{
logger.debug("create diff for {}", request);
}
public void getDiffResult(DiffCommandRequest request, OutputStream output) {
logger.debug("create diff for {}", request);
Preconditions.checkNotNull(request, "request is required");
Preconditions.checkNotNull(output, "outputstream is required");
String path = request.getPath();
SVNClientManager clientManager = null;
try
{
try {
SVNURL svnurl = context.createUrl();
if (Util.isNotEmpty(path))
{
if (Util.isNotEmpty(path)) {
svnurl = svnurl.appendPath(path, true);
}
clientManager = SVNClientManager.newInstance();
SVNDiffClient diffClient = clientManager.getDiffClient();
ISVNDiffGenerator diffGenerator = diffClient.getDiffGenerator();
if (diffGenerator == null)
{
diffGenerator = new DefaultSVNDiffGenerator();
}
diffGenerator.setDiffAdded(true);
diffGenerator.setDiffDeleted(true);
diffClient.setDiffGenerator(diffGenerator);
diffClient.setDiffGenerator(new SvnNewDiffGenerator(new SCMSvnDiffGenerator()));
long currentRev = SvnUtil.getRevisionNumber(request.getRevision());
@@ -118,13 +93,9 @@ public class SvnDiffCommand extends AbstractSvnCommand implements DiffCommand
diffClient.doDiff(svnurl, SVNRevision.HEAD,
SVNRevision.create(currentRev - 1), SVNRevision.create(currentRev),
SVNDepth.INFINITY, false, output);
}
catch (SVNException ex)
{
} catch (SVNException ex) {
throw new InternalRepositoryException("could not create diff", ex);
}
finally
{
} finally {
SvnUtil.dispose(clientManager);
}
}

View File

@@ -1,6 +1,6 @@
//@flow
import React from "react";
import { withContextPath } from "./urls";
import {withContextPath} from "./urls";
type Props = {
src: string,

View File

@@ -1,6 +1,6 @@
//@flow
import * as React from "react";
import { Route, Link } from "react-router-dom";
import {Link, Route} from "react-router-dom";
// TODO mostly copy of PrimaryNavigationLink

View File

@@ -1,5 +1,5 @@
// @flow
import { concat, getPageFromMatch, withEndingSlash } from "./urls";
import {concat, getPageFromMatch, withEndingSlash} from "./urls";
describe("tests for withEndingSlash", () => {

View File

@@ -1,5 +1,5 @@
//@flow
import type { Links } from "./hal";
import type {Links} from "./hal";
export type Permission = PermissionCreateEntry & {
_links: Links

View File

@@ -10,14 +10,18 @@
"bulma": "^0.7.1",
"bulma-tooltip": "^2.0.2",
"classnames": "^2.2.5",
"diff2html": "^2.4.0",
"font-awesome": "^4.7.0",
"history": "^4.7.2",
"i18next": "^11.4.0",
"i18next-browser-languagedetector": "^2.2.2",
"i18next-fetch-backend": "^0.1.0",
"moment": "^2.22.2",
"react": "^16.5.2",
"react-dom": "^16.5.2",
"node-sass": "^4.9.3",
"postcss-easy-import": "^3.0.0",
"react": "^16.4.2",
"react-diff-view": "^1.7.0",
"react-dom": "^16.4.2",
"react-i18next": "^7.9.0",
"react-jss": "^8.6.0",
"react-redux": "^5.0.7",
@@ -54,6 +58,7 @@
"node-sass": "^4.9.3",
"node-sass-chokidar": "^1.3.0",
"npm-run-all": "^4.1.3",
"postcss-easy-import": "^3.0.0",
"prettier": "^1.13.7",
"react-router-enzyme-context": "^1.2.0",
"react-test-renderer": "^16.4.1",

View File

@@ -1,8 +1,8 @@
//@flow
import React from "react";
import { binder } from "@scm-manager/ui-extensions";
import type { Changeset } from "@scm-manager/ui-types";
import { Image } from "@scm-manager/ui-components";
import {binder} from "@scm-manager/ui-extensions";
import type {Changeset} from "@scm-manager/ui-types";
import {Image} from "@scm-manager/ui-components";
type Props = {
changeset: Changeset

View File

@@ -1,6 +1,6 @@
//@flow
import * as React from "react";
import { binder } from "@scm-manager/ui-extensions";
import {binder} from "@scm-manager/ui-extensions";
type Props = {
children: React.Node

View File

@@ -1,20 +1,18 @@
//@flow
import React from "react";
import type {
Changeset,
Repository
} from "../../../../../scm-ui-components/packages/ui-types/src/index";
import type { Changeset, Repository } from "@scm-manager/ui-types";
import { Interpolate, translate } from "react-i18next";
import injectSheet from "react-jss";
import ChangesetTag from "./ChangesetTag";
import ChangesetAuthor from "./ChangesetAuthor";
import { parseDescription } from "./changesets";
import { DateFromNow } from "../../../../../scm-ui-components/packages/ui-components/src/index";
import { DateFromNow } from "@scm-manager/ui-components";
import AvatarWrapper from "./AvatarWrapper";
import AvatarImage from "./AvatarImage";
import classNames from "classnames";
import ChangesetId from "./ChangesetId";
import type { Tag } from "@scm-manager/ui-types";
import ScmDiff from "../../containers/ScmDiff";
const styles = {
spacing: {
@@ -41,38 +39,43 @@ class ChangesetDetails extends React.Component<Props> {
const date = <DateFromNow date={changeset.date} />;
return (
<div className="content">
<h4>{description.title}</h4>
<article className="media">
<AvatarWrapper>
<p className={classNames("image", "is-64x64", classes.spacing)}>
<AvatarImage changeset={changeset} />
</p>
</AvatarWrapper>
<div className="media-content">
<p>
<ChangesetAuthor changeset={changeset} />
</p>
<p>
<Interpolate
i18nKey="changesets.changeset.summary"
id={id}
time={date}
/>
</p>
</div>
<div className="media-right">{this.renderTags()}</div>
</article>
<p>
{description.message.split("\n").map((item, key) => {
return (
<span key={key}>
{item}
<br />
</span>
);
})}
</p>
<div>
<div className="content">
<h4>{description.title}</h4>
<article className="media">
<AvatarWrapper>
<p className={classNames("image", "is-64x64", classes.spacing)}>
<AvatarImage changeset={changeset} />
</p>
</AvatarWrapper>
<div className="media-content">
<p>
<ChangesetAuthor changeset={changeset} />
</p>
<p>
<Interpolate
i18nKey="changesets.changeset.summary"
id={id}
time={date}
/>
</p>
</div>
<div className="media-right">{this.renderTags()}</div>
</article>
<p>
{description.message.split("\n").map((item, key) => {
return (
<span key={key}>
{item}
<br />
</span>
);
})}
</p>
</div>
<div>
<ScmDiff changeset={changeset} sideBySide={false} />
</div>
</div>
);
}

View File

@@ -1,8 +1,8 @@
//@flow
import { Link } from "react-router-dom";
import {Link} from "react-router-dom";
import React from "react";
import type { Repository, Changeset } from "@scm-manager/ui-types";
import type {Changeset, Repository} from "@scm-manager/ui-types";
type Props = {
repository: Repository,

View File

@@ -1,15 +1,15 @@
//@flow
import React from "react";
import type { Changeset, Repository, Tag } from "@scm-manager/ui-types";
import type {Changeset, Repository, Tag} from "@scm-manager/ui-types";
import classNames from "classnames";
import { translate, Interpolate } from "react-i18next";
import {Interpolate, translate} from "react-i18next";
import ChangesetId from "./ChangesetId";
import injectSheet from "react-jss";
import { DateFromNow } from "@scm-manager/ui-components";
import {DateFromNow} from "@scm-manager/ui-components";
import ChangesetAuthor from "./ChangesetAuthor";
import ChangesetTag from "./ChangesetTag";
import { compose } from "redux";
import { parseDescription } from "./changesets";
import {compose} from "redux";
import {parseDescription} from "./changesets";
import AvatarWrapper from "./AvatarWrapper";
import AvatarImage from "./AvatarImage";

View File

@@ -1,6 +1,6 @@
// @flow
import { parseDescription } from "./changesets";
import {parseDescription} from "./changesets";
describe("parseDescription tests", () => {
it("should return a description with title and message", () => {

View File

@@ -11,7 +11,7 @@ import {
} from "../modules/changesets";
import ChangesetDetails from "../components/changesets/ChangesetDetails";
import { translate } from "react-i18next";
import { Loading, ErrorPage } from "@scm-manager/ui-components";
import { ErrorPage, Loading } from "@scm-manager/ui-components";
type Props = {
id: string,

View File

@@ -1,13 +1,8 @@
// @flow
import React from "react";
import { withRouter } from "react-router-dom";
import type {
Branch,
Changeset,
PagedCollection,
Repository
} from "@scm-manager/ui-types";
import {withRouter} from "react-router-dom";
import type {Branch, Changeset, PagedCollection, Repository} from "@scm-manager/ui-types";
import {
fetchChangesets,
getChangesets,
@@ -16,15 +11,10 @@ import {
selectListAsCollection
} from "../modules/changesets";
import { connect } from "react-redux";
import {connect} from "react-redux";
import ChangesetList from "../components/changesets/ChangesetList";
import {
ErrorNotification,
LinkPaginator,
Loading,
getPageFromMatch
} from "@scm-manager/ui-components";
import { compose } from "redux";
import {ErrorNotification, getPageFromMatch, LinkPaginator, Loading} from "@scm-manager/ui-components";
import {compose} from "redux";
type Props = {
repository: Repository,

View File

@@ -1,32 +1,19 @@
//@flow
import React from "react";
import {
deleteRepo,
fetchRepo,
getFetchRepoFailure,
getRepository,
isFetchRepoPending
} from "../modules/repos";
import {deleteRepo, fetchRepo, getFetchRepoFailure, getRepository, isFetchRepoPending} from "../modules/repos";
import { connect } from "react-redux";
import { Route, Switch } from "react-router-dom";
import type { Repository } from "@scm-manager/ui-types";
import {connect} from "react-redux";
import {Route, Switch} from "react-router-dom";
import type {Repository} from "@scm-manager/ui-types";
import {
ErrorPage,
Loading,
Navigation,
NavLink,
Page,
Section
} from "@scm-manager/ui-components";
import { translate } from "react-i18next";
import {ErrorPage, Loading, Navigation, NavLink, Page, Section} from "@scm-manager/ui-components";
import {translate} from "react-i18next";
import RepositoryDetails from "../components/RepositoryDetails";
import DeleteNavAction from "../components/DeleteNavAction";
import Edit from "../containers/Edit";
import Permissions from "../permissions/containers/Permissions";
import type { History } from "history";
import type {History} from "history";
import EditNavLink from "../components/EditNavLink";
import BranchRoot from "./ChangesetsRoot";
@@ -80,11 +67,6 @@ class RepositoryRoot extends React.Component<Props> {
this.props.deleteRepo(repository, this.deleted);
};
matchChangeset = (route: any) => {
const url = this.matchedUrl();
return route.location.pathname.match(`${url}/changeset/`);
};
matches = (route: any) => {
const url = this.matchedUrl();
const regex = new RegExp(`${url}(/branches)?/?[^/]*/changesets?.*`);
@@ -125,7 +107,7 @@ class RepositoryRoot extends React.Component<Props> {
/>
<Route
path={`${url}/permissions`}
render={props => (
render={() => (
<Permissions
namespace={this.props.repository.namespace}
repoName={this.props.repository.name}

View File

@@ -0,0 +1,51 @@
// @flow
import React from "react";
import { apiClient } from "@scm-manager/ui-components";
import type { Changeset } from "@scm-manager/ui-types";
import { Diff2Html } from "diff2html";
type Props = {
changeset: Changeset,
sideBySide: boolean
};
type State = {
diff: string,
error?: Error
};
class ScmDiff extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { diff: "" };
}
componentDidMount() {
const { changeset } = this.props;
const url = changeset._links.diff.href+"?format=GIT";
apiClient
.get(url)
.then(response => response.text())
.then(text => this.setState({ ...this.state, diff: text }))
.catch(error => this.setState({ ...this.state, error }));
}
render() {
const options = {
inputFormat: "diff",
outputFormat: this.props.sideBySide ? "side-by-side" : "line-by-line",
showFiles: false,
matching: "lines"
};
const outputHtml = Diff2Html.getPrettyHtml(this.state.diff, options);
return (
// eslint-disable-next-line react/no-danger
<div dangerouslySetInnerHTML={{ __html: outputHtml }} />
);
}
}
export default ScmDiff;

View File

@@ -1,19 +1,10 @@
// @flow
import {
FAILURE_SUFFIX,
PENDING_SUFFIX,
SUCCESS_SUFFIX
} from "../../modules/types";
import { apiClient, urls } from "@scm-manager/ui-components";
import { isPending } from "../../modules/pending";
import { getFailure } from "../../modules/failure";
import type {
Action,
Branch,
PagedCollection,
Repository
} from "@scm-manager/ui-types";
import {FAILURE_SUFFIX, PENDING_SUFFIX, SUCCESS_SUFFIX} from "../../modules/types";
import {apiClient, urls} from "@scm-manager/ui-components";
import {isPending} from "../../modules/pending";
import {getFailure} from "../../modules/failure";
import type {Action, Branch, PagedCollection, Repository} from "@scm-manager/ui-types";
export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS";
export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`;

View File

@@ -4,27 +4,27 @@ import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";
import fetchMock from "fetch-mock";
import reducer, {
FETCH_CHANGESETS,
FETCH_CHANGESETS_FAILURE,
FETCH_CHANGESETS_PENDING,
FETCH_CHANGESETS_SUCCESS,
FETCH_CHANGESET,
FETCH_CHANGESET_FAILURE,
FETCH_CHANGESET_PENDING,
FETCH_CHANGESET_SUCCESS,
FETCH_CHANGESETS,
FETCH_CHANGESETS_FAILURE,
FETCH_CHANGESETS_PENDING,
FETCH_CHANGESETS_SUCCESS,
fetchChangeset,
fetchChangesetIfNeeded,
fetchChangesets,
fetchChangesetsSuccess,
getChangesets,
getFetchChangesetsFailure,
isFetchChangesetsPending,
fetchChangeset,
getChangeset,
fetchChangesetIfNeeded,
shouldFetchChangeset,
isFetchChangesetPending,
getFetchChangesetFailure,
fetchChangesetSuccess,
selectListAsCollection
getChangeset,
getChangesets,
getFetchChangesetFailure,
getFetchChangesetsFailure,
isFetchChangesetPending,
isFetchChangesetsPending,
selectListAsCollection,
shouldFetchChangeset
} from "./changesets";
const branch = {

View File

@@ -1,12 +1,9 @@
// @flow
import React from "react";
import { translate } from "react-i18next";
import { Checkbox, InputField, SubmitButton } from "@scm-manager/ui-components";
import {translate} from "react-i18next";
import {Checkbox, InputField, SubmitButton} from "@scm-manager/ui-components";
import TypeSelector from "./TypeSelector";
import type {
PermissionCollection,
PermissionCreateEntry
} from "@scm-manager/ui-types";
import type {PermissionCollection, PermissionCreateEntry} from "@scm-manager/ui-types";
import * as validator from "./permissionValidation";
type Props = {

View File

@@ -1,6 +1,7 @@
// @flow
import { validation } from "@scm-manager/ui-components";
import type { PermissionCollection } from "@scm-manager/ui-types";
import {validation} from "@scm-manager/ui-components";
import type {PermissionCollection} from "@scm-manager/ui-types";
const isNameValid = validation.isNameValid;
export { isNameValid };

View File

@@ -1,16 +1,12 @@
// @flow
import { apiClient } from "@scm-manager/ui-components";
import type {Action} from "@scm-manager/ui-components";
import {apiClient} from "@scm-manager/ui-components";
import * as types from "../../../modules/types";
import type { Action } from "@scm-manager/ui-components";
import type {
PermissionCollection,
Permission,
PermissionCreateEntry
} from "@scm-manager/ui-types";
import { isPending } from "../../../modules/pending";
import { getFailure } from "../../../modules/failure";
import { Dispatch } from "redux";
import type {Permission, PermissionCollection, PermissionCreateEntry} from "@scm-manager/ui-types";
import {isPending} from "../../../modules/pending";
import {getFailure} from "../../../modules/failure";
import {Dispatch} from "redux";
export const FETCH_PERMISSIONS = "scm/permissions/FETCH_PERMISSIONS";
export const FETCH_PERMISSIONS_PENDING = `${FETCH_PERMISSIONS}_${

View File

@@ -3,44 +3,44 @@ import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";
import fetchMock from "fetch-mock";
import reducer, {
fetchPermissions,
fetchPermissionsSuccess,
getPermissionsOfRepo,
isFetchPermissionsPending,
getFetchPermissionsFailure,
modifyPermission,
modifyPermissionSuccess,
getModifyPermissionFailure,
isModifyPermissionPending,
createPermission,
hasCreatePermission,
deletePermission,
deletePermissionSuccess,
getDeletePermissionFailure,
isDeletePermissionPending,
getModifyPermissionsFailure,
MODIFY_PERMISSION_FAILURE,
MODIFY_PERMISSION_PENDING,
FETCH_PERMISSIONS,
FETCH_PERMISSIONS_PENDING,
FETCH_PERMISSIONS_SUCCESS,
FETCH_PERMISSIONS_FAILURE,
MODIFY_PERMISSION_SUCCESS,
MODIFY_PERMISSION,
CREATE_PERMISSION,
CREATE_PERMISSION_FAILURE,
CREATE_PERMISSION_PENDING,
CREATE_PERMISSION_SUCCESS,
CREATE_PERMISSION_FAILURE,
createPermission,
createPermissionSuccess,
DELETE_PERMISSION,
DELETE_PERMISSION_FAILURE,
DELETE_PERMISSION_PENDING,
DELETE_PERMISSION_SUCCESS,
DELETE_PERMISSION_FAILURE,
CREATE_PERMISSION,
createPermissionSuccess,
deletePermission,
deletePermissionSuccess,
FETCH_PERMISSIONS,
FETCH_PERMISSIONS_FAILURE,
FETCH_PERMISSIONS_PENDING,
FETCH_PERMISSIONS_SUCCESS,
fetchPermissions,
fetchPermissionsSuccess,
getCreatePermissionFailure,
getDeletePermissionFailure,
getDeletePermissionsFailure,
getFetchPermissionsFailure,
getModifyPermissionFailure,
getModifyPermissionsFailure,
getPermissionsOfRepo,
hasCreatePermission,
isCreatePermissionPending,
getDeletePermissionsFailure
isDeletePermissionPending,
isFetchPermissionsPending,
isModifyPermissionPending,
MODIFY_PERMISSION,
MODIFY_PERMISSION_FAILURE,
MODIFY_PERMISSION_PENDING,
MODIFY_PERMISSION_SUCCESS,
modifyPermission,
modifyPermissionSuccess
} from "./permissions";
import type { Permission, PermissionCollection } from "@scm-manager/ui-types";
import type {Permission, PermissionCollection} from "@scm-manager/ui-types";
const hitchhiker_puzzle42Permission_user_eins: Permission = {
name: "user_eins",

View File

@@ -1,6 +1,7 @@
@import "bulma/sass/utilities/initial-variables";
@import "bulma/sass/utilities/functions";
$blue: #33B2E8;
// $footer-background-color
@@ -52,3 +53,5 @@ $blue: #33B2E8;
@import "@fortawesome/fontawesome-free/scss/fontawesome.scss";
$fa-font-path: "webfonts";
@import "@fortawesome/fontawesome-free/scss/solid.scss";
@import "diff2html/dist/diff2html";

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,6 @@ import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryNotFoundException;
import sonia.scm.repository.RepositoryPermissions;
import sonia.scm.repository.RevisionNotFoundException;
import sonia.scm.repository.api.LogCommandBuilder;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.web.VndMediaType;
@@ -26,6 +25,7 @@ import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.util.Optional;
@Slf4j
@@ -89,7 +89,7 @@ public class ChangesetRootResource {
@Produces(VndMediaType.CHANGESET)
@TypeHint(ChangesetDto.class)
@Path("{id}")
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("id") String id) throws IOException, RevisionNotFoundException, RepositoryNotFoundException {
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("id") String id) throws IOException {
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
Repository repository = repositoryService.getRepository();
RepositoryPermissions.read(repository).check();
@@ -97,8 +97,12 @@ public class ChangesetRootResource {
.setStartChangeset(id)
.setEndChangeset(id)
.getChangesets();
if (changesets != null && changesets.getChangesets() != null && changesets.getChangesets().size() == 1) {
return Response.ok(changesetToChangesetDtoMapper.map(changesets.getChangesets().get(0), repository)).build();
if (changesets != null && changesets.getChangesets() != null && !changesets.getChangesets().isEmpty()) {
Optional<Changeset> changeset = changesets.getChangesets().stream().filter(ch -> ch.getId().equals(id)).findFirst();
if (!changeset.isPresent()) {
return Response.status(Response.Status.NOT_FOUND).build();
}
return Response.ok(changesetToChangesetDtoMapper.map(changeset.get(), repository)).build();
} else {
return Response.status(Response.Status.NOT_FOUND).build();
}

View File

@@ -5,16 +5,20 @@ import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import sonia.scm.NotFoundException;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.RevisionNotFoundException;
import sonia.scm.repository.api.DiffFormat;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.util.HttpUtil;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.validation.constraints.Pattern;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
@@ -22,6 +26,9 @@ import javax.ws.rs.core.StreamingOutput;
public class DiffRootResource {
public static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition";
private static final String DIFF_FORMAT_VALUES_REGEX = "NATIVE|GIT|UNIFIED";
private final RepositoryServiceFactory serviceFactory;
@Inject
@@ -50,13 +57,15 @@ public class DiffRootResource {
@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 get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision){
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision , @Pattern(regexp = DIFF_FORMAT_VALUES_REGEX) @DefaultValue("NATIVE") @QueryParam("format") String format ){
HttpUtil.checkForCRLFInjection(revision);
DiffFormat diffFormat = DiffFormat.valueOf(format);
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
StreamingOutput responseEntry = output -> {
try {
repositoryService.getDiffCommand()
.setRevision(revision)
.setFormat(diffFormat)
.retriveContent(output);
} catch (RevisionNotFoundException e) {
throw new WebApplicationException(Response.Status.NOT_FOUND);

View File

@@ -169,7 +169,7 @@ public class ChangesetRootResourceTest extends RepositoryTestBase {
when(logCommandBuilder.setStartChangeset(anyString())).thenReturn(logCommandBuilder);
when(logCommandBuilder.getChangesets()).thenReturn(changesetPagingResult);
MockHttpRequest request = MockHttpRequest
.get(CHANGESET_URL + "id")
.get(CHANGESET_URL + id)
.accept(VndMediaType.CHANGESET);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);

View File

@@ -18,19 +18,23 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.api.rest.AuthorizationExceptionMapper;
import sonia.scm.api.rest.IllegalArgumentExceptionMapper;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryNotFoundException;
import sonia.scm.repository.RevisionNotFoundException;
import sonia.scm.repository.api.DiffCommandBuilder;
import sonia.scm.repository.api.DiffFormat;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.web.VndMediaType;
import java.net.URISyntaxException;
import java.util.Arrays;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
@@ -72,6 +76,7 @@ public class DiffResourceTest extends RepositoryTestBase {
dispatcher.getProviderFactory().registerProvider(NotFoundExceptionMapper.class);
dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class);
dispatcher.getProviderFactory().registerProvider(CRLFInjectionExceptionMapper.class);
dispatcher.getProviderFactory().registerProvider(IllegalArgumentExceptionMapper.class);
when(service.getDiffCommand()).thenReturn(diffCommandBuilder);
subjectThreadState.bind();
ThreadContext.bind(subject);
@@ -86,19 +91,17 @@ public class DiffResourceTest extends RepositoryTestBase {
@Test
public void shouldGetDiffs() throws Exception {
when(diffCommandBuilder.setRevision(anyString())).thenReturn(diffCommandBuilder);
when(diffCommandBuilder.setFormat(any())).thenReturn(diffCommandBuilder);
when(diffCommandBuilder.retriveContent(any())).thenReturn(diffCommandBuilder);
MockHttpRequest request = MockHttpRequest
.get(DIFF_URL + "revision")
.accept(VndMediaType.DIFF);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(200, response.getStatus());
log.info("Response :{}", response.getContentAsString());
assertThat(response.getStatus())
.isEqualTo(200);
assertThat(response.getContentAsString())
.isNotNull();
String expectedHeader = "Content-Disposition";
String expectedValue = "attachment; filename=\"repo-revision.diff\"; filename*=utf-8''repo-revision.diff";
assertThat(response.getOutputHeaders().containsKey(expectedHeader)).isTrue();
@@ -120,6 +123,7 @@ public class DiffResourceTest extends RepositoryTestBase {
@Test
public void shouldGet404OnMissingRevision() throws Exception {
when(diffCommandBuilder.setRevision(anyString())).thenReturn(diffCommandBuilder);
when(diffCommandBuilder.setFormat(any())).thenReturn(diffCommandBuilder);
when(diffCommandBuilder.retriveContent(any())).thenThrow(RevisionNotFoundException.class);
MockHttpRequest request = MockHttpRequest
@@ -133,6 +137,7 @@ public class DiffResourceTest extends RepositoryTestBase {
@Test
public void shouldGet400OnCrlfInjection() throws Exception {
when(diffCommandBuilder.setRevision(anyString())).thenReturn(diffCommandBuilder);
when(diffCommandBuilder.setFormat(any())).thenReturn(diffCommandBuilder);
when(diffCommandBuilder.retriveContent(any())).thenThrow(RevisionNotFoundException.class);
MockHttpRequest request = MockHttpRequest
@@ -143,6 +148,47 @@ public class DiffResourceTest extends RepositoryTestBase {
assertEquals(400, response.getStatus());
}
@Test
public void shouldGet400OnUnknownFormat() throws Exception {
when(diffCommandBuilder.setRevision(anyString())).thenReturn(diffCommandBuilder);
when(diffCommandBuilder.setFormat(any())).thenReturn(diffCommandBuilder);
when(diffCommandBuilder.retriveContent(any())).thenThrow(RevisionNotFoundException.class);
MockHttpRequest request = MockHttpRequest
.get(DIFF_URL + "revision?format=Unknown")
.accept(VndMediaType.DIFF);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(400, response.getStatus());
}
@Test
public void shouldAcceptDiffFormats() throws Exception {
when(diffCommandBuilder.setRevision(anyString())).thenReturn(diffCommandBuilder);
when(diffCommandBuilder.setFormat(any())).thenReturn(diffCommandBuilder);
when(diffCommandBuilder.retriveContent(any())).thenReturn(diffCommandBuilder);
Arrays.stream(DiffFormat.values()).map(DiffFormat::name).forEach(
this::assertRequestOk
);
}
private void assertRequestOk(String format) {
MockHttpRequest request = null;
try {
request = MockHttpRequest
.get(DIFF_URL + "revision?format=" + format)
.accept(VndMediaType.DIFF);
} catch (URISyntaxException e) {
e.printStackTrace();
fail("got exception: " + e);
}
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertThat(response.getStatus())
.withFailMessage("diff format from DiffFormat enum must be accepted: " + format)
.isEqualTo(200);
}
}