mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-07-04 12:19:53 +02:00
265
scm-it/src/test/java/sonia/scm/it/DiffITCase.java
Normal file
265
scm-it/src/test/java/sonia/scm/it/DiffITCase.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
BIN
scm-it/src/test/resources/diff/binaryfile/echo
Executable file
BIN
scm-it/src/test/resources/diff/binaryfile/echo
Executable file
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
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { withContextPath } from "./urls";
|
||||
import {withContextPath} from "./urls";
|
||||
|
||||
type Props = {
|
||||
src: string,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @flow
|
||||
import { concat, getPageFromMatch, withEndingSlash } from "./urls";
|
||||
import {concat, getPageFromMatch, withEndingSlash} from "./urls";
|
||||
|
||||
describe("tests for withEndingSlash", () => {
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//@flow
|
||||
import type { Links } from "./hal";
|
||||
import type {Links} from "./hal";
|
||||
|
||||
export type Permission = PermissionCreateEntry & {
|
||||
_links: Links
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}
|
||||
|
||||
51
scm-ui/src/repos/containers/ScmDiff.js
Normal file
51
scm-ui/src/repos/containers/ScmDiff.js
Normal 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;
|
||||
@@ -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}`;
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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}_${
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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";
|
||||
|
||||
1448
scm-ui/yarn.lock
1448
scm-ui/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user