diff --git a/pom.xml b/pom.xml
index df49023265..383ed6186d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -757,7 +757,7 @@
4.0
- 1.3.0
+ 1.4.0
9.2.10.v20150310
diff --git a/scm-it/src/test/java/sonia/scm/it/DiffITCase.java b/scm-it/src/test/java/sonia/scm/it/DiffITCase.java
new file mode 100644
index 0000000000..acd2816422
--- /dev/null
+++ b/scm-it/src/test/java/sonia/scm/it/DiffITCase.java
@@ -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 svnRepositoryResponse;
+ private ScmRequests.RepositoryResponse hgRepositoryResponse;
+ private ScmRequests.RepositoryResponse 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 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 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");
+ 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 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 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 addedFiles = new HashMap() {{
+ put(addedFile, "content");
+ }};
+ Map modifiedFiles = new HashMap() {{
+ put(fileToBeUpdated, "the updated content");
+ }};
+ ArrayList removedFiles = Lists.newArrayList(fileToBeDeleted);
+ return RepositoryUtil.commitMultipleFileModifications(repositoryClient, ADMIN_USERNAME, addedFiles, modifiedFiles, removedFiles);
+ }
+}
diff --git a/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java b/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java
index 66ebc57c90..83baa89463 100644
--- a/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java
+++ b/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java
@@ -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 addedFiles = new HashMap()
- {{
+ Map addedFiles = new HashMap() {{
put("a.txt", "bla bla");
}};
- Map modifiedFiles = new HashMap()
- {{
+ Map modifiedFiles = new HashMap() {{
put("b.txt", "new content");
}};
ArrayList 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)
diff --git a/scm-it/src/test/java/sonia/scm/it/utils/RepositoryUtil.java b/scm-it/src/test/java/sonia/scm/it/utils/RepositoryUtil.java
index 11db0200f1..427d98f245 100644
--- a/scm-it/src/test/java/sonia/scm/it/utils/RepositoryUtil.java
+++ b/scm-it/src/test/java/sonia/scm/it/utils/RepositoryUtil.java
@@ -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)) {
diff --git a/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java b/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java
index 14caa57beb..e784d3b4b1 100644
--- a/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java
+++ b/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java
@@ -234,8 +234,8 @@ public class ScmRequests {
return this;
}
- public DiffResponse requestDiff(String revision) {
- return new DiffResponse<>(applyGETRequestFromLink(response, "_embedded.changesets.find{it.id=='" + revision + "'}._links.diff.href"), this);
+ public DiffResponse requestDiffInGitFormat(String revision) {
+ return new DiffResponse<>(applyGETRequestFromLinkWithParams(response, "_embedded.changesets.find{it.id=='" + revision + "'}._links.diff.href", "?format=GIT"), this);
}
public ModificationsResponse requestModifications(String revision) {
@@ -362,6 +362,10 @@ public class ScmRequests {
this.previousResponse = previousResponse;
}
+ public Response getResponse(){
+ return response;
+ }
+
public PREV returnToPrevious() {
return previousResponse;
}
diff --git a/scm-it/src/test/resources/diff/binaryfile/echo b/scm-it/src/test/resources/diff/binaryfile/echo
new file mode 100755
index 0000000000..11bc2152e4
Binary files /dev/null and b/scm-it/src/test/resources/diff/binaryfile/echo differ
diff --git a/scm-it/src/test/resources/diff/largefile/modified/v1/SvnDiffGenerator_forTest b/scm-it/src/test/resources/diff/largefile/modified/v1/SvnDiffGenerator_forTest
new file mode 100644
index 0000000000..f714a955c7
--- /dev/null
+++ b/scm-it/src/test/resources/diff/largefile/modified/v1/SvnDiffGenerator_forTest
@@ -0,0 +1,1230 @@
+package sonia.scm.repository.spi;
+
+import de.regnis.q.sequence.line.diff.QDiffGenerator;
+import de.regnis.q.sequence.line.diff.QDiffGeneratorFactory;
+import de.regnis.q.sequence.line.diff.QDiffManager;
+import de.regnis.q.sequence.line.diff.QDiffUniGenerator;
+import org.tmatesoft.svn.core.SVNErrorCode;
+import org.tmatesoft.svn.core.SVNErrorMessage;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNMergeRangeList;
+import org.tmatesoft.svn.core.SVNProperties;
+import org.tmatesoft.svn.core.SVNProperty;
+import org.tmatesoft.svn.core.SVNPropertyValue;
+import org.tmatesoft.svn.core.internal.util.SVNHashMap;
+import org.tmatesoft.svn.core.internal.util.SVNMergeInfoUtil;
+import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
+import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions;
+import org.tmatesoft.svn.core.internal.wc.ISVNReturnValueCallback;
+import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
+import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
+import org.tmatesoft.svn.core.internal.wc2.ng.ISvnDiffGenerator;
+import org.tmatesoft.svn.core.internal.wc2.ng.SvnDiffCallback;
+import org.tmatesoft.svn.core.wc.ISVNOptions;
+import org.tmatesoft.svn.core.wc.SVNDiffOptions;
+import org.tmatesoft.svn.core.wc2.SvnTarget;
+import org.tmatesoft.svn.util.SVNLogType;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.RandomAccessFile;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+public class SCMSvnDiffGenerator implements ISvnDiffGenerator {
+
+ protected static final String WC_REVISION_LABEL = "(working copy)";
+ protected static final String PROPERTIES_SEPARATOR = "___________________________________________________________________";
+ protected static final String HEADER_SEPARATOR = "===================================================================";
+ protected static final String HEADER_ENCODING = "UTF-8";
+
+ private SvnTarget originalTarget1;
+ private SvnTarget originalTarget2;
+ private SvnTarget baseTarget;
+ private SvnTarget relativeToTarget;
+ private SvnTarget repositoryRoot;
+ private String encoding;
+ private byte[] eol;
+ private boolean useGitFormat;
+ private boolean forcedBinaryDiff;
+
+ private boolean diffDeleted;
+ private boolean diffAdded;
+ private List rawDiffOptions;
+ private boolean forceEmpty;
+
+ private Set visitedPaths;
+ private String externalDiffCommand;
+ private SVNDiffOptions diffOptions;
+ private boolean fallbackToAbsolutePath;
+ private ISVNOptions options;
+ private boolean propertiesOnly;
+ private boolean ignoreProperties;
+
+ private String getDisplayPath(SvnTarget target) {
+ String relativePath;
+ if (baseTarget == null) {
+ relativePath = null;
+ } else {
+ String targetString = target.getPathOrUrlDecodedString();
+ String baseTargetString = baseTarget.getPathOrUrlDecodedString();
+ relativePath = getRelativePath(targetString, baseTargetString);
+ }
+
+ return relativePath != null ? relativePath : target.getPathOrUrlString();
+ }
+
+ private String getRelativeToRootPath(SvnTarget target, SvnTarget originalTarget) {
+ String relativePath;
+ if (repositoryRoot == null) {
+ relativePath = null;
+ } else {
+ if (repositoryRoot.isFile() == target.isFile()) {
+ String targetString = target.getPathOrUrlDecodedString();
+ String baseTargetString = repositoryRoot.getPathOrUrlDecodedString();
+ relativePath = getRelativePath(targetString, baseTargetString);
+ } else {
+ String targetString = target.getPathOrUrlDecodedString();
+ String baseTargetString = new File("").getAbsolutePath();
+ relativePath = getRelativePath(targetString, baseTargetString);
+ }
+ }
+
+ return relativePath != null ? relativePath : target.getPathOrUrlString();
+ }
+
+ private String getRelativePath(String targetString, String baseTargetString) {
+ if (targetString != null) {
+ targetString = targetString.replace(File.separatorChar, '/');
+ }
+ if (baseTargetString != null) {
+ baseTargetString = baseTargetString.replace(File.separatorChar, '/');
+ }
+
+ final String pathAsChild = SVNPathUtil.getPathAsChild(baseTargetString, targetString);
+ if (pathAsChild != null) {
+ return pathAsChild;
+ }
+ if (targetString.equals(baseTargetString)) {
+ return "";
+ }
+ return null;
+ }
+
+ private String getChildPath(String path, String relativeToPath) {
+ if (relativeToTarget == null) {
+ return null;
+ }
+
+ String relativePath = getRelativePath(path, relativeToPath);
+ if (relativePath == null) {
+ return path;
+ }
+
+ if (relativePath.length() > 0) {
+ return relativePath;
+ }
+
+ if (relativeToPath.equals(path)) {
+ return ".";
+ }
+
+ return null;
+ }
+
+ public SCMSvnDiffGenerator() {
+ this.originalTarget1 = null;
+ this.originalTarget2 = null;
+ this.visitedPaths = new HashSet();
+ this.diffDeleted = true;
+ this.diffAdded = true;
+ }
+
+ public void setBaseTarget(SvnTarget baseTarget) {
+ this.baseTarget = baseTarget;
+ }
+
+ public void setUseGitFormat(boolean useGitFormat) {
+ this.useGitFormat = useGitFormat;
+ }
+
+ public void setOriginalTargets(SvnTarget originalTarget1, SvnTarget originalTarget2) {
+ this.originalTarget1 = originalTarget1;
+ this.originalTarget2 = originalTarget2;
+ }
+
+ public void setRelativeToTarget(SvnTarget relativeToTarget) {
+ this.relativeToTarget = relativeToTarget;
+ }
+
+ public void setAnchors(SvnTarget originalTarget1, SvnTarget originalTarget2) {
+ //anchors are not used
+ }
+
+ public void setRepositoryRoot(SvnTarget repositoryRoot) {
+ this.repositoryRoot = repositoryRoot;
+ }
+
+ public void setForceEmpty(boolean forceEmpty) {
+ this.forceEmpty = forceEmpty;
+ }
+
+ public void setEncoding(String encoding) {
+ this.encoding = encoding;
+ }
+
+ public String getEncoding() {
+ return encoding;
+ }
+
+ public String getGlobalEncoding() {
+ ISVNOptions options = getOptions();
+
+ if (options != null && options instanceof DefaultSVNOptions) {
+ DefaultSVNOptions defaultOptions = (DefaultSVNOptions) options;
+ return defaultOptions.getGlobalCharset();
+ }
+ return null;
+ }
+
+ public void setEOL(byte[] eol) {
+ this.eol = eol;
+ }
+
+ public byte[] getEOL() {
+ return eol;
+ }
+
+ public boolean isForcedBinaryDiff() {
+ return forcedBinaryDiff;
+ }
+
+ public void setForcedBinaryDiff(boolean forcedBinaryDiff) {
+ this.forcedBinaryDiff = forcedBinaryDiff;
+ }
+
+ public boolean isPropertiesOnly() {
+ return propertiesOnly;
+ }
+
+ public void setPropertiesOnly(boolean propertiesOnly) {
+ this.propertiesOnly = propertiesOnly;
+ }
+
+ public boolean isIgnoreProperties() {
+ return ignoreProperties;
+ }
+
+ public void setIgnoreProperties(boolean ignoreProperties) {
+ this.ignoreProperties = ignoreProperties;
+ }
+
+ public void displayDeletedDirectory(SvnTarget target, String revision1, String revision2, OutputStream outputStream) throws SVNException {
+ }
+
+ public void displayAddedDirectory(SvnTarget target, String revision1, String revision2, OutputStream outputStream) throws SVNException {
+ }
+
+ public void displayPropsChanged(SvnTarget target, String revision1, String revision2, boolean dirWasAdded, SVNProperties originalProps, SVNProperties propChanges, OutputStream outputStream) throws SVNException {
+ if (isIgnoreProperties()) {
+ return;
+ }
+ if (dirWasAdded && !isDiffAdded()) {
+ return;
+ }
+ ensureEncodingAndEOLSet();
+ String displayPath = getDisplayPath(target);
+
+ String targetString1 = originalTarget1.getPathOrUrlDecodedString();
+ String targetString2 = originalTarget2.getPathOrUrlDecodedString();
+
+ if (displayPath == null || displayPath.length() == 0) {
+ displayPath = ".";
+ }
+
+ if (useGitFormat) {
+ targetString1 = adjustRelativeToReposRoot(targetString1);
+ targetString2 = adjustRelativeToReposRoot(targetString2);
+ }
+
+ String newTargetString = displayPath;
+ String newTargetString1 = targetString1;
+ String newTargetString2 = targetString2;
+
+ String commonAncestor = SVNPathUtil.getCommonPathAncestor(newTargetString1, newTargetString2);
+ int commonLength = commonAncestor == null ? 0 : commonAncestor.length();
+
+ newTargetString1 = newTargetString1.substring(commonLength);
+ newTargetString2 = newTargetString2.substring(commonLength);
+
+ newTargetString1 = computeLabel(newTargetString, newTargetString1);
+ newTargetString2 = computeLabel(newTargetString, newTargetString2);
+
+ if (relativeToTarget != null) {
+ String relativeToPath = relativeToTarget.getPathOrUrlDecodedString();
+ String absolutePath = target.getPathOrUrlDecodedString();
+
+ String childPath = getChildPath(absolutePath, relativeToPath);
+ if (childPath == null) {
+ throwBadRelativePathException(absolutePath, relativeToPath);
+ }
+ String childPath1 = getChildPath(newTargetString1, relativeToPath);
+ if (childPath1 == null) {
+ throwBadRelativePathException(newTargetString1, relativeToPath);
+ }
+ String childPath2 = getChildPath(newTargetString2, relativeToPath);
+ if (childPath2 == null) {
+ throwBadRelativePathException(newTargetString2, relativeToPath);
+ }
+
+ displayPath = childPath;
+ newTargetString1 = childPath1;
+ newTargetString2 = childPath2;
+ }
+
+ boolean showDiffHeader = !visitedPaths.contains(displayPath);
+ if (showDiffHeader) {
+ String label1 = getLabel(newTargetString1, revision1);
+ String label2 = getLabel(newTargetString2, revision2);
+
+ boolean shouldStopDisplaying = displayHeader(outputStream, displayPath, false, fallbackToAbsolutePath, SvnDiffCallback.OperationKind.Modified);
+ visitedPaths.add(displayPath);
+ if (useGitFormat) {
+ displayGitDiffHeader(outputStream, SvnDiffCallback.OperationKind.Modified,
+ getRelativeToRootPath(target, originalTarget1),
+ getRelativeToRootPath(target, originalTarget2),
+ null);
+ }
+ if (shouldStopDisplaying) {
+ return;
+ }
+
+// if (useGitFormat) {
+// String copyFromPath = null;
+// SvnDiffCallback.OperationKind operationKind = SvnDiffCallback.OperationKind.Modified;
+// label1 = getGitDiffLabel1(operationKind, targetString1, targetString2, copyFromPath, revision1);
+// label2 = getGitDiffLabel2(operationKind, targetString1, targetString2, copyFromPath, revision2);
+// displayGitDiffHeader(outputStream, operationKind,
+// getRelativeToRootPath(target, originalTarget1),
+// getRelativeToRootPath(target, originalTarget2),
+// copyFromPath);
+// }
+
+ if (useGitFormat) {
+ displayGitHeaderFields(outputStream, target, revision1, revision2, SvnDiffCallback.OperationKind.Modified, null);
+ } else {
+ displayHeaderFields(outputStream, label1, label2);
+ }
+ }
+
+ displayPropertyChangesOn(useGitFormat ? getRelativeToRootPath(target, originalTarget1) : displayPath, outputStream);
+
+ displayPropDiffValues(outputStream, propChanges, originalProps);
+ }
+
+ private void throwBadRelativePathException(String displayPath, String relativeToPath) throws SVNException {
+ SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.BAD_RELATIVE_PATH, "Path ''{0}'' must be an immediate child of the directory ''{0}''",
+ displayPath, relativeToPath);
+ SVNErrorManager.error(errorMessage, SVNLogType.CLIENT);
+ }
+
+ private void displayGitHeaderFields(OutputStream outputStream, SvnTarget target, String revision1, String revision2, SvnDiffCallback.OperationKind operation, String copyFromPath) throws SVNException {
+ String path1 = copyFromPath != null ? copyFromPath : getRelativeToRootPath(target, originalTarget1);
+ String path2 = getRelativeToRootPath(target, originalTarget2);
+
+ try {
+ displayString(outputStream, "--- ");
+ displayFirstGitLabelPath(outputStream, path1, revision1, operation);
+ displayEOL(outputStream);
+ displayString(outputStream, "+++ ");
+ displaySecondGitLabelPath(outputStream, path2, revision2, operation);
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private String adjustRelativeToReposRoot(String targetString) {
+ if (repositoryRoot != null) {
+ String repositoryRootString = repositoryRoot.getPathOrUrlDecodedString();
+ String relativePath = getRelativePath(targetString, repositoryRootString);
+ return relativePath == null ? "" : relativePath;
+ }
+ return targetString;
+ }
+
+ private String computeLabel(String targetString, String originalTargetString) {
+ if (originalTargetString.length() == 0) {
+ return targetString;
+ } else if (originalTargetString.charAt(0) == '/') {
+ return targetString + "\t(..." + originalTargetString + ")";
+ } else {
+ return targetString + "\t(.../" + originalTargetString + ")";
+ }
+ }
+
+ public void displayContentChanged(SvnTarget target, File leftFile, File rightFile, String revision1, String revision2, String mimeType1, String mimeType2, SvnDiffCallback.OperationKind operation, File copyFromPath, SVNProperties originalProperties, SVNProperties propChanges, OutputStream outputStream) throws SVNException {
+ if (isPropertiesOnly()) {
+ return;
+ }
+ ensureEncodingAndEOLSet();
+ String displayPath = getDisplayPath(target);
+
+ String targetString1 = originalTarget1.getPathOrUrlDecodedString();
+ String targetString2 = originalTarget2.getPathOrUrlDecodedString();
+
+ if (useGitFormat) {
+ targetString1 = adjustRelativeToReposRoot(targetString1);
+ targetString2 = adjustRelativeToReposRoot(targetString2);
+ }
+
+ String newTargetString = displayPath;
+ String newTargetString1 = targetString1;
+ String newTargetString2 = targetString2;
+
+ String commonAncestor = SVNPathUtil.getCommonPathAncestor(newTargetString1, newTargetString2);
+ int commonLength = commonAncestor == null ? 0 : commonAncestor.length();
+
+ newTargetString1 = newTargetString1.substring(commonLength);
+ newTargetString2 = newTargetString2.substring(commonLength);
+
+ newTargetString1 = computeLabel(newTargetString, newTargetString1);
+ newTargetString2 = computeLabel(newTargetString, newTargetString2);
+
+ if (relativeToTarget != null) {
+ String relativeToPath = relativeToTarget.getPathOrUrlDecodedString();
+ String absolutePath = target.getPathOrUrlDecodedString();
+
+ String childPath = getChildPath(absolutePath, relativeToPath);
+ if (childPath == null) {
+ throwBadRelativePathException(absolutePath, relativeToPath);
+ }
+ String childPath1 = getChildPath(newTargetString1, relativeToPath);
+ if (childPath1 == null) {
+ throwBadRelativePathException(newTargetString1, relativeToPath);
+ }
+ String childPath2 = getChildPath(newTargetString2, relativeToPath);
+ if (childPath2 == null) {
+ throwBadRelativePathException(newTargetString2, relativeToPath);
+ }
+
+ displayPath = childPath;
+ newTargetString1 = childPath1;
+ newTargetString2 = childPath2;
+ }
+
+ String label1 = getLabel(newTargetString1, revision1);
+ String label2 = getLabel(newTargetString2, revision2);
+
+ boolean leftIsBinary = false;
+ boolean rightIsBinary = false;
+
+ if (mimeType1 != null) {
+ leftIsBinary = SVNProperty.isBinaryMimeType(mimeType1);
+ }
+ if (mimeType2 != null) {
+ rightIsBinary = SVNProperty.isBinaryMimeType(mimeType2);
+ }
+
+ if (!forcedBinaryDiff && (leftIsBinary || rightIsBinary)) {
+ boolean shouldStopDisplaying = displayHeader(outputStream, displayPath, rightFile == null, leftFile == null, operation);
+ if (useGitFormat) {
+ displayGitDiffHeader(outputStream, operation,
+ getRelativeToRootPath(target, originalTarget1),
+ getRelativeToRootPath(target, originalTarget2),
+ null);
+ }
+ visitedPaths.add(displayPath);
+ if (shouldStopDisplaying) {
+ return;
+ }
+
+
+ displayBinary(mimeType1, mimeType2, outputStream, leftIsBinary, rightIsBinary);
+
+ return;
+ }
+
+ final String diffCommand = getExternalDiffCommand();
+ if (diffCommand != null) {
+ boolean shouldStopDisplaying = displayHeader(outputStream, displayPath, rightFile == null, leftFile == null, operation);
+ if (useGitFormat) {
+ displayGitDiffHeader(outputStream, operation,
+ getRelativeToRootPath(target, originalTarget1),
+ getRelativeToRootPath(target, originalTarget2),
+ null);
+ }
+ visitedPaths.add(displayPath);
+ if (shouldStopDisplaying) {
+ return;
+ }
+
+ runExternalDiffCommand(outputStream, diffCommand, leftFile, rightFile, label1, label2);
+ } else {
+ internalDiff(target, outputStream, displayPath, leftFile, rightFile, label1, label2, operation, copyFromPath == null ? null : copyFromPath.getPath(), revision1, revision2);
+ }
+ }
+
+ private void displayBinary(String mimeType1, String mimeType2, OutputStream outputStream, boolean leftIsBinary, boolean rightIsBinary) throws SVNException {
+ displayCannotDisplayFileMarkedBinary(outputStream);
+
+ if (leftIsBinary && !rightIsBinary) {
+ displayMimeType(outputStream, mimeType1);
+ } else if (!leftIsBinary && rightIsBinary) {
+ displayMimeType(outputStream, mimeType2);
+ } else if (leftIsBinary && rightIsBinary) {
+ if (mimeType1.equals(mimeType2)) {
+ displayMimeType(outputStream, mimeType1);
+ } else {
+ displayMimeTypes(outputStream, mimeType1, mimeType2);
+ }
+ }
+ }
+
+ private void internalDiff(SvnTarget target, OutputStream outputStream, String displayPath, File file1, File file2, String label1, String label2, SvnDiffCallback.OperationKind operation, String copyFromPath, String revision1, String revision2) throws SVNException {
+ String header = getHeaderString(target, displayPath, file2 == null, file1 == null, operation, copyFromPath);
+ if (file2 == null && !isDiffDeleted()) {
+ try {
+ displayString(outputStream, header);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ visitedPaths.add(displayPath);
+ return;
+ }
+ if (file1 == null && !isDiffAdded()) {
+ try {
+ displayString(outputStream, header);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ visitedPaths.add(displayPath);
+ return;
+ }
+ String headerFields = getHeaderFieldsString(target, displayPath, label1, label2, revision1, revision2, operation, copyFromPath);
+
+ RandomAccessFile is1 = null;
+ RandomAccessFile is2 = null;
+ try {
+ is1 = file1 == null ? null : SVNFileUtil.openRAFileForReading(file1);
+ is2 = file2 == null ? null : SVNFileUtil.openRAFileForReading(file2);
+
+ QDiffUniGenerator.setup();
+ Map properties = new SVNHashMap();
+
+ properties.put(QDiffGeneratorFactory.IGNORE_EOL_PROPERTY, Boolean.valueOf(getDiffOptions().isIgnoreEOLStyle()));
+ properties.put(QDiffGeneratorFactory.EOL_PROPERTY, new String(getEOL()));
+ if (getDiffOptions().isIgnoreAllWhitespace()) {
+ properties.put(QDiffGeneratorFactory.IGNORE_SPACE_PROPERTY, QDiffGeneratorFactory.IGNORE_ALL_SPACE);
+ } else if (getDiffOptions().isIgnoreAmountOfWhitespace()) {
+ properties.put(QDiffGeneratorFactory.IGNORE_SPACE_PROPERTY, QDiffGeneratorFactory.IGNORE_SPACE_CHANGE);
+ }
+
+ final String diffHeader;
+ if (forceEmpty || useGitFormat) {
+ displayString(outputStream, header);
+ diffHeader = headerFields;
+
+ visitedPaths.add(displayPath);
+ } else {
+ diffHeader = header + headerFields;
+ }
+ QDiffGenerator generator = new QDiffUniGenerator(properties, diffHeader);
+ EmptyDetectionOutputStream emptyDetectionOutputStream = new EmptyDetectionOutputStream(outputStream);
+ QDiffManager.generateTextDiff(is1, is2, emptyDetectionOutputStream, generator);
+ if (emptyDetectionOutputStream.isSomethingWritten()) {
+ visitedPaths.add(displayPath);
+ }
+ emptyDetectionOutputStream.flush();
+ } catch (IOException e) {
+ SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getMessage());
+ SVNErrorManager.error(err, e, SVNLogType.DEFAULT);
+ } finally {
+ SVNFileUtil.closeFile(is1);
+ SVNFileUtil.closeFile(is2);
+ }
+ }
+
+ private String getHeaderFieldsString(SvnTarget target, String displayPath, String label1, String label2, String revision1, String revision2, SvnDiffCallback.OperationKind operation, String copyFromPath) throws SVNException {
+ final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ try {
+ if (useGitFormat) {
+ displayGitHeaderFields(byteArrayOutputStream, target, revision1, revision2, operation, copyFromPath);
+ } else {
+ displayHeaderFields(byteArrayOutputStream, label1, label2);
+ }
+ } catch (SVNException e) {
+ SVNFileUtil.closeFile(byteArrayOutputStream);
+
+ try {
+ byteArrayOutputStream.writeTo(byteArrayOutputStream);
+ } catch (IOException e1) {
+ }
+
+ throw e;
+ }
+
+ try {
+ byteArrayOutputStream.close();
+ return byteArrayOutputStream.toString(HEADER_ENCODING);
+ } catch (IOException e) {
+ return "";
+ }
+ }
+
+ private String getHeaderString(SvnTarget target, String displayPath, boolean deleted, boolean added, SvnDiffCallback.OperationKind operation, String copyFromPath) throws SVNException {
+ final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ try {
+ boolean stopDisplaying = displayHeader(byteArrayOutputStream, displayPath, deleted, added, operation);
+ if (useGitFormat) {
+ displayGitDiffHeader(byteArrayOutputStream, operation,
+ getRelativeToRootPath(target, originalTarget1),
+ getRelativeToRootPath(target, originalTarget2),
+ copyFromPath);
+ }
+ } catch (SVNException e) {
+ SVNFileUtil.closeFile(byteArrayOutputStream);
+
+ try {
+ byteArrayOutputStream.writeTo(byteArrayOutputStream);
+ } catch (IOException e1) {
+ }
+
+ throw e;
+ }
+
+ try {
+ byteArrayOutputStream.close();
+ return byteArrayOutputStream.toString(HEADER_ENCODING);
+ } catch (IOException e) {
+ return "";
+ }
+ }
+
+ private void runExternalDiffCommand(OutputStream outputStream, final String diffCommand, File file1, File file2, String label1, String label2) throws SVNException {
+ final List args = new ArrayList();
+ args.add(diffCommand);
+ if (rawDiffOptions != null) {
+ args.addAll(rawDiffOptions);
+ } else {
+ Collection svnDiffOptionsCollection = getDiffOptions().toOptionsCollection();
+ args.addAll(svnDiffOptionsCollection);
+ args.add("-u");
+ }
+
+ if (label1 != null) {
+ args.add("-L");
+ args.add(label1);
+ }
+
+ if (label2 != null) {
+ args.add("-L");
+ args.add(label2);
+ }
+
+ boolean tmpFile1 = false;
+ boolean tmpFile2 = false;
+ if (file1 == null) {
+ file1 = SVNFileUtil.createTempFile("svn.", ".tmp");
+ tmpFile1 = true;
+ }
+ if (file2 == null) {
+ file2 = SVNFileUtil.createTempFile("svn.", ".tmp");
+ tmpFile2 = true;
+ }
+
+ String file1Path = file1.getAbsolutePath().replace(File.separatorChar, '/');
+ String file2Path = file2.getAbsolutePath().replace(File.separatorChar, '/');
+
+ args.add(file1Path);
+ args.add(file2Path);
+ try {
+ final Writer writer = new OutputStreamWriter(outputStream, getEncoding());
+
+ SVNFileUtil.execCommand(args.toArray(new String[args.size()]), true,
+ new ISVNReturnValueCallback() {
+
+ public void handleReturnValue(int returnValue) throws SVNException {
+ if (returnValue != 0 && returnValue != 1) {
+ SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.EXTERNAL_PROGRAM,
+ "''{0}'' returned {1}", new Object[]{diffCommand, String.valueOf(returnValue)});
+ SVNErrorManager.error(err, SVNLogType.DEFAULT);
+ }
+ }
+
+ public void handleChar(char ch) throws SVNException {
+ try {
+ writer.write(ch);
+ } catch (IOException ioe) {
+ SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getMessage());
+ SVNErrorManager.error(err, ioe, SVNLogType.DEFAULT);
+ }
+ }
+
+ public boolean isHandleProgramOutput() {
+ return true;
+ }
+ });
+
+ writer.flush();
+ } catch (IOException ioe) {
+ SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getMessage());
+ SVNErrorManager.error(err, ioe, SVNLogType.DEFAULT);
+ } finally {
+ try {
+ if (tmpFile1) {
+ SVNFileUtil.deleteFile(file1);
+ }
+ if (tmpFile2) {
+ SVNFileUtil.deleteFile(file2);
+ }
+ } catch (SVNException e) {
+ // skip
+ }
+ }
+ }
+
+ private String getExternalDiffCommand() {
+ return externalDiffCommand;
+ }
+
+ private void displayMimeType(OutputStream outputStream, String mimeType) throws SVNException {
+ try {
+ displayString(outputStream, SVNProperty.MIME_TYPE);
+ displayString(outputStream, " = ");
+ displayString(outputStream, mimeType);
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void displayMimeTypes(OutputStream outputStream, String mimeType1, String mimeType2) throws SVNException {
+ try {
+ displayString(outputStream, SVNProperty.MIME_TYPE);
+ displayString(outputStream, " = (");
+ displayString(outputStream, mimeType1);
+ displayString(outputStream, ", ");
+ displayString(outputStream, mimeType2);
+ displayString(outputStream, ")");
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void displayCannotDisplayFileMarkedBinary(OutputStream outputStream) throws SVNException {
+ try {
+ displayString(outputStream, "Cannot display: file marked as a binary type.");
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void ensureEncodingAndEOLSet() {
+ if (getEOL() == null) {
+ setEOL(SVNProperty.EOL_LF_BYTES);
+ }
+ if (getEncoding() == null) {
+ final ISVNOptions options = getOptions();
+ if (options != null && options.getNativeCharset() != null) {
+ setEncoding(options.getNativeCharset());
+ } else {
+ setEncoding("UTF-8");
+ }
+ }
+ }
+
+ private void displayPropDiffValues(OutputStream outputStream, SVNProperties diff, SVNProperties baseProps) throws SVNException {
+ for (Iterator changedPropNames = diff.nameSet().iterator(); changedPropNames.hasNext(); ) {
+ String name = (String) changedPropNames.next();
+ SVNPropertyValue originalValue = baseProps != null ? baseProps.getSVNPropertyValue(name) : null;
+ SVNPropertyValue newValue = diff.getSVNPropertyValue(name);
+ String headerFormat = null;
+
+ if (originalValue == null) {
+ headerFormat = "Added: ";
+ } else if (newValue == null) {
+ headerFormat = "Deleted: ";
+ } else {
+ headerFormat = "Modified: ";
+ }
+
+ try {
+ displayString(outputStream, (headerFormat + name));
+ displayEOL(outputStream);
+ if (SVNProperty.MERGE_INFO.equals(name)) {
+ displayMergeInfoDiff(outputStream, originalValue == null ? null : originalValue.getString(), newValue == null ? null : newValue.getString());
+ continue;
+ }
+
+ byte[] originalValueBytes = getPropertyAsBytes(originalValue, getEncoding());
+ byte[] newValueBytes = getPropertyAsBytes(newValue, getEncoding());
+
+ if (originalValueBytes == null) {
+ originalValueBytes = new byte[0];
+ } else {
+ originalValueBytes = maybeAppendEOL(originalValueBytes);
+ }
+
+ boolean newValueHadEol = newValueBytes != null && newValueBytes.length > 0 &&
+ (newValueBytes[newValueBytes.length - 1] == SVNProperty.EOL_CR_BYTES[0] ||
+ newValueBytes[newValueBytes.length - 1] == SVNProperty.EOL_LF_BYTES[0]);
+
+ if (newValueBytes == null) {
+ newValueBytes = new byte[0];
+ } else {
+ newValueBytes = maybeAppendEOL(newValueBytes);
+ }
+
+ QDiffUniGenerator.setup();
+ Map properties = new SVNHashMap();
+
+ properties.put(QDiffGeneratorFactory.IGNORE_EOL_PROPERTY, Boolean.valueOf(getDiffOptions().isIgnoreEOLStyle()));
+ properties.put(QDiffGeneratorFactory.EOL_PROPERTY, new String(getEOL()));
+ properties.put(QDiffGeneratorFactory.HUNK_DELIMITER, "##");
+ if (getDiffOptions().isIgnoreAllWhitespace()) {
+ properties.put(QDiffGeneratorFactory.IGNORE_SPACE_PROPERTY, QDiffGeneratorFactory.IGNORE_ALL_SPACE);
+ } else if (getDiffOptions().isIgnoreAmountOfWhitespace()) {
+ properties.put(QDiffGeneratorFactory.IGNORE_SPACE_PROPERTY, QDiffGeneratorFactory.IGNORE_SPACE_CHANGE);
+ }
+
+ QDiffGenerator generator = new QDiffUniGenerator(properties, "");
+ Writer writer = new OutputStreamWriter(outputStream, getEncoding());
+ QDiffManager.generateTextDiff(new ByteArrayInputStream(originalValueBytes), new ByteArrayInputStream(newValueBytes),
+ null, writer, generator);
+ writer.flush();
+ if (!newValueHadEol) {
+ displayString(outputStream, "\\ No newline at end of property");
+ displayEOL(outputStream);
+ }
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ }
+
+ private byte[] maybeAppendEOL(byte[] buffer) {
+ if (buffer.length == 0) {
+ return buffer;
+ }
+
+ byte lastByte = buffer[buffer.length - 1];
+ if (lastByte == SVNProperty.EOL_CR_BYTES[0]) {
+ return buffer;
+ } else if (lastByte != SVNProperty.EOL_LF_BYTES[0]) {
+ final byte[] newBuffer = new byte[buffer.length + getEOL().length];
+ System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
+ System.arraycopy(getEOL(), 0, newBuffer, buffer.length, getEOL().length);
+ return newBuffer;
+ } else {
+ return buffer;
+ }
+ }
+
+ private String getGitDiffLabel1(SvnDiffCallback.OperationKind operationKind, String path1, String path2, String copyFromPath, String revision) {
+ if (operationKind == SvnDiffCallback.OperationKind.Deleted) {
+ return getLabel("a/" + path1, revision);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Copied) {
+ return getLabel("a/" + copyFromPath, revision);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Added) {
+ return getLabel("/dev/null", revision);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Modified) {
+ return getLabel("a/" + path1, revision);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Moved) {
+ return getLabel("a/" + copyFromPath, revision);
+ }
+ throw new IllegalArgumentException("Unsupported operation: " + operationKind);
+ }
+
+ private String getGitDiffLabel2(SvnDiffCallback.OperationKind operationKind, String path1, String path2, String copyFromPath, String revision) {
+ if (operationKind == SvnDiffCallback.OperationKind.Deleted) {
+ return getLabel("/dev/null", revision);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Copied) {
+ return getLabel("b/" + path2, revision);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Added) {
+ return getLabel("b/" + path2, revision);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Modified) {
+ return getLabel("b/" + path2, revision);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Moved) {
+ return getLabel("b/" + path2, revision);
+ }
+ throw new IllegalArgumentException("Unsupported operation: " + operationKind);
+ }
+
+ private void displayGitDiffHeader(OutputStream outputStream, SvnDiffCallback.OperationKind operationKind, String path1, String path2, String copyFromPath) throws SVNException {
+ if (operationKind == SvnDiffCallback.OperationKind.Deleted) {
+ displayGitDiffHeaderDeleted(outputStream, path1, path2, copyFromPath);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Copied) {
+ displayGitDiffHeaderCopied(outputStream, path1, path2, copyFromPath);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Added) {
+ displayGitDiffHeaderAdded(outputStream, path1, path2, copyFromPath);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Modified) {
+ displayGitDiffHeaderModified(outputStream, path1, path2, copyFromPath);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Moved) {
+ displayGitDiffHeaderRenamed(outputStream, path1, path2, copyFromPath);
+ }
+ }
+
+ private void displayGitDiffHeaderAdded(OutputStream outputStream, String path1, String path2, String copyFromPath) throws SVNException {
+ try {
+ displayString(outputStream, "diff --git ");
+ displayFirstGitPath(outputStream, path1);
+ displayString(outputStream, " ");
+ displaySecondGitPath(outputStream, path2);
+ displayEOL(outputStream);
+ displayString(outputStream, "new file mode 10644");
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void displayGitDiffHeaderDeleted(OutputStream outputStream, String path1, String path2, String copyFromPath) throws SVNException {
+ try {
+ displayString(outputStream, "diff --git ");
+ displayFirstGitPath(outputStream, path1);
+ displayString(outputStream, " ");
+ displaySecondGitPath(outputStream, path2);
+ displayEOL(outputStream);
+ displayString(outputStream, "deleted file mode 10644");
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void displayGitDiffHeaderCopied(OutputStream outputStream, String path1, String path2, String copyFromPath) throws SVNException {
+ try {
+ displayString(outputStream, "diff --git ");
+ displayFirstGitPath(outputStream, copyFromPath);
+ displayString(outputStream, " ");
+ displaySecondGitPath(outputStream, path2);
+ displayEOL(outputStream);
+ displayString(outputStream, "copy from ");
+ displayString(outputStream, copyFromPath);
+ displayEOL(outputStream);
+ displayString(outputStream, "copy to ");
+ displayString(outputStream, path2);
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void displayGitDiffHeaderRenamed(OutputStream outputStream, String path1, String path2, String copyFromPath) throws SVNException {
+ try {
+ displayString(outputStream, "diff --git ");
+ displayFirstGitPath(outputStream, copyFromPath);
+ displayString(outputStream, " ");
+ displaySecondGitPath(outputStream, path2);
+ displayEOL(outputStream);
+ displayString(outputStream, "rename from ");
+ displayString(outputStream, copyFromPath);
+ displayEOL(outputStream);
+ displayString(outputStream, "rename to ");
+ displayString(outputStream, path2);
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void displayGitDiffHeaderModified(OutputStream outputStream, String path1, String path2, String copyFromPath) throws SVNException {
+ try {
+ displayString(outputStream, "diff --git ");
+ displayFirstGitPath(outputStream, path1);
+ displayString(outputStream, " ");
+ displaySecondGitPath(outputStream, path2);
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void displayFirstGitPath(OutputStream outputStream, String path1) throws IOException {
+ displayGitPath(outputStream, path1, "a/", false);
+ }
+
+ private void displaySecondGitPath(OutputStream outputStream, String path2) throws IOException {
+ displayGitPath(outputStream, path2, "b/", false);
+ }
+
+ private void displayFirstGitLabelPath(OutputStream outputStream, String path1, String revision1, SvnDiffCallback.OperationKind operation) throws IOException {
+ String pathPrefix = "a/";
+ if (operation == SvnDiffCallback.OperationKind.Added) {
+ path1 = "/dev/null";
+ pathPrefix = "";
+ }
+ displayGitPath(outputStream, getLabel(path1, revision1), pathPrefix, true);
+ }
+
+ private void displaySecondGitLabelPath(OutputStream outputStream, String path2, String revision2, SvnDiffCallback.OperationKind operation) throws IOException {
+ String pathPrefix = "b/";
+ if (operation == SvnDiffCallback.OperationKind.Deleted) {
+ path2 = "/dev/null";
+ pathPrefix = "";
+ }
+ displayGitPath(outputStream, getLabel(path2, revision2), pathPrefix, true);
+ }
+
+ private void displayGitPath(OutputStream outputStream, String path1, String pathPrefix, boolean label) throws IOException {
+// if (!label && path1.length() == 0) {
+// displayString(outputStream, ".");
+// } else {
+ displayString(outputStream, pathPrefix);
+ displayString(outputStream, path1);
+// }
+ }
+
+ private String getAdjustedPathWithLabel(String displayPath, String path, String revision, String commonAncestor) {
+ String adjustedPath = getAdjustedPath(displayPath, path, commonAncestor);
+ return getLabel(adjustedPath, revision);
+ }
+
+ private String getAdjustedPath(String displayPath, String path1, String commonAncestor) {
+ String adjustedPath = getRelativePath(path1, commonAncestor);
+
+ if (adjustedPath == null || adjustedPath.length() == 0) {
+ adjustedPath = displayPath;
+ } else if (adjustedPath.charAt(0) == '/') {
+ adjustedPath = displayPath + "\t(..." + adjustedPath + ")";
+ } else {
+ adjustedPath = displayPath + "\t(.../" + adjustedPath + ")";
+ }
+ return adjustedPath;
+ //TODO: respect relativeToDir
+ }
+
+ protected String getLabel(String path, String revToken) {
+ revToken = revToken == null ? WC_REVISION_LABEL : revToken;
+ return path + "\t" + revToken;
+ }
+
+ protected boolean displayHeader(OutputStream os, String path, boolean deleted, boolean added, SvnDiffCallback.OperationKind operation) throws SVNException {
+ try {
+ if (deleted && !isDiffDeleted()) {
+ displayString(os, "Index: ");
+ displayString(os, path);
+ displayString(os, " (deleted)");
+ displayEOL(os);
+ displayString(os, HEADER_SEPARATOR);
+ displayEOL(os);
+ return true;
+ }
+ if (added && !isDiffAdded()) {
+ displayString(os, "Index: ");
+ displayString(os, path);
+ displayString(os, " (added)");
+ displayEOL(os);
+ displayString(os, HEADER_SEPARATOR);
+ displayEOL(os);
+ return true;
+ }
+ displayString(os, "Index: ");
+ displayString(os, path);
+ displayEOL(os);
+ displayString(os, HEADER_SEPARATOR);
+ displayEOL(os);
+ return false;
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ return false;
+ }
+
+ protected void displayHeaderFields(OutputStream os, String label1, String label2) throws SVNException {
+ try {
+ displayString(os, "--- ");
+ displayString(os, label1);
+ displayEOL(os);
+ displayString(os, "+++ ");
+ displayString(os, label2);
+ displayEOL(os);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void displayPropertyChangesOn(String path, OutputStream outputStream) throws SVNException {
+ try {
+ displayEOL(outputStream);
+ displayString(outputStream, ("Property changes on: " + (useLocalFileSeparatorChar() ? path.replace('/', File.separatorChar) : path)));
+ displayEOL(outputStream);
+ displayString(outputStream, PROPERTIES_SEPARATOR);
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private byte[] getPropertyAsBytes(SVNPropertyValue value, String encoding) {
+ if (value == null) {
+ return null;
+ }
+ if (value.isString()) {
+ try {
+ return value.getString().getBytes(encoding);
+ } catch (UnsupportedEncodingException e) {
+ return value.getString().getBytes();
+ }
+ }
+ return value.getBytes();
+ }
+
+ private void displayMergeInfoDiff(OutputStream outputStream, String oldValue, String newValue) throws SVNException, IOException {
+ Map oldMergeInfo = null;
+ Map newMergeInfo = null;
+ if (oldValue != null) {
+ oldMergeInfo = SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(oldValue), null);
+ }
+ if (newValue != null) {
+ newMergeInfo = SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(newValue), null);
+ }
+
+ Map deleted = new TreeMap();
+ Map added = new TreeMap();
+ SVNMergeInfoUtil.diffMergeInfo(deleted, added, oldMergeInfo, newMergeInfo, true);
+
+ for (Iterator paths = deleted.keySet().iterator(); paths.hasNext(); ) {
+ String path = (String) paths.next();
+ SVNMergeRangeList rangeList = (SVNMergeRangeList) deleted.get(path);
+ displayString(outputStream, (" Reverse-merged " + path + ":r"));
+ displayString(outputStream, rangeList.toString());
+ displayEOL(outputStream);
+ }
+
+ for (Iterator paths = added.keySet().iterator(); paths.hasNext(); ) {
+ String path = (String) paths.next();
+ SVNMergeRangeList rangeList = (SVNMergeRangeList) added.get(path);
+ displayString(outputStream, (" Merged " + path + ":r"));
+ displayString(outputStream, rangeList.toString());
+ displayEOL(outputStream);
+ }
+ }
+
+ private boolean useLocalFileSeparatorChar() {
+ return true;
+ }
+
+ public boolean isDiffDeleted() {
+ return diffDeleted;
+ }
+
+ public boolean isDiffAdded() {
+ return diffAdded;
+ }
+
+ private void wrapException(IOException e) throws SVNException {
+ SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, e);
+ SVNErrorManager.error(errorMessage, e, SVNLogType.WC);
+ }
+
+ private void displayString(OutputStream outputStream, String s) throws IOException {
+ outputStream.write(s.getBytes(HEADER_ENCODING));
+ }
+
+ private void displayEOL(OutputStream os) throws IOException {
+ os.write(getEOL());
+ }
+
+ public SVNDiffOptions getDiffOptions() {
+ if (diffOptions == null) {
+ diffOptions = new SVNDiffOptions();
+ }
+ return diffOptions;
+ }
+
+ public void setExternalDiffCommand(String externalDiffCommand) {
+ this.externalDiffCommand = externalDiffCommand;
+ }
+
+ public void setRawDiffOptions(List rawDiffOptions) {
+ this.rawDiffOptions = rawDiffOptions;
+ }
+
+ public void setDiffOptions(SVNDiffOptions diffOptions) {
+ this.diffOptions = diffOptions;
+ }
+
+ public void setDiffDeleted(boolean diffDeleted) {
+ this.diffDeleted = diffDeleted;
+ }
+
+ public void setDiffAdded(boolean diffAdded) {
+ this.diffAdded = diffAdded;
+ }
+
+ public void setBasePath(File absoluteFile) {
+ setBaseTarget(SvnTarget.fromFile(absoluteFile));
+ }
+
+ public void setFallbackToAbsolutePath(boolean fallbackToAbsolutePath) {
+ this.fallbackToAbsolutePath = fallbackToAbsolutePath;
+ }
+
+ public void setOptions(ISVNOptions options) {
+ this.options = options;
+ }
+
+ public ISVNOptions getOptions() {
+ return options;
+ }
+
+ private class EmptyDetectionOutputStream extends OutputStream {
+
+ private final OutputStream outputStream;
+ private boolean somethingWritten;
+
+ public EmptyDetectionOutputStream(OutputStream outputStream) {
+ this.outputStream = outputStream;
+ this.somethingWritten = false;
+ }
+
+ public boolean isSomethingWritten() {
+ return somethingWritten;
+ }
+
+ @Override
+ public void write(int c) throws IOException {
+ somethingWritten = true;
+ outputStream.write(c);
+ }
+
+ @Override
+ public void write(byte[] bytes) throws IOException {
+ somethingWritten = bytes.length > 0;
+ outputStream.write(bytes);
+ }
+
+ @Override
+ public void write(byte[] bytes, int offset, int length) throws IOException {
+ somethingWritten = length > 0;
+ outputStream.write(bytes, offset, length);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ outputStream.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ outputStream.close();
+ }
+ }
+}
diff --git a/scm-it/src/test/resources/diff/largefile/modified/v2/SvnDiffGenerator_forTest b/scm-it/src/test/resources/diff/largefile/modified/v2/SvnDiffGenerator_forTest
new file mode 100644
index 0000000000..c46ce5f534
--- /dev/null
+++ b/scm-it/src/test/resources/diff/largefile/modified/v2/SvnDiffGenerator_forTest
@@ -0,0 +1,1234 @@
+package sonia.scm.repository.spi;
+
+import de.regnis.q.sequence.line.diff.QDiffGenerator;
+import de.regnis.q.sequence.line.diff.QDiffGeneratorFactory;
+import de.regnis.q.sequence.line.diff.QDiffManager;
+import de.regnis.q.sequence.line.diff.QDiffUniGenerator;
+import org.tmatesoft.svn.core.SVNErrorCode;
+import org.tmatesoft.svn.core.SVNErrorMessage;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNMergeRangeList;
+import org.tmatesoft.svn.core.SVNProperties;
+import org.tmatesoft.svn.core.SVNProperty;
+import org.tmatesoft.svn.core.SVNPropertyValue;
+import org.tmatesoft.svn.core.internal.util.SVNHashMap;
+import org.tmatesoft.svn.core.internal.util.SVNMergeInfoUtil;
+import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
+import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions;
+import org.tmatesoft.svn.core.internal.wc.ISVNReturnValueCallback;
+import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
+import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
+import org.tmatesoft.svn.core.internal.wc2.ng.ISvnDiffGenerator;
+import org.tmatesoft.svn.core.internal.wc2.ng.SvnDiffCallback;
+import org.tmatesoft.svn.core.wc.ISVNOptions;
+import org.tmatesoft.svn.core.wc.SVNDiffOptions;
+import org.tmatesoft.svn.core.wc2.SvnTarget;
+import org.tmatesoft.svn.util.SVNLogType;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.RandomAccessFile;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+public class SCMSvnDiffGenerator implements ISvnDiffGenerator {
+
+ protected static final String WC_REVISION_LABEL = "(working copy)";
+ protected static final String PROPERTIES_SEPARATOR = "___________________________________________________________________";
+ protected static final String HEADER_SEPARATOR = "===================================================================";
+ protected static final String HEADER_ENCODING = "UTF-8";
+
+ private SvnTarget originalTarget1;
+ private SvnTarget originalTarget2;
+ private SvnTarget baseTarget;
+ private SvnTarget relativeToTarget;
+ private SvnTarget repositoryRoot;
+ private String encoding;
+ private byte[] eol;
+ private boolean useGitFormat;
+ private boolean forcedBinaryDiff;
+
+ private boolean diffDeleted;
+ private boolean diffAdded;
+ private List rawDiffOptions;
+ private boolean forceEmpty;
+
+ private Set visitedPaths;
+ private String externalDiffCommand;
+ private SVNDiffOptions diffOptions;
+ private boolean fallbackToAbsolutePath;
+ private ISVNOptions options;
+ private boolean propertiesOnly;
+ private boolean ignoreProperties;
+
+ private String getDisplayPath(SvnTarget target) {
+ String relativePath;
+ if (baseTarget == null) {
+ relativePath = null;
+ } else {
+ String targetString = target.getPathOrUrlDecodedString();
+ String baseTargetString = baseTarget.getPathOrUrlDecodedString();
+ relativePath = getRelativePath(targetString, baseTargetString);
+ }
+
+ return relativePath != null ? relativePath : target.getPathOrUrlString();
+ }
+
+ private String getRelativeToRootPath(SvnTarget target, SvnTarget originalTarget) {
+ String relativePath;
+ if (repositoryRoot == null) {
+ relativePath = null;
+ } else {
+ if (repositoryRoot.isFile() == target.isFile()) {
+ String targetString = target.getPathOrUrlDecodedString();
+ String baseTargetString = repositoryRoot.getPathOrUrlDecodedString();
+ relativePath = getRelativePath(targetString, baseTargetString);
+ } else {
+ String targetString = target.getPathOrUrlDecodedString();
+ String baseTargetString = new File("").getAbsolutePath();
+ relativePath = getRelativePath(targetString, baseTargetString);
+ }
+ }
+
+ return relativePath != null ? relativePath : target.getPathOrUrlString();
+ }
+
+ private String getRelativePath(String targetString, String baseTargetString) {
+ if (targetString != null) {
+ targetString = targetString.replace(File.separatorChar, '/');
+ }
+ if (baseTargetString != null) {
+ baseTargetString = baseTargetString.replace(File.separatorChar, '/');
+ }
+
+ final String pathAsChild = SVNPathUtil.getPathAsChild(baseTargetString, targetString);
+ if (pathAsChild != null) {
+ return pathAsChild;
+ }
+ if (targetString.equals(baseTargetString)) {
+ return "";
+ }
+ return null;
+ }
+
+ private String getChildPath(String path, String relativeToPath) {
+ if (relativeToTarget == null) {
+ return null;
+ }
+
+ String relativePath = getRelativePath(path, relativeToPath);
+ if (relativePath == null) {
+ return path;
+ }
+
+ if (relativePath.length() > 0) {
+ return relativePath;
+ }
+
+ if (relativeToPath.equals(path)) {
+ return ".";
+ }
+
+ return null;
+ }
+
+ public SCMSvnDiffGenerator() {
+ this.originalTarget1 = null;
+ this.originalTarget2 = null;
+ this.visitedPaths = new HashSet();
+ this.diffDeleted = true;
+ this.diffAdded = true;
+ }
+
+ public void setBaseTarget(SvnTarget baseTarget) {
+ this.baseTarget = baseTarget;
+ }
+
+ public void setUseGitFormat(boolean useGitFormat) {
+ this.useGitFormat = useGitFormat;
+ }
+
+ public void setOriginalTargets(SvnTarget originalTarget1, SvnTarget originalTarget2) {
+ this.originalTarget1 = originalTarget1;
+ this.originalTarget2 = originalTarget2;
+ }
+
+ public void setRelativeToTarget(SvnTarget relativeToTarget) {
+ this.relativeToTarget = relativeToTarget;
+ }
+
+ public void setAnchors(SvnTarget originalTarget1, SvnTarget originalTarget2) {
+ //anchors are not used
+ }
+
+ public void setRepositoryRoot(SvnTarget repositoryRoot) {
+ this.repositoryRoot = repositoryRoot;
+ }
+
+ public void setForceEmpty(boolean forceEmpty) {
+ this.forceEmpty = forceEmpty;
+ }
+
+ public void setEncoding(String encoding) {
+ this.encoding = encoding;
+ }
+
+ public String getEncoding() {
+ return encoding;
+ }
+
+ public String getGlobalEncoding() {
+ ISVNOptions options = getOptions();
+
+ if (options != null && options instanceof DefaultSVNOptions) {
+ DefaultSVNOptions defaultOptions = (DefaultSVNOptions) options;
+ return defaultOptions.getGlobalCharset();
+ }
+ return null;
+ }
+
+ public void setEOL(byte[] eol) {
+ this.eol = eol;
+ }
+
+ public byte[] getEOL() {
+ return eol;
+ }
+
+ public boolean isForcedBinaryDiff() {
+ return forcedBinaryDiff;
+ }
+
+ public void setForcedBinaryDiff(boolean forcedBinaryDiff) {
+ this.forcedBinaryDiff = forcedBinaryDiff;
+ }
+
+ public boolean isPropertiesOnly() {
+ return propertiesOnly;
+ }
+
+ public void setPropertiesOnly(boolean propertiesOnly) {
+ this.propertiesOnly = propertiesOnly;
+ }
+
+ public boolean isIgnoreProperties() {
+ return ignoreProperties;
+ }
+
+ public void setIgnoreProperties(boolean ignoreProperties) {
+ this.ignoreProperties = ignoreProperties;
+ }
+
+ public void displayDeletedDirectory(SvnTarget target, String revision1, String revision2, OutputStream outputStream) throws SVNException {
+ }
+
+ public void displayAddedDirectory(SvnTarget target, String revision1, String revision2, OutputStream outputStream) throws SVNException {
+ }
+
+ public void displayPropsChanged(SvnTarget target, String revision1, String revision2, boolean dirWasAdded, SVNProperties originalProps, SVNProperties propChanges, OutputStream outputStream) throws SVNException {
+ if (isIgnoreProperties()) {
+ return;
+ }
+ if (dirWasAdded && !isDiffAdded()) {
+ return;
+ }
+ ensureEncodingAndEOLSet();
+ String displayPath = getDisplayPath(target);
+
+ String targetString1 = originalTarget1.getPathOrUrlDecodedString();
+ String targetString2 = originalTarget2.getPathOrUrlDecodedString();
+
+ if (displayPath == null || displayPath.length() == 0) {
+ displayPath = ".";
+ }
+
+ if (useGitFormat) {
+ targetString1 = adjustRelativeToReposRoot(targetString1);
+ targetString2 = adjustRelativeToReposRoot(targetString2);
+ }
+
+ String newTargetString = displayPath;
+ String newTargetString1 = targetString1;
+ String newTargetString2 = targetString2;
+
+ String commonAncestor = SVNPathUtil.getCommonPathAncestor(newTargetString1, newTargetString2);
+ int commonLength = commonAncestor == null ? 0 : commonAncestor.length();
+
+ newTargetString1 = newTargetString1.substring(commonLength);
+ newTargetString2 = newTargetString2.substring(commonLength);
+
+ newTargetString1 = computeLabel(newTargetString, newTargetString1);
+ newTargetString2 = computeLabel(newTargetString, newTargetString2);
+
+ if (relativeToTarget != null) {
+ String relativeToPath = relativeToTarget.getPathOrUrlDecodedString();
+ String absolutePath = target.getPathOrUrlDecodedString();
+
+ String childPath = getChildPath(absolutePath, relativeToPath);
+ if (childPath == null) {
+ throwBadRelativePathException(absolutePath, relativeToPath);
+ }
+ String childPath1 = getChildPath(newTargetString1, relativeToPath);
+ if (childPath1 == null) {
+ throwBadRelativePathException(newTargetString1, relativeToPath);
+ }
+ String childPath2 = getChildPath(newTargetString2, relativeToPath);
+ if (childPath2 == null) {
+ throwBadRelativePathException(newTargetString2, relativeToPath);
+ }
+
+ displayPath = childPath;
+ newTargetString1 = childPath1;
+ newTargetString2 = childPath2;
+ }
+
+ boolean showDiffHeader = !visitedPaths.contains(displayPath);
+ if (showDiffHeader) {
+ String label1 = getLabel(newTargetString1, revision1);
+ String label2 = getLabel(newTargetString2, revision2);
+
+ boolean shouldStopDisplaying = displayHeader(outputStream, displayPath, false, fallbackToAbsolutePath, SvnDiffCallback.OperationKind.Modified);
+ visitedPaths.add(displayPath);
+ if (useGitFormat) {
+ displayGitDiffHeader(outputStream, SvnDiffCallback.OperationKind.Modified,
+ getRelativeToRootPath(target, originalTarget1),
+ getRelativeToRootPath(target, originalTarget2),
+ null);
+ }
+ if (shouldStopDisplaying) {
+ return;
+ }
+
+// if (useGitFormat) {
+// String copyFromPath = null;
+// SvnDiffCallback.OperationKind operationKind = SvnDiffCallback.OperationKind.Modified;
+// label1 = getGitDiffLabel1(operationKind, targetString1, targetString2, copyFromPath, revision1);
+// label2 = getGitDiffLabel2(operationKind, targetString1, targetString2, copyFromPath, revision2);
+// displayGitDiffHeader(outputStream, operationKind,
+// getRelativeToRootPath(target, originalTarget1),
+// getRelativeToRootPath(target, originalTarget2),
+// copyFromPath);
+// }
+
+ if (useGitFormat) {
+ displayGitHeaderFields(outputStream, target, revision1, revision2, SvnDiffCallback.OperationKind.Modified, null);
+ } else {
+ displayHeaderFields(outputStream, label1, label2);
+ }
+ }
+
+ displayPropertyChangesOn(useGitFormat ? getRelativeToRootPath(target, originalTarget1) : displayPath, outputStream);
+
+ displayPropDiffValues(outputStream, propChanges, originalProps);
+ }
+
+ private void throwBadRelativePathException(String displayPath, String relativeToPath) throws SVNException {
+ SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.BAD_RELATIVE_PATH, "Path ''{0}'' must be an immediate child of the directory ''{0}''",
+ displayPath, relativeToPath);
+ SVNErrorManager.error(errorMessage, SVNLogType.CLIENT);
+ }
+
+ private void displayGitHeaderFields(OutputStream outputStream, SvnTarget target, String revision1, String revision2, SvnDiffCallback.OperationKind operation, String copyFromPath) throws SVNException {
+ String path1 = copyFromPath != null ? copyFromPath : getRelativeToRootPath(target, originalTarget1);
+ String path2 = getRelativeToRootPath(target, originalTarget2);
+
+ try {
+ displayString(outputStream, "--- ");
+ displayFirstGitLabelPath(outputStream, path1, revision1, operation);
+ displayEOL(outputStream);
+ displayString(outputStream, "+++ ");
+ displaySecondGitLabelPath(outputStream, path2, revision2, operation);
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private String adjustRelativeToReposRoot(String targetString) {
+ if (repositoryRoot != null) {
+ String repositoryRootString = repositoryRoot.getPathOrUrlDecodedString();
+ String relativePath = getRelativePath(targetString, repositoryRootString);
+ return relativePath == null ? "" : relativePath;
+ }
+ return targetString;
+ }
+
+ private String computeLabel(String targetString, String originalTargetString) {
+ if (originalTargetString.length() == 0) {
+ return targetString;
+ } else if (originalTargetString.charAt(0) == '/') {
+ return targetString + "\t(..." + originalTargetString + ")";
+ } else {
+ return targetString + "\t(.../" + originalTargetString + ")";
+ }
+ }
+
+ public void displayContentChanged(SvnTarget target, File leftFile, File rightFile, String revision1, String revision2, String mimeType1, String mimeType2, SvnDiffCallback.OperationKind operation, File copyFromPath, SVNProperties originalProperties, SVNProperties propChanges, OutputStream outputStream) throws SVNException {
+ if (isPropertiesOnly()) {
+ return;
+ }
+ ensureEncodingAndEOLSet();
+ String displayPath = getDisplayPath(target);
+
+ String targetString1 = originalTarget1.getPathOrUrlDecodedString();
+ String targetString2 = originalTarget2.getPathOrUrlDecodedString();
+
+ if (useGitFormat) {
+ targetString1 = adjustRelativeToReposRoot(targetString1);
+ targetString2 = adjustRelativeToReposRoot(targetString2);
+ }
+
+ String newTargetString = displayPath;
+ String newTargetString1 = targetString1;
+ String newTargetString2 = targetString2;
+
+ String commonAncestor = SVNPathUtil.getCommonPathAncestor(newTargetString1, newTargetString2);
+ int commonLength = commonAncestor == null ? 0 : commonAncestor.length();
+
+ newTargetString1 = newTargetString1.substring(commonLength);
+ newTargetString2 = newTargetString2.substring(commonLength);
+
+ newTargetString1 = computeLabel(newTargetString, newTargetString1);
+ newTargetString2 = computeLabel(newTargetString, newTargetString2);
+
+ if (relativeToTarget != null) {
+ String relativeToPath = relativeToTarget.getPathOrUrlDecodedString();
+ String absolutePath = target.getPathOrUrlDecodedString();
+
+ String childPath = getChildPath(absolutePath, relativeToPath);
+ if (childPath == null) {
+ throwBadRelativePathException(absolutePath, relativeToPath);
+ }
+ String childPath1 = getChildPath(newTargetString1, relativeToPath);
+ if (childPath1 == null) {
+ throwBadRelativePathException(newTargetString1, relativeToPath);
+ }
+ String childPath2 = getChildPath(newTargetString2, relativeToPath);
+ if (childPath2 == null) {
+ throwBadRelativePathException(newTargetString2, relativeToPath);
+ }
+
+ displayPath = childPath;
+ newTargetString1 = childPath1;
+ newTargetString2 = childPath2;
+ }
+
+ String label1 = getLabel(newTargetString1, revision1);
+ String label2 = getLabel(newTargetString2, revision2);
+
+ boolean leftIsBinary = false;
+ boolean rightIsBinary = false;
+
+ if (mimeType1 != null) {
+ leftIsBinary = SVNProperty.isBinaryMimeType(mimeType1);
+ }
+ if (mimeType2 != null) {
+ rightIsBinary = SVNProperty.isBinaryMimeType(mimeType2);
+ }
+
+ if (!forcedBinaryDiff && (leftIsBinary || rightIsBinary)) {
+ boolean shouldStopDisplaying = displayHeader(outputStream, displayPath, rightFile == null, leftFile == null, operation);
+ if (useGitFormat) {
+ displayGitDiffHeader(outputStream, operation,
+ getRelativeToRootPath(target, originalTarget1),
+ getRelativeToRootPath(target, originalTarget2),
+ null);
+ }
+ visitedPaths.add(displayPath);
+ if (shouldStopDisplaying) {
+ return;
+ }
+
+
+ displayBinary(mimeType1, mimeType2, outputStream, leftIsBinary, rightIsBinary);
+
+ return;
+ }
+
+ final String diffCommand = getExternalDiffCommand();
+ if (diffCommand != null) {
+ boolean shouldStopDisplaying = displayHeader(outputStream, displayPath, rightFile == null, leftFile == null, operation);
+ if (useGitFormat) {
+ displayGitDiffHeader(outputStream, operation,
+ getRelativeToRootPath(target, originalTarget1),
+ getRelativeToRootPath(target, originalTarget2),
+ null);
+ }
+ visitedPaths.add(displayPath);
+ if (shouldStopDisplaying) {
+ return;
+ }
+
+ runExternalDiffCommand(outputStream, diffCommand, leftFile, rightFile, label1, label2);
+ } else {
+ internalDiff(target, outputStream, displayPath, leftFile, rightFile, label1, label2, operation, copyFromPath == null ? null : copyFromPath.getPath(), revision1, revision2);
+ }
+ }
+
+ private void displayBinary(String mimeType1, String mimeType2, OutputStream outputStream, boolean leftIsBinary, boolean rightIsBinary) throws SVNException {
+ displayCannotDisplayFileMarkedBinary(outputStream);
+
+ if (leftIsBinary && !rightIsBinary) {
+ displayMimeType(outputStream, mimeType1);
+ } else if (!leftIsBinary && rightIsBinary) {
+ displayMimeType(outputStream, mimeType2);
+ } else if (leftIsBinary && rightIsBinary) {
+ if (mimeType1.equals(mimeType2)) {
+ displayMimeType(outputStream, mimeType1);
+ } else {
+ displayMimeTypes(outputStream, mimeType1, mimeType2);
+ }
+ }
+ }
+
+ private void internalDiff(SvnTarget target, OutputStream outputStream, String displayPath, File file1, File file2, String label1, String label2, SvnDiffCallback.OperationKind operation, String copyFromPath, String revision1, String revision2) throws SVNException {
+ String header = getHeaderString(target, displayPath, file2 == null, file1 == null, operation, copyFromPath);
+ if (file2 == null && !isDiffDeleted()) {
+ try {
+ displayString(outputStream, header);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ visitedPaths.add(displayPath);
+ return;
+ }
+ if (file1 == null && !isDiffAdded()) {
+ try {
+ displayString(outputStream, header);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ visitedPaths.add(displayPath);
+ return;
+ }
+ String headerFields = getHeaderFieldsString(target, displayPath, label1, label2, revision1, revision2, operation, copyFromPath);
+
+ RandomAccessFile is1 = null;
+ RandomAccessFile is2 = null;
+ try {
+ is1 = file1 == null ? null : SVNFileUtil.openRAFileForReading(file1);
+ is2 = file2 == null ? null : SVNFileUtil.openRAFileForReading(file2);
+
+ QDiffUniGenerator.setup();
+ Map properties = new SVNHashMap();
+
+ properties.put(QDiffGeneratorFactory.IGNORE_EOL_PROPERTY, Boolean.valueOf(getDiffOptions().isIgnoreEOLStyle()));
+ properties.put(QDiffGeneratorFactory.EOL_PROPERTY, new String(getEOL()));
+ if (getDiffOptions().isIgnoreAllWhitespace()) {
+ properties.put(QDiffGeneratorFactory.IGNORE_SPACE_PROPERTY, QDiffGeneratorFactory.IGNORE_ALL_SPACE);
+ } else if (getDiffOptions().isIgnoreAmountOfWhitespace()) {
+ properties.put(QDiffGeneratorFactory.IGNORE_SPACE_PROPERTY, QDiffGeneratorFactory.IGNORE_SPACE_CHANGE);
+ }
+
+ final String diffHeader;
+ if (forceEmpty || useGitFormat) {
+ displayString(outputStream, header);
+ diffHeader = headerFields;
+
+ visitedPaths.add(displayPath);
+ } else {
+ diffHeader = header + headerFields;
+ }
+ QDiffGenerator generator = new QDiffUniGenerator(properties, diffHeader);
+ EmptyDetectionOutputStream emptyDetectionOutputStream = new EmptyDetectionOutputStream(outputStream);
+ QDiffManager.generateTextDiff(is1, is2, emptyDetectionOutputStream, generator);
+ if (emptyDetectionOutputStream.isSomethingWritten()) {
+ visitedPaths.add(displayPath);
+ }
+ emptyDetectionOutputStream.flush();
+ } catch (IOException e) {
+ SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getMessage());
+ SVNErrorManager.error(err, e, SVNLogType.DEFAULT);
+ } finally {
+ SVNFileUtil.closeFile(is1);
+ SVNFileUtil.closeFile(is2);
+ }
+ }
+
+ private String getHeaderFieldsString(SvnTarget target, String displayPath, String label1, String label2, String revision1, String revision2, SvnDiffCallback.OperationKind operation, String copyFromPath) throws SVNException {
+ final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ try {
+ if (useGitFormat) {
+ displayGitHeaderFields(byteArrayOutputStream, target, revision1, revision2, operation, copyFromPath);
+ } else {
+ displayHeaderFields(byteArrayOutputStream, label1, label2);
+ }
+ } catch (SVNException e) {
+ SVNFileUtil.closeFile(byteArrayOutputStream);
+
+ try {
+ byteArrayOutputStream.writeTo(byteArrayOutputStream);
+ } catch (IOException e1) {
+ }
+
+ throw e;
+ }
+
+ try {
+ byteArrayOutputStream.close();
+ return byteArrayOutputStream.toString(HEADER_ENCODING);
+ } catch (IOException e) {
+ return "";
+ }
+ }
+
+ private String getHeaderString(SvnTarget target, String displayPath, boolean deleted, boolean added, SvnDiffCallback.OperationKind operation, String copyFromPath) throws SVNException {
+ final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ try {
+ if (!useGitFormat) {
+ displayHeader(byteArrayOutputStream, displayPath, deleted, added, operation);
+ } else {
+ displayGitDiffHeader(byteArrayOutputStream, operation,
+ getRelativeToRootPath(target, originalTarget1),
+ getRelativeToRootPath(target, originalTarget2),
+ copyFromPath);
+ }
+ } catch (SVNException e) {
+ SVNFileUtil.closeFile(byteArrayOutputStream);
+
+ try {
+ byteArrayOutputStream.writeTo(byteArrayOutputStream);
+ } catch (IOException e1) {
+ }
+
+ throw e;
+ }
+
+ try {
+ byteArrayOutputStream.close();
+ return byteArrayOutputStream.toString(HEADER_ENCODING);
+ } catch (IOException e) {
+ return "";
+ }
+ }
+
+ private void runExternalDiffCommand(OutputStream outputStream, final String diffCommand, File file1, File file2, String label1, String label2) throws SVNException {
+ final List args = new ArrayList();
+ args.add(diffCommand);
+ if (rawDiffOptions != null) {
+ args.addAll(rawDiffOptions);
+ } else {
+ Collection svnDiffOptionsCollection = getDiffOptions().toOptionsCollection();
+ args.addAll(svnDiffOptionsCollection);
+ args.add("-u");
+ }
+
+ if (label1 != null) {
+ args.add("-L");
+ args.add(label1);
+ }
+
+ if (label2 != null) {
+ args.add("-L");
+ args.add(label2);
+ }
+
+ boolean tmpFile1 = false;
+ boolean tmpFile2 = false;
+ if (file1 == null) {
+ file1 = SVNFileUtil.createTempFile("svn.", ".tmp");
+ tmpFile1 = true;
+ }
+ if (file2 == null) {
+ file2 = SVNFileUtil.createTempFile("svn.", ".tmp");
+ tmpFile2 = true;
+ }
+
+ String file1Path = file1.getAbsolutePath().replace(File.separatorChar, '/');
+ String file2Path = file2.getAbsolutePath().replace(File.separatorChar, '/');
+
+ args.add(file1Path);
+ args.add(file2Path);
+ try {
+ final Writer writer = new OutputStreamWriter(outputStream, getEncoding());
+
+ SVNFileUtil.execCommand(args.toArray(new String[args.size()]), true,
+ new ISVNReturnValueCallback() {
+
+ public void handleReturnValue(int returnValue) throws SVNException {
+ if (returnValue != 0 && returnValue != 1) {
+ SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.EXTERNAL_PROGRAM,
+ "''{0}'' returned {1}", new Object[]{diffCommand, String.valueOf(returnValue)});
+ SVNErrorManager.error(err, SVNLogType.DEFAULT);
+ }
+ }
+
+ public void handleChar(char ch) throws SVNException {
+ try {
+ writer.write(ch);
+ } catch (IOException ioe) {
+ SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getMessage());
+ SVNErrorManager.error(err, ioe, SVNLogType.DEFAULT);
+ }
+ }
+
+ public boolean isHandleProgramOutput() {
+ return true;
+ }
+ });
+
+ writer.flush();
+ } catch (IOException ioe) {
+ SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getMessage());
+ SVNErrorManager.error(err, ioe, SVNLogType.DEFAULT);
+ } finally {
+ try {
+ if (tmpFile1) {
+ SVNFileUtil.deleteFile(file1);
+ }
+ if (tmpFile2) {
+ SVNFileUtil.deleteFile(file2);
+ }
+ } catch (SVNException e) {
+ // skip
+ }
+ }
+ }
+
+ private String getExternalDiffCommand() {
+ return externalDiffCommand;
+ }
+
+ private void displayMimeType(OutputStream outputStream, String mimeType) throws SVNException {
+ try {
+ displayString(outputStream, SVNProperty.MIME_TYPE);
+ displayString(outputStream, " = ");
+ displayString(outputStream, mimeType);
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void displayMimeTypes(OutputStream outputStream, String mimeType1, String mimeType2) throws SVNException {
+ try {
+ displayString(outputStream, SVNProperty.MIME_TYPE);
+ displayString(outputStream, " = (");
+ displayString(outputStream, mimeType1);
+ displayString(outputStream, ", ");
+ displayString(outputStream, mimeType2);
+ displayString(outputStream, ")");
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void displayCannotDisplayFileMarkedBinary(OutputStream outputStream) throws SVNException {
+ try {
+ displayString(outputStream, "Cannot display: file marked as a binary type.");
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void ensureEncodingAndEOLSet() {
+ if (getEOL() == null) {
+ setEOL(SVNProperty.EOL_LF_BYTES);
+ }
+ if (getEncoding() == null) {
+ final ISVNOptions options = getOptions();
+ if (options != null && options.getNativeCharset() != null) {
+ setEncoding(options.getNativeCharset());
+ } else {
+ setEncoding("UTF-8");
+ }
+ }
+ }
+
+ private void displayPropDiffValues(OutputStream outputStream, SVNProperties diff, SVNProperties baseProps) throws SVNException {
+ for (Iterator changedPropNames = diff.nameSet().iterator(); changedPropNames.hasNext(); ) {
+ String name = (String) changedPropNames.next();
+ SVNPropertyValue originalValue = baseProps != null ? baseProps.getSVNPropertyValue(name) : null;
+ SVNPropertyValue newValue = diff.getSVNPropertyValue(name);
+ String headerFormat = null;
+
+ if (originalValue == null) {
+ headerFormat = "Added: ";
+ } else if (newValue == null) {
+ headerFormat = "Deleted: ";
+ } else {
+ headerFormat = "Modified: ";
+ }
+
+ try {
+ displayString(outputStream, (headerFormat + name));
+ displayEOL(outputStream);
+ if (SVNProperty.MERGE_INFO.equals(name)) {
+ displayMergeInfoDiff(outputStream, originalValue == null ? null : originalValue.getString(), newValue == null ? null : newValue.getString());
+ continue;
+ }
+
+ byte[] originalValueBytes = getPropertyAsBytes(originalValue, getEncoding());
+ byte[] newValueBytes = getPropertyAsBytes(newValue, getEncoding());
+
+ if (originalValueBytes == null) {
+ originalValueBytes = new byte[0];
+ } else {
+ originalValueBytes = maybeAppendEOL(originalValueBytes);
+ }
+
+ boolean newValueHadEol = newValueBytes != null && newValueBytes.length > 0 &&
+ (newValueBytes[newValueBytes.length - 1] == SVNProperty.EOL_CR_BYTES[0] ||
+ newValueBytes[newValueBytes.length - 1] == SVNProperty.EOL_LF_BYTES[0]);
+
+ if (newValueBytes == null) {
+ newValueBytes = new byte[0];
+ } else {
+ newValueBytes = maybeAppendEOL(newValueBytes);
+ }
+
+ QDiffUniGenerator.setup();
+ Map properties = new SVNHashMap();
+
+ properties.put(QDiffGeneratorFactory.IGNORE_EOL_PROPERTY, Boolean.valueOf(getDiffOptions().isIgnoreEOLStyle()));
+ properties.put(QDiffGeneratorFactory.EOL_PROPERTY, new String(getEOL()));
+ properties.put(QDiffGeneratorFactory.HUNK_DELIMITER, "##");
+ if (getDiffOptions().isIgnoreAllWhitespace()) {
+ properties.put(QDiffGeneratorFactory.IGNORE_SPACE_PROPERTY, QDiffGeneratorFactory.IGNORE_ALL_SPACE);
+ } else if (getDiffOptions().isIgnoreAmountOfWhitespace()) {
+ properties.put(QDiffGeneratorFactory.IGNORE_SPACE_PROPERTY, QDiffGeneratorFactory.IGNORE_SPACE_CHANGE);
+ }
+
+ QDiffGenerator generator = new QDiffUniGenerator(properties, "");
+ Writer writer = new OutputStreamWriter(outputStream, getEncoding());
+ QDiffManager.generateTextDiff(new ByteArrayInputStream(originalValueBytes), new ByteArrayInputStream(newValueBytes),
+ null, writer, generator);
+ writer.flush();
+ if (!newValueHadEol) {
+ displayString(outputStream, "\\ No newline at end of property");
+ displayEOL(outputStream);
+ }
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ }
+
+ private byte[] maybeAppendEOL(byte[] buffer) {
+ if (buffer.length == 0) {
+ return buffer;
+ }
+
+ byte lastByte = buffer[buffer.length - 1];
+ if (lastByte == SVNProperty.EOL_CR_BYTES[0]) {
+ return buffer;
+ } else if (lastByte != SVNProperty.EOL_LF_BYTES[0]) {
+ final byte[] newBuffer = new byte[buffer.length + getEOL().length];
+ System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
+ System.arraycopy(getEOL(), 0, newBuffer, buffer.length, getEOL().length);
+ return newBuffer;
+ } else {
+ return buffer;
+ }
+ }
+
+ private String getGitDiffLabel1(SvnDiffCallback.OperationKind operationKind, String path1, String path2, String copyFromPath, String revision) {
+ if (operationKind == SvnDiffCallback.OperationKind.Deleted) {
+ return getLabel("a/" + path1, revision);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Copied) {
+ return getLabel("a/" + copyFromPath, revision);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Added) {
+ return getLabel("/dev/null", revision);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Modified) {
+ return getLabel("a/" + path1, revision);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Moved) {
+ return getLabel("a/" + copyFromPath, revision);
+ }
+ throw new IllegalArgumentException("Unsupported operation: " + operationKind);
+ }
+
+ private String getGitDiffLabel2(SvnDiffCallback.OperationKind operationKind, String path1, String path2, String copyFromPath, String revision) {
+ if (operationKind == SvnDiffCallback.OperationKind.Deleted) {
+ return getLabel("/dev/null", revision);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Copied) {
+ return getLabel("b/" + path2, revision);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Added) {
+ return getLabel("b/" + path2, revision);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Modified) {
+ return getLabel("b/" + path2, revision);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Moved) {
+ return getLabel("b/" + path2, revision);
+ }
+ throw new IllegalArgumentException("Unsupported operation: " + operationKind);
+ }
+
+ private void displayGitDiffHeader(OutputStream outputStream, SvnDiffCallback.OperationKind operationKind, String path1, String path2, String copyFromPath) throws SVNException {
+ if (operationKind == SvnDiffCallback.OperationKind.Deleted) {
+ displayGitDiffHeaderDeleted(outputStream, path1, path2, copyFromPath);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Copied) {
+ displayGitDiffHeaderCopied(outputStream, path1, path2, copyFromPath);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Added) {
+ displayGitDiffHeaderAdded(outputStream, path1, path2, copyFromPath);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Modified) {
+ displayGitDiffHeaderModified(outputStream, path1, path2, copyFromPath);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Moved) {
+ displayGitDiffHeaderRenamed(outputStream, path1, path2, copyFromPath);
+ }
+ }
+
+ private void displayGitDiffHeaderAdded(OutputStream outputStream, String path1, String path2, String copyFromPath) throws SVNException {
+ try {
+ displayString(outputStream, "diff --git ");
+ displayFirstGitPath(outputStream, path1);
+ displayString(outputStream, " ");
+ displaySecondGitPath(outputStream, path2);
+ displayEOL(outputStream);
+ displayString(outputStream, "new file mode 100644");
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void displayGitDiffHeaderDeleted(OutputStream outputStream, String path1, String path2, String copyFromPath) throws SVNException {
+ try {
+ displayString(outputStream, "diff --git ");
+ displayFirstGitPath(outputStream, path1);
+ displayString(outputStream, " ");
+ displaySecondGitPath(outputStream, path2);
+ displayEOL(outputStream);
+ displayString(outputStream, "deleted file mode 100644");
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void displayGitDiffHeaderCopied(OutputStream outputStream, String path1, String path2, String copyFromPath) throws SVNException {
+ try {
+ displayString(outputStream, "diff --git ");
+ displayFirstGitPath(outputStream, copyFromPath);
+ displayString(outputStream, " ");
+ displaySecondGitPath(outputStream, path2);
+ displayEOL(outputStream);
+ displayString(outputStream, "copy from ");
+ displayString(outputStream, copyFromPath);
+ displayEOL(outputStream);
+ displayString(outputStream, "copy to ");
+ displayString(outputStream, path2);
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void displayGitDiffHeaderRenamed(OutputStream outputStream, String path1, String path2, String copyFromPath) throws SVNException {
+ try {
+ displayString(outputStream, "diff --git ");
+ displayFirstGitPath(outputStream, copyFromPath);
+ displayString(outputStream, " ");
+ displaySecondGitPath(outputStream, path2);
+ displayEOL(outputStream);
+ displayString(outputStream, "rename from ");
+ displayString(outputStream, copyFromPath);
+ displayEOL(outputStream);
+ displayString(outputStream, "rename to ");
+ displayString(outputStream, path2);
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void displayGitDiffHeaderModified(OutputStream outputStream, String path1, String path2, String copyFromPath) throws SVNException {
+ try {
+ displayString(outputStream, "diff --git ");
+ displayFirstGitPath(outputStream, path1);
+ displayString(outputStream, " ");
+ displaySecondGitPath(outputStream, path2);
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void displayFirstGitPath(OutputStream outputStream, String path1) throws IOException {
+ displayGitPath(outputStream, path1, "a/", false);
+ }
+
+ private void displaySecondGitPath(OutputStream outputStream, String path2) throws IOException {
+ displayGitPath(outputStream, path2, "b/", false);
+ }
+
+ private void displayFirstGitLabelPath(OutputStream outputStream, String path1, String revision1, SvnDiffCallback.OperationKind operation) throws IOException {
+ String pathPrefix = "a/";
+ if (operation == SvnDiffCallback.OperationKind.Added) {
+ path1 = "/dev/null";
+ pathPrefix = "";
+ }
+ displayGitPath(outputStream, getLabel(path1, revision1), pathPrefix, true);
+ }
+
+ private void displaySecondGitLabelPath(OutputStream outputStream, String path2, String revision2, SvnDiffCallback.OperationKind operation) throws IOException {
+ String pathPrefix = "b/";
+ if (operation == SvnDiffCallback.OperationKind.Deleted) {
+ path2 = "/dev/null";
+ pathPrefix = "";
+ }
+ displayGitPath(outputStream, getLabel(path2, revision2), pathPrefix, true);
+ }
+
+ private void displayGitPath(OutputStream outputStream, String path1, String pathPrefix, boolean label) throws IOException {
+// if (!label && path1.length() == 0) {
+// displayString(outputStream, ".");
+// } else {
+ displayString(outputStream, pathPrefix);
+ displayString(outputStream, path1);
+// }
+ }
+
+ private String getAdjustedPathWithLabel(String displayPath, String path, String revision, String commonAncestor) {
+ String adjustedPath = getAdjustedPath(displayPath, path, commonAncestor);
+ return getLabel(adjustedPath, revision);
+ }
+
+ private String getAdjustedPath(String displayPath, String path1, String commonAncestor) {
+ String adjustedPath = getRelativePath(path1, commonAncestor);
+
+ if (adjustedPath == null || adjustedPath.length() == 0) {
+ adjustedPath = displayPath;
+ } else if (adjustedPath.charAt(0) == '/') {
+ adjustedPath = displayPath + "\t(..." + adjustedPath + ")";
+ } else {
+ adjustedPath = displayPath + "\t(.../" + adjustedPath + ")";
+ }
+ return adjustedPath;
+ //TODO: respect relativeToDir
+ }
+
+ protected String getLabel(String path, String revToken) {
+ if (useGitFormat){
+ return path;
+ }
+ revToken = revToken == null ? WC_REVISION_LABEL : revToken;
+ return path + "\t" + revToken;
+ }
+
+ protected boolean displayHeader(OutputStream os, String path, boolean deleted, boolean added, SvnDiffCallback.OperationKind operation) throws SVNException {
+ try {
+ if (deleted && !isDiffDeleted()) {
+ displayString(os, "Index: ");
+ displayString(os, path);
+ displayString(os, " (deleted)");
+ displayEOL(os);
+ displayString(os, HEADER_SEPARATOR);
+ displayEOL(os);
+ return true;
+ }
+ if (added && !isDiffAdded()) {
+ displayString(os, "Index: ");
+ displayString(os, path);
+ displayString(os, " (added)");
+ displayEOL(os);
+ displayString(os, HEADER_SEPARATOR);
+ displayEOL(os);
+ return true;
+ }
+ displayString(os, "Index: ");
+ displayString(os, path);
+ displayEOL(os);
+ displayString(os, HEADER_SEPARATOR);
+ displayEOL(os);
+ return false;
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ return false;
+ }
+
+ protected void displayHeaderFields(OutputStream os, String label1, String label2) throws SVNException {
+ try {
+ displayString(os, "--- ");
+ displayString(os, label1);
+ displayEOL(os);
+ displayString(os, "+++ ");
+ displayString(os, label2);
+ displayEOL(os);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void displayPropertyChangesOn(String path, OutputStream outputStream) throws SVNException {
+ try {
+ displayEOL(outputStream);
+ displayString(outputStream, ("Property changes on: " + (useLocalFileSeparatorChar() ? path.replace('/', File.separatorChar) : path)));
+ displayEOL(outputStream);
+ displayString(outputStream, PROPERTIES_SEPARATOR);
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private byte[] getPropertyAsBytes(SVNPropertyValue value, String encoding) {
+ if (value == null) {
+ return null;
+ }
+ if (value.isString()) {
+ try {
+ return value.getString().getBytes(encoding);
+ } catch (UnsupportedEncodingException e) {
+ return value.getString().getBytes();
+ }
+ }
+ return value.getBytes();
+ }
+
+ private void displayMergeInfoDiff(OutputStream outputStream, String oldValue, String newValue) throws SVNException, IOException {
+ Map oldMergeInfo = null;
+ Map newMergeInfo = null;
+ if (oldValue != null) {
+ oldMergeInfo = SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(oldValue), null);
+ }
+ if (newValue != null) {
+ newMergeInfo = SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(newValue), null);
+ }
+
+ Map deleted = new TreeMap();
+ Map added = new TreeMap();
+ SVNMergeInfoUtil.diffMergeInfo(deleted, added, oldMergeInfo, newMergeInfo, true);
+
+ for (Iterator paths = deleted.keySet().iterator(); paths.hasNext(); ) {
+ String path = (String) paths.next();
+ SVNMergeRangeList rangeList = (SVNMergeRangeList) deleted.get(path);
+ displayString(outputStream, (" Reverse-merged " + path + ":r"));
+ displayString(outputStream, rangeList.toString());
+ displayEOL(outputStream);
+ }
+
+ for (Iterator paths = added.keySet().iterator(); paths.hasNext(); ) {
+ String path = (String) paths.next();
+ SVNMergeRangeList rangeList = (SVNMergeRangeList) added.get(path);
+ displayString(outputStream, (" Merged " + path + ":r"));
+ displayString(outputStream, rangeList.toString());
+ displayEOL(outputStream);
+ }
+ }
+
+ private boolean useLocalFileSeparatorChar() {
+ return true;
+ }
+
+ public boolean isDiffDeleted() {
+ return diffDeleted;
+ }
+
+ public boolean isDiffAdded() {
+ return diffAdded;
+ }
+
+ private void wrapException(IOException e) throws SVNException {
+ SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, e);
+ SVNErrorManager.error(errorMessage, e, SVNLogType.WC);
+ }
+
+ private void displayString(OutputStream outputStream, String s) throws IOException {
+ outputStream.write(s.getBytes(HEADER_ENCODING));
+ }
+
+ private void displayEOL(OutputStream os) throws IOException {
+ os.write(getEOL());
+ }
+
+ public SVNDiffOptions getDiffOptions() {
+ if (diffOptions == null) {
+ diffOptions = new SVNDiffOptions();
+ }
+ return diffOptions;
+ }
+
+ public void setExternalDiffCommand(String externalDiffCommand) {
+ this.externalDiffCommand = externalDiffCommand;
+ }
+
+ public void setRawDiffOptions(List rawDiffOptions) {
+ this.rawDiffOptions = rawDiffOptions;
+ }
+
+ public void setDiffOptions(SVNDiffOptions diffOptions) {
+ this.diffOptions = diffOptions;
+ }
+
+ public void setDiffDeleted(boolean diffDeleted) {
+ this.diffDeleted = diffDeleted;
+ }
+
+ public void setDiffAdded(boolean diffAdded) {
+ this.diffAdded = diffAdded;
+ }
+
+ public void setBasePath(File absoluteFile) {
+ setBaseTarget(SvnTarget.fromFile(absoluteFile));
+ }
+
+ public void setFallbackToAbsolutePath(boolean fallbackToAbsolutePath) {
+ this.fallbackToAbsolutePath = fallbackToAbsolutePath;
+ }
+
+ public void setOptions(ISVNOptions options) {
+ this.options = options;
+ }
+
+ public ISVNOptions getOptions() {
+ return options;
+ }
+
+ private class EmptyDetectionOutputStream extends OutputStream {
+
+ private final OutputStream outputStream;
+ private boolean somethingWritten;
+
+ public EmptyDetectionOutputStream(OutputStream outputStream) {
+ this.outputStream = outputStream;
+ this.somethingWritten = false;
+ }
+
+ public boolean isSomethingWritten() {
+ return somethingWritten;
+ }
+
+ @Override
+ public void write(int c) throws IOException {
+ somethingWritten = true;
+ outputStream.write(c);
+ }
+
+ @Override
+ public void write(byte[] bytes) throws IOException {
+ somethingWritten = bytes.length > 0;
+ outputStream.write(bytes);
+ }
+
+ @Override
+ public void write(byte[] bytes, int offset, int length) throws IOException {
+ somethingWritten = length > 0;
+ outputStream.write(bytes, offset, length);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ outputStream.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ outputStream.close();
+ }
+ }
+}
diff --git a/scm-it/src/test/resources/diff/largefile/original/SvnDiffGenerator_forTest b/scm-it/src/test/resources/diff/largefile/original/SvnDiffGenerator_forTest
new file mode 100644
index 0000000000..d0cf749024
--- /dev/null
+++ b/scm-it/src/test/resources/diff/largefile/original/SvnDiffGenerator_forTest
@@ -0,0 +1,1240 @@
+package sonia.scm.repository.spi;
+
+import de.regnis.q.sequence.line.diff.QDiffGenerator;
+import de.regnis.q.sequence.line.diff.QDiffGeneratorFactory;
+import de.regnis.q.sequence.line.diff.QDiffManager;
+import de.regnis.q.sequence.line.diff.QDiffUniGenerator;
+import org.tmatesoft.svn.core.SVNErrorCode;
+import org.tmatesoft.svn.core.SVNErrorMessage;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNMergeRangeList;
+import org.tmatesoft.svn.core.SVNProperties;
+import org.tmatesoft.svn.core.SVNProperty;
+import org.tmatesoft.svn.core.SVNPropertyValue;
+import org.tmatesoft.svn.core.internal.util.SVNHashMap;
+import org.tmatesoft.svn.core.internal.util.SVNMergeInfoUtil;
+import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
+import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions;
+import org.tmatesoft.svn.core.internal.wc.ISVNReturnValueCallback;
+import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
+import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
+import org.tmatesoft.svn.core.internal.wc2.ng.ISvnDiffGenerator;
+import org.tmatesoft.svn.core.internal.wc2.ng.SvnDiffCallback;
+import org.tmatesoft.svn.core.wc.ISVNOptions;
+import org.tmatesoft.svn.core.wc.SVNDiffOptions;
+import org.tmatesoft.svn.core.wc2.SvnTarget;
+import org.tmatesoft.svn.util.SVNLogType;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.RandomAccessFile;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+public class SCMSvnDiffGenerator implements ISvnDiffGenerator {
+
+ protected static final String WC_REVISION_LABEL = "(working copy)";
+ protected static final String PROPERTIES_SEPARATOR = "___________________________________________________________________";
+ protected static final String HEADER_SEPARATOR = "===================================================================";
+ protected static final String HEADER_ENCODING = "UTF-8";
+
+ private SvnTarget originalTarget1;
+ private SvnTarget originalTarget2;
+ private SvnTarget baseTarget;
+ private SvnTarget relativeToTarget;
+ private SvnTarget repositoryRoot;
+ private String encoding;
+ private byte[] eol;
+ private boolean useGitFormat;
+ private boolean forcedBinaryDiff;
+
+ private boolean diffDeleted;
+ private boolean diffAdded;
+ private List rawDiffOptions;
+ private boolean forceEmpty;
+
+ private Set visitedPaths;
+ private String externalDiffCommand;
+ private SVNDiffOptions diffOptions;
+ private boolean fallbackToAbsolutePath;
+ private ISVNOptions options;
+ private boolean propertiesOnly;
+ private boolean ignoreProperties;
+
+ private String getDisplayPath(SvnTarget target) {
+ String relativePath;
+ if (baseTarget == null) {
+ relativePath = null;
+ } else {
+ String targetString = target.getPathOrUrlDecodedString();
+ String baseTargetString = baseTarget.getPathOrUrlDecodedString();
+ relativePath = getRelativePath(targetString, baseTargetString);
+ }
+
+ return relativePath != null ? relativePath : target.getPathOrUrlString();
+ }
+
+ private String getRelativeToRootPath(SvnTarget target, SvnTarget originalTarget) {
+ String relativePath;
+ if (repositoryRoot == null)
+ {
+ relativePath = null;
+ }
+ else
+ {
+ if (repositoryRoot.isFile() == target.isFile())
+ {
+ String targetString = target.getPathOrUrlDecodedString();
+ String baseTargetString = repositoryRoot.getPathOrUrlDecodedString();
+ relativePath = getRelativePath(targetString, baseTargetString);
+ }
+ else
+ {
+ String targetString = target.getPathOrUrlDecodedString();
+ String baseTargetString = new File("").getAbsolutePath();
+ relativePath = getRelativePath(targetString, baseTargetString);
+ }
+ }
+
+ return relativePath != null ? relativePath : target.getPathOrUrlString();
+ }
+
+ private String getRelativePath(String targetString, String baseTargetString) {
+ if (targetString != null)
+ {
+ targetString = targetString.replace(File.separatorChar, '/');
+ }
+ if (baseTargetString != null)
+ {
+ baseTargetString = baseTargetString.replace(File.separatorChar, '/');
+ }
+
+ final String pathAsChild = SVNPathUtil.getPathAsChild(baseTargetString, targetString);
+ if (pathAsChild != null)
+ {
+ return pathAsChild;
+ }
+ if (targetString.equals(baseTargetString))
+ {
+ return "";
+ }
+ return null;
+ }
+
+ private String getChildPath(String path, String relativeToPath) {
+ if (relativeToTarget == null) {
+ return null;
+ }
+
+ String relativePath = getRelativePath(path, relativeToPath);
+ if (relativePath == null) {
+ return path;
+ }
+
+ if (relativePath.length() > 0) {
+ return relativePath;
+ }
+
+ if (relativeToPath.equals(path)) {
+ return ".";
+ }
+
+ return null;
+ }
+
+ public SCMSvnDiffGenerator() {
+ this.originalTarget1 = null;
+ this.originalTarget2 = null;
+ this.visitedPaths = new HashSet();
+ this.diffDeleted = true;
+ this.diffAdded = true;
+ }
+
+ public void setBaseTarget(SvnTarget baseTarget) {
+ this.baseTarget = baseTarget;
+ }
+
+ public void setUseGitFormat(boolean useGitFormat) {
+ this.useGitFormat = useGitFormat;
+ }
+
+ public void setOriginalTargets(SvnTarget originalTarget1, SvnTarget originalTarget2) {
+ this.originalTarget1 = originalTarget1;
+ this.originalTarget2 = originalTarget2;
+ }
+
+ public void setRelativeToTarget(SvnTarget relativeToTarget) {
+ this.relativeToTarget = relativeToTarget;
+ }
+
+ public void setAnchors(SvnTarget originalTarget1, SvnTarget originalTarget2) {
+ //anchors are not used
+ }
+
+ public void setRepositoryRoot(SvnTarget repositoryRoot) {
+ this.repositoryRoot = repositoryRoot;
+ }
+
+ public void setForceEmpty(boolean forceEmpty) {
+ this.forceEmpty = forceEmpty;
+ }
+
+ public void setEncoding(String encoding) {
+ this.encoding = encoding;
+ }
+
+ public String getEncoding() {
+ return encoding;
+ }
+
+ public String getGlobalEncoding() {
+ ISVNOptions options = getOptions();
+
+ if (options != null && options instanceof DefaultSVNOptions) {
+ DefaultSVNOptions defaultOptions = (DefaultSVNOptions) options;
+ return defaultOptions.getGlobalCharset();
+ }
+ return null;
+ }
+
+ public void setEOL(byte[] eol) {
+ this.eol = eol;
+ }
+
+ public byte[] getEOL() {
+ return eol;
+ }
+
+ public boolean isForcedBinaryDiff() {
+ return forcedBinaryDiff;
+ }
+
+ public void setForcedBinaryDiff(boolean forcedBinaryDiff) {
+ this.forcedBinaryDiff = forcedBinaryDiff;
+ }
+
+ public boolean isPropertiesOnly() {
+ return propertiesOnly;
+ }
+
+ public void setPropertiesOnly(boolean propertiesOnly) {
+ this.propertiesOnly = propertiesOnly;
+ }
+
+ public boolean isIgnoreProperties() {
+ return ignoreProperties;
+ }
+
+ public void setIgnoreProperties(boolean ignoreProperties) {
+ this.ignoreProperties = ignoreProperties;
+ }
+
+ public void displayDeletedDirectory(SvnTarget target, String revision1, String revision2, OutputStream outputStream) throws SVNException {
+ }
+
+ public void displayAddedDirectory(SvnTarget target, String revision1, String revision2, OutputStream outputStream) throws SVNException {
+ }
+
+ public void displayPropsChanged(SvnTarget target, String revision1, String revision2, boolean dirWasAdded, SVNProperties originalProps, SVNProperties propChanges, OutputStream outputStream) throws SVNException {
+ if (isIgnoreProperties()) {
+ return;
+ }
+ if (dirWasAdded && !isDiffAdded()) {
+ return;
+ }
+ ensureEncodingAndEOLSet();
+ String displayPath = getDisplayPath(target);
+
+ String targetString1 = originalTarget1.getPathOrUrlDecodedString();
+ String targetString2 = originalTarget2.getPathOrUrlDecodedString();
+
+ if (displayPath == null || displayPath.length() == 0) {
+ displayPath = ".";
+ }
+
+ if (useGitFormat) {
+ targetString1 = adjustRelativeToReposRoot(targetString1);
+ targetString2 = adjustRelativeToReposRoot(targetString2);
+ }
+
+ String newTargetString = displayPath;
+ String newTargetString1 = targetString1;
+ String newTargetString2 = targetString2;
+
+ String commonAncestor = SVNPathUtil.getCommonPathAncestor(newTargetString1, newTargetString2);
+ int commonLength = commonAncestor == null ? 0 : commonAncestor.length();
+
+ newTargetString1 = newTargetString1.substring(commonLength);
+ newTargetString2 = newTargetString2.substring(commonLength);
+
+ newTargetString1 = computeLabel(newTargetString, newTargetString1);
+ newTargetString2 = computeLabel(newTargetString, newTargetString2);
+
+ if (relativeToTarget != null) {
+ String relativeToPath = relativeToTarget.getPathOrUrlDecodedString();
+ String absolutePath = target.getPathOrUrlDecodedString();
+
+ String childPath = getChildPath(absolutePath, relativeToPath);
+ if (childPath == null) {
+ throwBadRelativePathException(absolutePath, relativeToPath);
+ }
+ String childPath1 = getChildPath(newTargetString1, relativeToPath);
+ if (childPath1 == null) {
+ throwBadRelativePathException(newTargetString1, relativeToPath);
+ }
+ String childPath2 = getChildPath(newTargetString2, relativeToPath);
+ if (childPath2 == null) {
+ throwBadRelativePathException(newTargetString2, relativeToPath);
+ }
+
+ displayPath = childPath;
+ newTargetString1 = childPath1;
+ newTargetString2 = childPath2;
+ }
+
+ boolean showDiffHeader = !visitedPaths.contains(displayPath);
+ if (showDiffHeader) {
+ String label1 = getLabel(newTargetString1, revision1);
+ String label2 = getLabel(newTargetString2, revision2);
+
+ boolean shouldStopDisplaying = displayHeader(outputStream, displayPath, false, fallbackToAbsolutePath, SvnDiffCallback.OperationKind.Modified);
+ visitedPaths.add(displayPath);
+ if (useGitFormat) {
+ displayGitDiffHeader(outputStream, SvnDiffCallback.OperationKind.Modified,
+ getRelativeToRootPath(target, originalTarget1),
+ getRelativeToRootPath(target, originalTarget2),
+ null);
+ }
+ if (shouldStopDisplaying) {
+ return;
+ }
+
+// if (useGitFormat) {
+// String copyFromPath = null;
+// SvnDiffCallback.OperationKind operationKind = SvnDiffCallback.OperationKind.Modified;
+// label1 = getGitDiffLabel1(operationKind, targetString1, targetString2, copyFromPath, revision1);
+// label2 = getGitDiffLabel2(operationKind, targetString1, targetString2, copyFromPath, revision2);
+// displayGitDiffHeader(outputStream, operationKind,
+// getRelativeToRootPath(target, originalTarget1),
+// getRelativeToRootPath(target, originalTarget2),
+// copyFromPath);
+// }
+
+ if (useGitFormat) {
+ displayGitHeaderFields(outputStream, target, revision1, revision2, SvnDiffCallback.OperationKind.Modified, null);
+ } else {
+ displayHeaderFields(outputStream, label1, label2);
+ }
+ }
+
+ displayPropertyChangesOn(useGitFormat ? getRelativeToRootPath(target, originalTarget1) : displayPath, outputStream);
+
+ displayPropDiffValues(outputStream, propChanges, originalProps);
+ }
+
+ private void throwBadRelativePathException(String displayPath, String relativeToPath) throws SVNException {
+ SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.BAD_RELATIVE_PATH, "Path ''{0}'' must be an immediate child of the directory ''{0}''",
+ displayPath, relativeToPath);
+ SVNErrorManager.error(errorMessage, SVNLogType.CLIENT);
+ }
+
+ private void displayGitHeaderFields(OutputStream outputStream, SvnTarget target, String revision1, String revision2, SvnDiffCallback.OperationKind operation, String copyFromPath) throws SVNException {
+ String path1 = copyFromPath != null ? copyFromPath : getRelativeToRootPath(target, originalTarget1);
+ String path2 = getRelativeToRootPath(target, originalTarget2);
+
+ try {
+ displayString(outputStream, "--- ");
+ displayFirstGitLabelPath(outputStream, path1, revision1, operation);
+ displayEOL(outputStream);
+ displayString(outputStream, "+++ ");
+ displaySecondGitLabelPath(outputStream, path2, revision2, operation);
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private String adjustRelativeToReposRoot(String targetString) {
+ if (repositoryRoot != null) {
+ String repositoryRootString = repositoryRoot.getPathOrUrlDecodedString();
+ String relativePath = getRelativePath(targetString, repositoryRootString);
+ return relativePath == null ? "" : relativePath;
+ }
+ return targetString;
+ }
+
+ private String computeLabel(String targetString, String originalTargetString) {
+ if (originalTargetString.length() == 0) {
+ return targetString;
+ } else if (originalTargetString.charAt(0) == '/') {
+ return targetString + "\t(..." + originalTargetString + ")";
+ } else {
+ return targetString + "\t(.../" + originalTargetString + ")";
+ }
+ }
+
+ public void displayContentChanged(SvnTarget target, File leftFile, File rightFile, String revision1, String revision2, String mimeType1, String mimeType2, SvnDiffCallback.OperationKind operation, File copyFromPath, SVNProperties originalProperties, SVNProperties propChanges, OutputStream outputStream) throws SVNException {
+ if (isPropertiesOnly()) {
+ return;
+ }
+ ensureEncodingAndEOLSet();
+ String displayPath = getDisplayPath(target);
+
+ String targetString1 = originalTarget1.getPathOrUrlDecodedString();
+ String targetString2 = originalTarget2.getPathOrUrlDecodedString();
+
+ if (useGitFormat) {
+ targetString1 = adjustRelativeToReposRoot(targetString1);
+ targetString2 = adjustRelativeToReposRoot(targetString2);
+ }
+
+ String newTargetString = displayPath;
+ String newTargetString1 = targetString1;
+ String newTargetString2 = targetString2;
+
+ String commonAncestor = SVNPathUtil.getCommonPathAncestor(newTargetString1, newTargetString2);
+ int commonLength = commonAncestor == null ? 0 : commonAncestor.length();
+
+ newTargetString1 = newTargetString1.substring(commonLength);
+ newTargetString2 = newTargetString2.substring(commonLength);
+
+ newTargetString1 = computeLabel(newTargetString, newTargetString1);
+ newTargetString2 = computeLabel(newTargetString, newTargetString2);
+
+ if (relativeToTarget != null) {
+ String relativeToPath = relativeToTarget.getPathOrUrlDecodedString();
+ String absolutePath = target.getPathOrUrlDecodedString();
+
+ String childPath = getChildPath(absolutePath, relativeToPath);
+ if (childPath == null) {
+ throwBadRelativePathException(absolutePath, relativeToPath);
+ }
+ String childPath1 = getChildPath(newTargetString1, relativeToPath);
+ if (childPath1 == null) {
+ throwBadRelativePathException(newTargetString1, relativeToPath);
+ }
+ String childPath2 = getChildPath(newTargetString2, relativeToPath);
+ if (childPath2 == null) {
+ throwBadRelativePathException(newTargetString2, relativeToPath);
+ }
+
+ displayPath = childPath;
+ newTargetString1 = childPath1;
+ newTargetString2 = childPath2;
+ }
+
+ String label1 = getLabel(newTargetString1, revision1);
+ String label2 = getLabel(newTargetString2, revision2);
+
+ boolean leftIsBinary = false;
+ boolean rightIsBinary = false;
+
+ if (mimeType1 != null) {
+ leftIsBinary = SVNProperty.isBinaryMimeType(mimeType1);
+ }
+ if (mimeType2 != null) {
+ rightIsBinary = SVNProperty.isBinaryMimeType(mimeType2);
+ }
+
+ if (!forcedBinaryDiff && (leftIsBinary || rightIsBinary)) {
+ boolean shouldStopDisplaying = displayHeader(outputStream, displayPath, rightFile == null, leftFile == null, operation);
+ if (useGitFormat) {
+ displayGitDiffHeader(outputStream, operation,
+ getRelativeToRootPath(target, originalTarget1),
+ getRelativeToRootPath(target, originalTarget2),
+ null);
+ }
+ visitedPaths.add(displayPath);
+ if (shouldStopDisplaying) {
+ return;
+ }
+
+
+ displayBinary(mimeType1, mimeType2, outputStream, leftIsBinary, rightIsBinary);
+
+ return;
+ }
+
+ final String diffCommand = getExternalDiffCommand();
+ if (diffCommand != null) {
+ boolean shouldStopDisplaying = displayHeader(outputStream, displayPath, rightFile == null, leftFile == null, operation);
+ if (useGitFormat) {
+ displayGitDiffHeader(outputStream, operation,
+ getRelativeToRootPath(target, originalTarget1),
+ getRelativeToRootPath(target, originalTarget2),
+ null);
+ }
+ visitedPaths.add(displayPath);
+ if (shouldStopDisplaying) {
+ return;
+ }
+
+ runExternalDiffCommand(outputStream, diffCommand, leftFile, rightFile, label1, label2);
+ } else {
+ internalDiff(target, outputStream, displayPath, leftFile, rightFile, label1, label2, operation, copyFromPath == null ? null : copyFromPath.getPath(), revision1, revision2);
+ }
+ }
+
+ private void displayBinary(String mimeType1, String mimeType2, OutputStream outputStream, boolean leftIsBinary, boolean rightIsBinary) throws SVNException {
+ displayCannotDisplayFileMarkedBinary(outputStream);
+
+ if (leftIsBinary && !rightIsBinary) {
+ displayMimeType(outputStream, mimeType1);
+ } else if (!leftIsBinary && rightIsBinary) {
+ displayMimeType(outputStream, mimeType2);
+ } else if (leftIsBinary && rightIsBinary) {
+ if (mimeType1.equals(mimeType2)) {
+ displayMimeType(outputStream, mimeType1);
+ } else {
+ displayMimeTypes(outputStream, mimeType1, mimeType2);
+ }
+ }
+ }
+
+ private void internalDiff(SvnTarget target, OutputStream outputStream, String displayPath, File file1, File file2, String label1, String label2, SvnDiffCallback.OperationKind operation, String copyFromPath, String revision1, String revision2) throws SVNException {
+ String header = getHeaderString(target, displayPath, file2 == null, file1 == null, operation, copyFromPath);
+ if (file2 == null && !isDiffDeleted()) {
+ try {
+ displayString(outputStream, header);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ visitedPaths.add(displayPath);
+ return;
+ }
+ if (file1 == null && !isDiffAdded()) {
+ try {
+ displayString(outputStream, header);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ visitedPaths.add(displayPath);
+ return;
+ }
+ String headerFields = getHeaderFieldsString(target, displayPath, label1, label2, revision1, revision2, operation, copyFromPath);
+
+ RandomAccessFile is1 = null;
+ RandomAccessFile is2 = null;
+ try {
+ is1 = file1 == null ? null : SVNFileUtil.openRAFileForReading(file1);
+ is2 = file2 == null ? null : SVNFileUtil.openRAFileForReading(file2);
+
+ QDiffUniGenerator.setup();
+ Map properties = new SVNHashMap();
+
+ properties.put(QDiffGeneratorFactory.IGNORE_EOL_PROPERTY, Boolean.valueOf(getDiffOptions().isIgnoreEOLStyle()));
+ properties.put(QDiffGeneratorFactory.EOL_PROPERTY, new String(getEOL()));
+ if (getDiffOptions().isIgnoreAllWhitespace()) {
+ properties.put(QDiffGeneratorFactory.IGNORE_SPACE_PROPERTY, QDiffGeneratorFactory.IGNORE_ALL_SPACE);
+ } else if (getDiffOptions().isIgnoreAmountOfWhitespace()) {
+ properties.put(QDiffGeneratorFactory.IGNORE_SPACE_PROPERTY, QDiffGeneratorFactory.IGNORE_SPACE_CHANGE);
+ }
+
+ final String diffHeader;
+ if (forceEmpty || useGitFormat) {
+ displayString(outputStream, header);
+ diffHeader = headerFields;
+
+ visitedPaths.add(displayPath);
+ } else {
+ diffHeader = header + headerFields;
+ }
+ QDiffGenerator generator = new QDiffUniGenerator(properties, diffHeader);
+ EmptyDetectionOutputStream emptyDetectionOutputStream = new EmptyDetectionOutputStream(outputStream);
+ QDiffManager.generateTextDiff(is1, is2, emptyDetectionOutputStream, generator);
+ if (emptyDetectionOutputStream.isSomethingWritten()) {
+ visitedPaths.add(displayPath);
+ }
+ emptyDetectionOutputStream.flush();
+ } catch (IOException e) {
+ SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getMessage());
+ SVNErrorManager.error(err, e, SVNLogType.DEFAULT);
+ } finally {
+ SVNFileUtil.closeFile(is1);
+ SVNFileUtil.closeFile(is2);
+ }
+ }
+
+ private String getHeaderFieldsString(SvnTarget target, String displayPath, String label1, String label2, String revision1, String revision2, SvnDiffCallback.OperationKind operation, String copyFromPath) throws SVNException {
+ final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ try {
+ if (useGitFormat) {
+ displayGitHeaderFields(byteArrayOutputStream, target, revision1, revision2, operation, copyFromPath);
+ } else {
+ displayHeaderFields(byteArrayOutputStream, label1, label2);
+ }
+ } catch (SVNException e) {
+ SVNFileUtil.closeFile(byteArrayOutputStream);
+
+ try {
+ byteArrayOutputStream.writeTo(byteArrayOutputStream);
+ } catch (IOException e1) {
+ }
+
+ throw e;
+ }
+
+ try {
+ byteArrayOutputStream.close();
+ return byteArrayOutputStream.toString(HEADER_ENCODING);
+ } catch (IOException e) {
+ return "";
+ }
+ }
+
+ private String getHeaderString(SvnTarget target, String displayPath, boolean deleted, boolean added, SvnDiffCallback.OperationKind operation, String copyFromPath) throws SVNException {
+ final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ try {
+ boolean stopDisplaying = displayHeader(byteArrayOutputStream, displayPath, deleted, added, operation);
+ if (useGitFormat) {
+ displayGitDiffHeader(byteArrayOutputStream, operation,
+ getRelativeToRootPath(target, originalTarget1),
+ getRelativeToRootPath(target, originalTarget2),
+ copyFromPath);
+ }
+ } catch (SVNException e) {
+ SVNFileUtil.closeFile(byteArrayOutputStream);
+
+ try {
+ byteArrayOutputStream.writeTo(byteArrayOutputStream);
+ } catch (IOException e1) {
+ }
+
+ throw e;
+ }
+
+ try {
+ byteArrayOutputStream.close();
+ return byteArrayOutputStream.toString(HEADER_ENCODING);
+ } catch (IOException e) {
+ return "";
+ }
+ }
+
+ private void runExternalDiffCommand(OutputStream outputStream, final String diffCommand, File file1, File file2, String label1, String label2) throws SVNException {
+ final List args = new ArrayList();
+ args.add(diffCommand);
+ if (rawDiffOptions != null) {
+ args.addAll(rawDiffOptions);
+ } else {
+ Collection svnDiffOptionsCollection = getDiffOptions().toOptionsCollection();
+ args.addAll(svnDiffOptionsCollection);
+ args.add("-u");
+ }
+
+ if (label1 != null) {
+ args.add("-L");
+ args.add(label1);
+ }
+
+ if (label2 != null) {
+ args.add("-L");
+ args.add(label2);
+ }
+
+ boolean tmpFile1 = false;
+ boolean tmpFile2 = false;
+ if (file1 == null) {
+ file1 = SVNFileUtil.createTempFile("svn.", ".tmp");
+ tmpFile1 = true;
+ }
+ if (file2 == null) {
+ file2 = SVNFileUtil.createTempFile("svn.", ".tmp");
+ tmpFile2 = true;
+ }
+
+ String file1Path = file1.getAbsolutePath().replace(File.separatorChar, '/');
+ String file2Path = file2.getAbsolutePath().replace(File.separatorChar, '/');
+
+ args.add(file1Path);
+ args.add(file2Path);
+ try {
+ final Writer writer = new OutputStreamWriter(outputStream, getEncoding());
+
+ SVNFileUtil.execCommand(args.toArray(new String[args.size()]), true,
+ new ISVNReturnValueCallback() {
+
+ public void handleReturnValue(int returnValue) throws SVNException {
+ if (returnValue != 0 && returnValue != 1) {
+ SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.EXTERNAL_PROGRAM,
+ "''{0}'' returned {1}", new Object[]{diffCommand, String.valueOf(returnValue)});
+ SVNErrorManager.error(err, SVNLogType.DEFAULT);
+ }
+ }
+
+ public void handleChar(char ch) throws SVNException {
+ try {
+ writer.write(ch);
+ } catch (IOException ioe) {
+ SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getMessage());
+ SVNErrorManager.error(err, ioe, SVNLogType.DEFAULT);
+ }
+ }
+
+ public boolean isHandleProgramOutput() {
+ return true;
+ }
+ });
+
+ writer.flush();
+ } catch (IOException ioe) {
+ SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getMessage());
+ SVNErrorManager.error(err, ioe, SVNLogType.DEFAULT);
+ } finally {
+ try {
+ if (tmpFile1) {
+ SVNFileUtil.deleteFile(file1);
+ }
+ if (tmpFile2) {
+ SVNFileUtil.deleteFile(file2);
+ }
+ } catch (SVNException e) {
+ // skip
+ }
+ }
+ }
+
+ private String getExternalDiffCommand() {
+ return externalDiffCommand;
+ }
+
+ private void displayMimeType(OutputStream outputStream, String mimeType) throws SVNException {
+ try {
+ displayString(outputStream, SVNProperty.MIME_TYPE);
+ displayString(outputStream, " = ");
+ displayString(outputStream, mimeType);
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void displayMimeTypes(OutputStream outputStream, String mimeType1, String mimeType2) throws SVNException {
+ try {
+ displayString(outputStream, SVNProperty.MIME_TYPE);
+ displayString(outputStream, " = (");
+ displayString(outputStream, mimeType1);
+ displayString(outputStream, ", ");
+ displayString(outputStream, mimeType2);
+ displayString(outputStream, ")");
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void displayCannotDisplayFileMarkedBinary(OutputStream outputStream) throws SVNException {
+ try {
+ displayString(outputStream, "Cannot display: file marked as a binary type.");
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void ensureEncodingAndEOLSet() {
+ if (getEOL() == null) {
+ setEOL(SVNProperty.EOL_LF_BYTES);
+ }
+ if (getEncoding() == null) {
+ final ISVNOptions options = getOptions();
+ if (options != null && options.getNativeCharset() != null) {
+ setEncoding(options.getNativeCharset());
+ } else {
+ setEncoding("UTF-8");
+ }
+ }
+ }
+
+ private void displayPropDiffValues(OutputStream outputStream, SVNProperties diff, SVNProperties baseProps) throws SVNException {
+ for (Iterator changedPropNames = diff.nameSet().iterator(); changedPropNames.hasNext(); ) {
+ String name = (String) changedPropNames.next();
+ SVNPropertyValue originalValue = baseProps != null ? baseProps.getSVNPropertyValue(name) : null;
+ SVNPropertyValue newValue = diff.getSVNPropertyValue(name);
+ String headerFormat = null;
+
+ if (originalValue == null) {
+ headerFormat = "Added: ";
+ } else if (newValue == null) {
+ headerFormat = "Deleted: ";
+ } else {
+ headerFormat = "Modified: ";
+ }
+
+ try {
+ displayString(outputStream, (headerFormat + name));
+ displayEOL(outputStream);
+ if (SVNProperty.MERGE_INFO.equals(name)) {
+ displayMergeInfoDiff(outputStream, originalValue == null ? null : originalValue.getString(), newValue == null ? null : newValue.getString());
+ continue;
+ }
+
+ byte[] originalValueBytes = getPropertyAsBytes(originalValue, getEncoding());
+ byte[] newValueBytes = getPropertyAsBytes(newValue, getEncoding());
+
+ if (originalValueBytes == null) {
+ originalValueBytes = new byte[0];
+ } else {
+ originalValueBytes = maybeAppendEOL(originalValueBytes);
+ }
+
+ boolean newValueHadEol = newValueBytes != null && newValueBytes.length > 0 &&
+ (newValueBytes[newValueBytes.length - 1] == SVNProperty.EOL_CR_BYTES[0] ||
+ newValueBytes[newValueBytes.length - 1] == SVNProperty.EOL_LF_BYTES[0]);
+
+ if (newValueBytes == null) {
+ newValueBytes = new byte[0];
+ } else {
+ newValueBytes = maybeAppendEOL(newValueBytes);
+ }
+
+ QDiffUniGenerator.setup();
+ Map properties = new SVNHashMap();
+
+ properties.put(QDiffGeneratorFactory.IGNORE_EOL_PROPERTY, Boolean.valueOf(getDiffOptions().isIgnoreEOLStyle()));
+ properties.put(QDiffGeneratorFactory.EOL_PROPERTY, new String(getEOL()));
+ properties.put(QDiffGeneratorFactory.HUNK_DELIMITER, "##");
+ if (getDiffOptions().isIgnoreAllWhitespace()) {
+ properties.put(QDiffGeneratorFactory.IGNORE_SPACE_PROPERTY, QDiffGeneratorFactory.IGNORE_ALL_SPACE);
+ } else if (getDiffOptions().isIgnoreAmountOfWhitespace()) {
+ properties.put(QDiffGeneratorFactory.IGNORE_SPACE_PROPERTY, QDiffGeneratorFactory.IGNORE_SPACE_CHANGE);
+ }
+
+ QDiffGenerator generator = new QDiffUniGenerator(properties, "");
+ Writer writer = new OutputStreamWriter(outputStream, getEncoding());
+ QDiffManager.generateTextDiff(new ByteArrayInputStream(originalValueBytes), new ByteArrayInputStream(newValueBytes),
+ null, writer, generator);
+ writer.flush();
+ if (!newValueHadEol) {
+ displayString(outputStream, "\\ No newline at end of property");
+ displayEOL(outputStream);
+ }
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ }
+
+ private byte[] maybeAppendEOL(byte[] buffer) {
+ if (buffer.length == 0) {
+ return buffer;
+ }
+
+ byte lastByte = buffer[buffer.length - 1];
+ if (lastByte == SVNProperty.EOL_CR_BYTES[0]) {
+ return buffer;
+ } else if (lastByte != SVNProperty.EOL_LF_BYTES[0]) {
+ final byte[] newBuffer = new byte[buffer.length + getEOL().length];
+ System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
+ System.arraycopy(getEOL(), 0, newBuffer, buffer.length, getEOL().length);
+ return newBuffer;
+ } else {
+ return buffer;
+ }
+ }
+
+ private String getGitDiffLabel1(SvnDiffCallback.OperationKind operationKind, String path1, String path2, String copyFromPath, String revision) {
+ if (operationKind == SvnDiffCallback.OperationKind.Deleted) {
+ return getLabel("a/" + path1, revision);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Copied) {
+ return getLabel("a/" + copyFromPath, revision);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Added) {
+ return getLabel("/dev/null", revision);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Modified) {
+ return getLabel("a/" + path1, revision);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Moved) {
+ return getLabel("a/" + copyFromPath, revision);
+ }
+ throw new IllegalArgumentException("Unsupported operation: " + operationKind);
+ }
+
+ private String getGitDiffLabel2(SvnDiffCallback.OperationKind operationKind, String path1, String path2, String copyFromPath, String revision) {
+ if (operationKind == SvnDiffCallback.OperationKind.Deleted) {
+ return getLabel("/dev/null", revision);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Copied) {
+ return getLabel("b/" + path2, revision);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Added) {
+ return getLabel("b/" + path2, revision);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Modified) {
+ return getLabel("b/" + path2, revision);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Moved) {
+ return getLabel("b/" + path2, revision);
+ }
+ throw new IllegalArgumentException("Unsupported operation: " + operationKind);
+ }
+
+ private void displayGitDiffHeader(OutputStream outputStream, SvnDiffCallback.OperationKind operationKind, String path1, String path2, String copyFromPath) throws SVNException {
+ if (operationKind == SvnDiffCallback.OperationKind.Deleted) {
+ displayGitDiffHeaderDeleted(outputStream, path1, path2, copyFromPath);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Copied) {
+ displayGitDiffHeaderCopied(outputStream, path1, path2, copyFromPath);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Added) {
+ displayGitDiffHeaderAdded(outputStream, path1, path2, copyFromPath);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Modified) {
+ displayGitDiffHeaderModified(outputStream, path1, path2, copyFromPath);
+ } else if (operationKind == SvnDiffCallback.OperationKind.Moved) {
+ displayGitDiffHeaderRenamed(outputStream, path1, path2, copyFromPath);
+ }
+ }
+
+ private void displayGitDiffHeaderAdded(OutputStream outputStream, String path1, String path2, String copyFromPath) throws SVNException {
+ try {
+ displayString(outputStream, "diff --git ");
+ displayFirstGitPath(outputStream, path1);
+ displayString(outputStream, " ");
+ displaySecondGitPath(outputStream, path2);
+ displayEOL(outputStream);
+ displayString(outputStream, "new file mode 10644");
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void displayGitDiffHeaderDeleted(OutputStream outputStream, String path1, String path2, String copyFromPath) throws SVNException {
+ try {
+ displayString(outputStream, "diff --git ");
+ displayFirstGitPath(outputStream, path1);
+ displayString(outputStream, " ");
+ displaySecondGitPath(outputStream, path2);
+ displayEOL(outputStream);
+ displayString(outputStream, "deleted file mode 10644");
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void displayGitDiffHeaderCopied(OutputStream outputStream, String path1, String path2, String copyFromPath) throws SVNException {
+ try {
+ displayString(outputStream, "diff --git ");
+ displayFirstGitPath(outputStream, copyFromPath);
+ displayString(outputStream, " ");
+ displaySecondGitPath(outputStream, path2);
+ displayEOL(outputStream);
+ displayString(outputStream, "copy from ");
+ displayString(outputStream, copyFromPath);
+ displayEOL(outputStream);
+ displayString(outputStream, "copy to ");
+ displayString(outputStream, path2);
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void displayGitDiffHeaderRenamed(OutputStream outputStream, String path1, String path2, String copyFromPath) throws SVNException {
+ try {
+ displayString(outputStream, "diff --git ");
+ displayFirstGitPath(outputStream, copyFromPath);
+ displayString(outputStream, " ");
+ displaySecondGitPath(outputStream, path2);
+ displayEOL(outputStream);
+ displayString(outputStream, "rename from ");
+ displayString(outputStream, copyFromPath);
+ displayEOL(outputStream);
+ displayString(outputStream, "rename to ");
+ displayString(outputStream, path2);
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void displayGitDiffHeaderModified(OutputStream outputStream, String path1, String path2, String copyFromPath) throws SVNException {
+ try {
+ displayString(outputStream, "diff --git ");
+ displayFirstGitPath(outputStream, path1);
+ displayString(outputStream, " ");
+ displaySecondGitPath(outputStream, path2);
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void displayFirstGitPath(OutputStream outputStream, String path1) throws IOException {
+ displayGitPath(outputStream, path1, "a/", false);
+ }
+
+ private void displaySecondGitPath(OutputStream outputStream, String path2) throws IOException {
+ displayGitPath(outputStream, path2, "b/", false);
+ }
+
+ private void displayFirstGitLabelPath(OutputStream outputStream, String path1, String revision1, SvnDiffCallback.OperationKind operation) throws IOException {
+ String pathPrefix = "a/";
+ if (operation == SvnDiffCallback.OperationKind.Added) {
+ path1 = "/dev/null";
+ pathPrefix = "";
+ }
+ displayGitPath(outputStream, getLabel(path1, revision1), pathPrefix, true);
+ }
+
+ private void displaySecondGitLabelPath(OutputStream outputStream, String path2, String revision2, SvnDiffCallback.OperationKind operation) throws IOException {
+ String pathPrefix = "b/";
+ if (operation == SvnDiffCallback.OperationKind.Deleted) {
+ path2 = "/dev/null";
+ pathPrefix = "";
+ }
+ displayGitPath(outputStream, getLabel(path2, revision2), pathPrefix, true);
+ }
+
+ private void displayGitPath(OutputStream outputStream, String path1, String pathPrefix, boolean label) throws IOException {
+// if (!label && path1.length() == 0) {
+// displayString(outputStream, ".");
+// } else {
+ displayString(outputStream, pathPrefix);
+ displayString(outputStream, path1);
+// }
+ }
+
+ private String getAdjustedPathWithLabel(String displayPath, String path, String revision, String commonAncestor) {
+ String adjustedPath = getAdjustedPath(displayPath, path, commonAncestor);
+ return getLabel(adjustedPath, revision);
+ }
+
+ private String getAdjustedPath(String displayPath, String path1, String commonAncestor) {
+ String adjustedPath = getRelativePath(path1, commonAncestor);
+
+ if (adjustedPath == null || adjustedPath.length() == 0) {
+ adjustedPath = displayPath;
+ } else if (adjustedPath.charAt(0) == '/') {
+ adjustedPath = displayPath + "\t(..." + adjustedPath + ")";
+ } else {
+ adjustedPath = displayPath + "\t(.../" + adjustedPath + ")";
+ }
+ return adjustedPath;
+ //TODO: respect relativeToDir
+ }
+
+ protected String getLabel(String path, String revToken) {
+ revToken = revToken == null ? WC_REVISION_LABEL : revToken;
+ return path + "\t" + revToken;
+ }
+
+ protected boolean displayHeader(OutputStream os, String path, boolean deleted, boolean added, SvnDiffCallback.OperationKind operation) throws SVNException {
+ try {
+ if (deleted && !isDiffDeleted()) {
+ displayString(os, "Index: ");
+ displayString(os, path);
+ displayString(os, " (deleted)");
+ displayEOL(os);
+ displayString(os, HEADER_SEPARATOR);
+ displayEOL(os);
+ return true;
+ }
+ if (added && !isDiffAdded()) {
+ displayString(os, "Index: ");
+ displayString(os, path);
+ displayString(os, " (added)");
+ displayEOL(os);
+ displayString(os, HEADER_SEPARATOR);
+ displayEOL(os);
+ return true;
+ }
+ displayString(os, "Index: ");
+ displayString(os, path);
+ displayEOL(os);
+ displayString(os, HEADER_SEPARATOR);
+ displayEOL(os);
+ return false;
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ return false;
+ }
+
+ protected void displayHeaderFields(OutputStream os, String label1, String label2) throws SVNException {
+ try {
+ displayString(os, "--- ");
+ displayString(os, label1);
+ displayEOL(os);
+ displayString(os, "+++ ");
+ displayString(os, label2);
+ displayEOL(os);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private void displayPropertyChangesOn(String path, OutputStream outputStream) throws SVNException {
+ try {
+ displayEOL(outputStream);
+ displayString(outputStream, ("Property changes on: " + (useLocalFileSeparatorChar() ? path.replace('/', File.separatorChar) : path)));
+ displayEOL(outputStream);
+ displayString(outputStream, PROPERTIES_SEPARATOR);
+ displayEOL(outputStream);
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private byte[] getPropertyAsBytes(SVNPropertyValue value, String encoding) {
+ if (value == null) {
+ return null;
+ }
+ if (value.isString()) {
+ try {
+ return value.getString().getBytes(encoding);
+ } catch (UnsupportedEncodingException e) {
+ return value.getString().getBytes();
+ }
+ }
+ return value.getBytes();
+ }
+
+ private void displayMergeInfoDiff(OutputStream outputStream, String oldValue, String newValue) throws SVNException, IOException {
+ Map oldMergeInfo = null;
+ Map newMergeInfo = null;
+ if (oldValue != null) {
+ oldMergeInfo = SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(oldValue), null);
+ }
+ if (newValue != null) {
+ newMergeInfo = SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(newValue), null);
+ }
+
+ Map deleted = new TreeMap();
+ Map added = new TreeMap();
+ SVNMergeInfoUtil.diffMergeInfo(deleted, added, oldMergeInfo, newMergeInfo, true);
+
+ for (Iterator paths = deleted.keySet().iterator(); paths.hasNext(); ) {
+ String path = (String) paths.next();
+ SVNMergeRangeList rangeList = (SVNMergeRangeList) deleted.get(path);
+ displayString(outputStream, (" Reverse-merged " + path + ":r"));
+ displayString(outputStream, rangeList.toString());
+ displayEOL(outputStream);
+ }
+
+ for (Iterator paths = added.keySet().iterator(); paths.hasNext(); ) {
+ String path = (String) paths.next();
+ SVNMergeRangeList rangeList = (SVNMergeRangeList) added.get(path);
+ displayString(outputStream, (" Merged " + path + ":r"));
+ displayString(outputStream, rangeList.toString());
+ displayEOL(outputStream);
+ }
+ }
+
+ private boolean useLocalFileSeparatorChar() {
+ return true;
+ }
+
+ public boolean isDiffDeleted() {
+ return diffDeleted;
+ }
+
+ public boolean isDiffAdded() {
+ return diffAdded;
+ }
+
+ private void wrapException(IOException e) throws SVNException {
+ SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, e);
+ SVNErrorManager.error(errorMessage, e, SVNLogType.WC);
+ }
+
+ private void displayString(OutputStream outputStream, String s) throws IOException {
+ outputStream.write(s.getBytes(HEADER_ENCODING));
+ }
+
+ private void displayEOL(OutputStream os) throws IOException {
+ os.write(getEOL());
+ }
+
+ public SVNDiffOptions getDiffOptions() {
+ if (diffOptions == null) {
+ diffOptions = new SVNDiffOptions();
+ }
+ return diffOptions;
+ }
+
+ public void setExternalDiffCommand(String externalDiffCommand) {
+ this.externalDiffCommand = externalDiffCommand;
+ }
+
+ public void setRawDiffOptions(List rawDiffOptions) {
+ this.rawDiffOptions = rawDiffOptions;
+ }
+
+ public void setDiffOptions(SVNDiffOptions diffOptions) {
+ this.diffOptions = diffOptions;
+ }
+
+ public void setDiffDeleted(boolean diffDeleted) {
+ this.diffDeleted = diffDeleted;
+ }
+
+ public void setDiffAdded(boolean diffAdded) {
+ this.diffAdded = diffAdded;
+ }
+
+ public void setBasePath(File absoluteFile) {
+ setBaseTarget(SvnTarget.fromFile(absoluteFile));
+ }
+
+ public void setFallbackToAbsolutePath(boolean fallbackToAbsolutePath) {
+ this.fallbackToAbsolutePath = fallbackToAbsolutePath;
+ }
+
+ public void setOptions(ISVNOptions options) {
+ this.options = options;
+ }
+
+ public ISVNOptions getOptions() {
+ return options;
+ }
+
+ private class EmptyDetectionOutputStream extends OutputStream {
+
+ private final OutputStream outputStream;
+ private boolean somethingWritten;
+
+ public EmptyDetectionOutputStream(OutputStream outputStream) {
+ this.outputStream = outputStream;
+ this.somethingWritten = false;
+ }
+
+ public boolean isSomethingWritten() {
+ return somethingWritten;
+ }
+
+ @Override
+ public void write(int c) throws IOException {
+ somethingWritten = true;
+ outputStream.write(c);
+ }
+
+ @Override
+ public void write(byte[] bytes) throws IOException {
+ somethingWritten = bytes.length > 0;
+ outputStream.write(bytes);
+ }
+
+ @Override
+ public void write(byte[] bytes, int offset, int length) throws IOException {
+ somethingWritten = length > 0;
+ outputStream.write(bytes, offset, length);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ outputStream.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ outputStream.close();
+ }
+ }
+}
diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml
index 16a74549e5..94d751dff7 100644
--- a/scm-plugins/pom.xml
+++ b/scm-plugins/pom.xml
@@ -113,92 +113,35 @@
sonia.scm.maven
smp-maven-plugin
- 1.0.0-alpha-2
+ 1.0.0-alpha-3
true
- true
+
+ @scm-manager/ui-types
+ @scm-manager/ui-components
+
-
-
- fix-descriptor
- process-resources
-
- fix-descriptor
-
-
-
- append-dependencies
- process-classes
-
- append-dependencies
-
-
-
-
- com.github.sdorra
- buildfrontend-maven-plugin
-
-
- ${nodejs.version}
-
-
- YARN
- ${yarn.version}
-
- false
-
-
-
-
- install
- process-resources
-
- install
-
-
-
- build
- compile
-
- run
-
-
-
-
+
+ com.github.sdorra
+ buildfrontend-maven-plugin
+
+
+ ${nodejs.version}
+
+
+ YARN
+ ${yarn.version}
+
+ false
+
+
-
- release
-
-
-
-
- sonia.maven
- web-compressor
- 1.4
-
-
- compile
-
- compress-directory
-
-
-
-
- true
- ${project.build.directory}/classes
-
-
-
-
-
-
-
doc
diff --git a/scm-plugins/scm-git-plugin/.flowconfig b/scm-plugins/scm-git-plugin/.flowconfig
index 7ede008602..b05e157358 100644
--- a/scm-plugins/scm-git-plugin/.flowconfig
+++ b/scm-plugins/scm-git-plugin/.flowconfig
@@ -4,5 +4,6 @@
[include]
[libs]
+./node_modules/@scm-manager/ui-components/flow-typed
[options]
diff --git a/scm-plugins/scm-git-plugin/flow-typed/npm/classnames_v2.x.x.js b/scm-plugins/scm-git-plugin/flow-typed/npm/classnames_v2.x.x.js
deleted file mode 100644
index 2307243eeb..0000000000
--- a/scm-plugins/scm-git-plugin/flow-typed/npm/classnames_v2.x.x.js
+++ /dev/null
@@ -1,23 +0,0 @@
-// flow-typed signature: cf86673cc32d185bdab1d2ea90578d37
-// flow-typed version: 614bf49aa8/classnames_v2.x.x/flow_>=v0.25.x
-
-type $npm$classnames$Classes =
- | string
- | { [className: string]: * }
- | false
- | void
- | null;
-
-declare module "classnames" {
- declare module.exports: (
- ...classes: Array<$npm$classnames$Classes | $npm$classnames$Classes[]>
- ) => string;
-}
-
-declare module "classnames/bind" {
- declare module.exports: $Exports<"classnames">;
-}
-
-declare module "classnames/dedupe" {
- declare module.exports: $Exports<"classnames">;
-}
diff --git a/scm-plugins/scm-git-plugin/flow-typed/npm/jest_v23.x.x.js b/scm-plugins/scm-git-plugin/flow-typed/npm/jest_v23.x.x.js
deleted file mode 100644
index 23b66b07e5..0000000000
--- a/scm-plugins/scm-git-plugin/flow-typed/npm/jest_v23.x.x.js
+++ /dev/null
@@ -1,1108 +0,0 @@
-// flow-typed signature: f5a484315a3dea13d273645306e4076a
-// flow-typed version: 7c5d14b3d4/jest_v23.x.x/flow_>=v0.39.x
-
-type JestMockFn, TReturn> = {
- (...args: TArguments): TReturn,
- /**
- * An object for introspecting mock calls
- */
- mock: {
- /**
- * An array that represents all calls that have been made into this mock
- * function. Each call is represented by an array of arguments that were
- * passed during the call.
- */
- calls: Array,
- /**
- * An array that contains all the object instances that have been
- * instantiated from this mock function.
- */
- instances: Array
- },
- /**
- * Resets all information stored in the mockFn.mock.calls and
- * mockFn.mock.instances arrays. Often this is useful when you want to clean
- * up a mock's usage data between two assertions.
- */
- mockClear(): void,
- /**
- * Resets all information stored in the mock. This is useful when you want to
- * completely restore a mock back to its initial state.
- */
- mockReset(): void,
- /**
- * Removes the mock and restores the initial implementation. This is useful
- * when you want to mock functions in certain test cases and restore the
- * original implementation in others. Beware that mockFn.mockRestore only
- * works when mock was created with jest.spyOn. Thus you have to take care of
- * restoration yourself when manually assigning jest.fn().
- */
- mockRestore(): void,
- /**
- * Accepts a function that should be used as the implementation of the mock.
- * The mock itself will still record all calls that go into and instances
- * that come from itself -- the only difference is that the implementation
- * will also be executed when the mock is called.
- */
- mockImplementation(
- fn: (...args: TArguments) => TReturn
- ): JestMockFn,
- /**
- * Accepts a function that will be used as an implementation of the mock for
- * one call to the mocked function. Can be chained so that multiple function
- * calls produce different results.
- */
- mockImplementationOnce(
- fn: (...args: TArguments) => TReturn
- ): JestMockFn,
- /**
- * Accepts a string to use in test result output in place of "jest.fn()" to
- * indicate which mock function is being referenced.
- */
- mockName(name: string): JestMockFn,
- /**
- * Just a simple sugar function for returning `this`
- */
- mockReturnThis(): void,
- /**
- * Accepts a value that will be returned whenever the mock function is called.
- */
- mockReturnValue(value: TReturn): JestMockFn,
- /**
- * Sugar for only returning a value once inside your mock
- */
- mockReturnValueOnce(value: TReturn): JestMockFn,
- /**
- * Sugar for jest.fn().mockImplementation(() => Promise.resolve(value))
- */
- mockResolvedValue(value: TReturn): JestMockFn>,
- /**
- * Sugar for jest.fn().mockImplementationOnce(() => Promise.resolve(value))
- */
- mockResolvedValueOnce(value: TReturn): JestMockFn>,
- /**
- * Sugar for jest.fn().mockImplementation(() => Promise.reject(value))
- */
- mockRejectedValue(value: TReturn): JestMockFn>,
- /**
- * Sugar for jest.fn().mockImplementationOnce(() => Promise.reject(value))
- */
- mockRejectedValueOnce(value: TReturn): JestMockFn>
-};
-
-type JestAsymmetricEqualityType = {
- /**
- * A custom Jasmine equality tester
- */
- asymmetricMatch(value: mixed): boolean
-};
-
-type JestCallsType = {
- allArgs(): mixed,
- all(): mixed,
- any(): boolean,
- count(): number,
- first(): mixed,
- mostRecent(): mixed,
- reset(): void
-};
-
-type JestClockType = {
- install(): void,
- mockDate(date: Date): void,
- tick(milliseconds?: number): void,
- uninstall(): void
-};
-
-type JestMatcherResult = {
- message?: string | (() => string),
- pass: boolean
-};
-
-type JestMatcher = (actual: any, expected: any) => JestMatcherResult;
-
-type JestPromiseType = {
- /**
- * Use rejects to unwrap the reason of a rejected promise so any other
- * matcher can be chained. If the promise is fulfilled the assertion fails.
- */
- rejects: JestExpectType,
- /**
- * Use resolves to unwrap the value of a fulfilled promise so any other
- * matcher can be chained. If the promise is rejected the assertion fails.
- */
- resolves: JestExpectType
-};
-
-/**
- * Jest allows functions and classes to be used as test names in test() and
- * describe()
- */
-type JestTestName = string | Function;
-
-/**
- * Plugin: jest-styled-components
- */
-
-type JestStyledComponentsMatcherValue =
- | string
- | JestAsymmetricEqualityType
- | RegExp
- | typeof undefined;
-
-type JestStyledComponentsMatcherOptions = {
- media?: string;
- modifier?: string;
- supports?: string;
-}
-
-type JestStyledComponentsMatchersType = {
- toHaveStyleRule(
- property: string,
- value: JestStyledComponentsMatcherValue,
- options?: JestStyledComponentsMatcherOptions
- ): void,
-};
-
-/**
- * Plugin: jest-enzyme
- */
-type EnzymeMatchersType = {
- toBeChecked(): void,
- toBeDisabled(): void,
- toBeEmpty(): void,
- toBeEmptyRender(): void,
- toBePresent(): void,
- toContainReact(element: React$Element): void,
- toExist(): void,
- toHaveClassName(className: string): void,
- toHaveHTML(html: string): void,
- toHaveProp: ((propKey: string, propValue?: any) => void) & ((props: Object) => void),
- toHaveRef(refName: string): void,
- toHaveState: ((stateKey: string, stateValue?: any) => void) & ((state: Object) => void),
- toHaveStyle: ((styleKey: string, styleValue?: any) => void) & ((style: Object) => void),
- toHaveTagName(tagName: string): void,
- toHaveText(text: string): void,
- toIncludeText(text: string): void,
- toHaveValue(value: any): void,
- toMatchElement(element: React$Element): void,
- toMatchSelector(selector: string): void
-};
-
-// DOM testing library extensions https://github.com/kentcdodds/dom-testing-library#custom-jest-matchers
-type DomTestingLibraryType = {
- toBeInTheDOM(): void,
- toHaveTextContent(content: string): void,
- toHaveAttribute(name: string, expectedValue?: string): void
-};
-
-// Jest JQuery Matchers: https://github.com/unindented/custom-jquery-matchers
-type JestJQueryMatchersType = {
- toExist(): void,
- toHaveLength(len: number): void,
- toHaveId(id: string): void,
- toHaveClass(className: string): void,
- toHaveTag(tag: string): void,
- toHaveAttr(key: string, val?: any): void,
- toHaveProp(key: string, val?: any): void,
- toHaveText(text: string | RegExp): void,
- toHaveData(key: string, val?: any): void,
- toHaveValue(val: any): void,
- toHaveCss(css: {[key: string]: any}): void,
- toBeChecked(): void,
- toBeDisabled(): void,
- toBeEmpty(): void,
- toBeHidden(): void,
- toBeSelected(): void,
- toBeVisible(): void,
- toBeFocused(): void,
- toBeInDom(): void,
- toBeMatchedBy(sel: string): void,
- toHaveDescendant(sel: string): void,
- toHaveDescendantWithText(sel: string, text: string | RegExp): void
-};
-
-
-// Jest Extended Matchers: https://github.com/jest-community/jest-extended
-type JestExtendedMatchersType = {
- /**
- * Note: Currently unimplemented
- * Passing assertion
- *
- * @param {String} message
- */
- // pass(message: string): void;
-
- /**
- * Note: Currently unimplemented
- * Failing assertion
- *
- * @param {String} message
- */
- // fail(message: string): void;
-
- /**
- * Use .toBeEmpty when checking if a String '', Array [] or Object {} is empty.
- */
- toBeEmpty(): void;
-
- /**
- * Use .toBeOneOf when checking if a value is a member of a given Array.
- * @param {Array.<*>} members
- */
- toBeOneOf(members: any[]): void;
-
- /**
- * Use `.toBeNil` when checking a value is `null` or `undefined`.
- */
- toBeNil(): void;
-
- /**
- * Use `.toSatisfy` when you want to use a custom matcher by supplying a predicate function that returns a `Boolean`.
- * @param {Function} predicate
- */
- toSatisfy(predicate: (n: any) => boolean): void;
-
- /**
- * Use `.toBeArray` when checking if a value is an `Array`.
- */
- toBeArray(): void;
-
- /**
- * Use `.toBeArrayOfSize` when checking if a value is an `Array` of size x.
- * @param {Number} x
- */
- toBeArrayOfSize(x: number): void;
-
- /**
- * Use `.toIncludeAllMembers` when checking if an `Array` contains all of the same members of a given set.
- * @param {Array.<*>} members
- */
- toIncludeAllMembers(members: any[]): void;
-
- /**
- * Use `.toIncludeAnyMembers` when checking if an `Array` contains any of the members of a given set.
- * @param {Array.<*>} members
- */
- toIncludeAnyMembers(members: any[]): void;
-
- /**
- * Use `.toSatisfyAll` when you want to use a custom matcher by supplying a predicate function that returns a `Boolean` for all values in an array.
- * @param {Function} predicate
- */
- toSatisfyAll(predicate: (n: any) => boolean): void;
-
- /**
- * Use `.toBeBoolean` when checking if a value is a `Boolean`.
- */
- toBeBoolean(): void;
-
- /**
- * Use `.toBeTrue` when checking a value is equal (===) to `true`.
- */
- toBeTrue(): void;
-
- /**
- * Use `.toBeFalse` when checking a value is equal (===) to `false`.
- */
- toBeFalse(): void;
-
- /**
- * Use .toBeDate when checking if a value is a Date.
- */
- toBeDate(): void;
-
- /**
- * Use `.toBeFunction` when checking if a value is a `Function`.
- */
- toBeFunction(): void;
-
- /**
- * Use `.toHaveBeenCalledBefore` when checking if a `Mock` was called before another `Mock`.
- *
- * Note: Required Jest version >22
- * Note: Your mock functions will have to be asynchronous to cause the timestamps inside of Jest to occur in a differentJS event loop, otherwise the mock timestamps will all be the same
- *
- * @param {Mock} mock
- */
- toHaveBeenCalledBefore(mock: JestMockFn): void;
-
- /**
- * Use `.toBeNumber` when checking if a value is a `Number`.
- */
- toBeNumber(): void;
-
- /**
- * Use `.toBeNaN` when checking a value is `NaN`.
- */
- toBeNaN(): void;
-
- /**
- * Use `.toBeFinite` when checking if a value is a `Number`, not `NaN` or `Infinity`.
- */
- toBeFinite(): void;
-
- /**
- * Use `.toBePositive` when checking if a value is a positive `Number`.
- */
- toBePositive(): void;
-
- /**
- * Use `.toBeNegative` when checking if a value is a negative `Number`.
- */
- toBeNegative(): void;
-
- /**
- * Use `.toBeEven` when checking if a value is an even `Number`.
- */
- toBeEven(): void;
-
- /**
- * Use `.toBeOdd` when checking if a value is an odd `Number`.
- */
- toBeOdd(): void;
-
- /**
- * Use `.toBeWithin` when checking if a number is in between the given bounds of: start (inclusive) and end (exclusive).
- *
- * @param {Number} start
- * @param {Number} end
- */
- toBeWithin(start: number, end: number): void;
-
- /**
- * Use `.toBeObject` when checking if a value is an `Object`.
- */
- toBeObject(): void;
-
- /**
- * Use `.toContainKey` when checking if an object contains the provided key.
- *
- * @param {String} key
- */
- toContainKey(key: string): void;
-
- /**
- * Use `.toContainKeys` when checking if an object has all of the provided keys.
- *
- * @param {Array.} keys
- */
- toContainKeys(keys: string[]): void;
-
- /**
- * Use `.toContainAllKeys` when checking if an object only contains all of the provided keys.
- *
- * @param {Array.} keys
- */
- toContainAllKeys(keys: string[]): void;
-
- /**
- * Use `.toContainAnyKeys` when checking if an object contains at least one of the provided keys.
- *
- * @param {Array.} keys
- */
- toContainAnyKeys(keys: string[]): void;
-
- /**
- * Use `.toContainValue` when checking if an object contains the provided value.
- *
- * @param {*} value
- */
- toContainValue(value: any): void;
-
- /**
- * Use `.toContainValues` when checking if an object contains all of the provided values.
- *
- * @param {Array.<*>} values
- */
- toContainValues(values: any[]): void;
-
- /**
- * Use `.toContainAllValues` when checking if an object only contains all of the provided values.
- *
- * @param {Array.<*>} values
- */
- toContainAllValues(values: any[]): void;
-
- /**
- * Use `.toContainAnyValues` when checking if an object contains at least one of the provided values.
- *
- * @param {Array.<*>} values
- */
- toContainAnyValues(values: any[]): void;
-
- /**
- * Use `.toContainEntry` when checking if an object contains the provided entry.
- *
- * @param {Array.} entry
- */
- toContainEntry(entry: [string, string]): void;
-
- /**
- * Use `.toContainEntries` when checking if an object contains all of the provided entries.
- *
- * @param {Array.>} entries
- */
- toContainEntries(entries: [string, string][]): void;
-
- /**
- * Use `.toContainAllEntries` when checking if an object only contains all of the provided entries.
- *
- * @param {Array.>} entries
- */
- toContainAllEntries(entries: [string, string][]): void;
-
- /**
- * Use `.toContainAnyEntries` when checking if an object contains at least one of the provided entries.
- *
- * @param {Array.>} entries
- */
- toContainAnyEntries(entries: [string, string][]): void;
-
- /**
- * Use `.toBeExtensible` when checking if an object is extensible.
- */
- toBeExtensible(): void;
-
- /**
- * Use `.toBeFrozen` when checking if an object is frozen.
- */
- toBeFrozen(): void;
-
- /**
- * Use `.toBeSealed` when checking if an object is sealed.
- */
- toBeSealed(): void;
-
- /**
- * Use `.toBeString` when checking if a value is a `String`.
- */
- toBeString(): void;
-
- /**
- * Use `.toEqualCaseInsensitive` when checking if a string is equal (===) to another ignoring the casing of both strings.
- *
- * @param {String} string
- */
- toEqualCaseInsensitive(string: string): void;
-
- /**
- * Use `.toStartWith` when checking if a `String` starts with a given `String` prefix.
- *
- * @param {String} prefix
- */
- toStartWith(prefix: string): void;
-
- /**
- * Use `.toEndWith` when checking if a `String` ends with a given `String` suffix.
- *
- * @param {String} suffix
- */
- toEndWith(suffix: string): void;
-
- /**
- * Use `.toInclude` when checking if a `String` includes the given `String` substring.
- *
- * @param {String} substring
- */
- toInclude(substring: string): void;
-
- /**
- * Use `.toIncludeRepeated` when checking if a `String` includes the given `String` substring the correct number of times.
- *
- * @param {String} substring
- * @param {Number} times
- */
- toIncludeRepeated(substring: string, times: number): void;
-
- /**
- * Use `.toIncludeMultiple` when checking if a `String` includes all of the given substrings.
- *
- * @param {Array.} substring
- */
- toIncludeMultiple(substring: string[]): void;
-};
-
-interface JestExpectType {
- not:
- & JestExpectType
- & EnzymeMatchersType
- & DomTestingLibraryType
- & JestJQueryMatchersType
- & JestStyledComponentsMatchersType
- & JestExtendedMatchersType,
- /**
- * If you have a mock function, you can use .lastCalledWith to test what
- * arguments it was last called with.
- */
- lastCalledWith(...args: Array): void,
- /**
- * toBe just checks that a value is what you expect. It uses === to check
- * strict equality.
- */
- toBe(value: any): void,
- /**
- * Use .toBeCalledWith to ensure that a mock function was called with
- * specific arguments.
- */
- toBeCalledWith(...args: Array): void,
- /**
- * Using exact equality with floating point numbers is a bad idea. Rounding
- * means that intuitive things fail.
- */
- toBeCloseTo(num: number, delta: any): void,
- /**
- * Use .toBeDefined to check that a variable is not undefined.
- */
- toBeDefined(): void,
- /**
- * Use .toBeFalsy when you don't care what a value is, you just want to
- * ensure a value is false in a boolean context.
- */
- toBeFalsy(): void,
- /**
- * To compare floating point numbers, you can use toBeGreaterThan.
- */
- toBeGreaterThan(number: number): void,
- /**
- * To compare floating point numbers, you can use toBeGreaterThanOrEqual.
- */
- toBeGreaterThanOrEqual(number: number): void,
- /**
- * To compare floating point numbers, you can use toBeLessThan.
- */
- toBeLessThan(number: number): void,
- /**
- * To compare floating point numbers, you can use toBeLessThanOrEqual.
- */
- toBeLessThanOrEqual(number: number): void,
- /**
- * Use .toBeInstanceOf(Class) to check that an object is an instance of a
- * class.
- */
- toBeInstanceOf(cls: Class<*>): void,
- /**
- * .toBeNull() is the same as .toBe(null) but the error messages are a bit
- * nicer.
- */
- toBeNull(): void,
- /**
- * Use .toBeTruthy when you don't care what a value is, you just want to
- * ensure a value is true in a boolean context.
- */
- toBeTruthy(): void,
- /**
- * Use .toBeUndefined to check that a variable is undefined.
- */
- toBeUndefined(): void,
- /**
- * Use .toContain when you want to check that an item is in a list. For
- * testing the items in the list, this uses ===, a strict equality check.
- */
- toContain(item: any): void,
- /**
- * Use .toContainEqual when you want to check that an item is in a list. For
- * testing the items in the list, this matcher recursively checks the
- * equality of all fields, rather than checking for object identity.
- */
- toContainEqual(item: any): void,
- /**
- * Use .toEqual when you want to check that two objects have the same value.
- * This matcher recursively checks the equality of all fields, rather than
- * checking for object identity.
- */
- toEqual(value: any): void,
- /**
- * Use .toHaveBeenCalled to ensure that a mock function got called.
- */
- toHaveBeenCalled(): void,
- toBeCalled(): void;
- /**
- * Use .toHaveBeenCalledTimes to ensure that a mock function got called exact
- * number of times.
- */
- toHaveBeenCalledTimes(number: number): void,
- toBeCalledTimes(number: number): void;
- /**
- *
- */
- toHaveBeenNthCalledWith(nthCall: number, ...args: Array): void;
- nthCalledWith(nthCall: number, ...args: Array): void;
- /**
- *
- */
- toHaveReturned(): void;
- toReturn(): void;
- /**
- *
- */
- toHaveReturnedTimes(number: number): void;
- toReturnTimes(number: number): void;
- /**
- *
- */
- toHaveReturnedWith(value: any): void;
- toReturnWith(value: any): void;
- /**
- *
- */
- toHaveLastReturnedWith(value: any): void;
- lastReturnedWith(value: any): void;
- /**
- *
- */
- toHaveNthReturnedWith(nthCall: number, value: any): void;
- nthReturnedWith(nthCall: number, value: any): void;
- /**
- * Use .toHaveBeenCalledWith to ensure that a mock function was called with
- * specific arguments.
- */
- toHaveBeenCalledWith(...args: Array): void,
- toBeCalledWith(...args: Array): void,
- /**
- * Use .toHaveBeenLastCalledWith to ensure that a mock function was last called
- * with specific arguments.
- */
- toHaveBeenLastCalledWith(...args: Array): void,
- lastCalledWith(...args: Array): void,
- /**
- * Check that an object has a .length property and it is set to a certain
- * numeric value.
- */
- toHaveLength(number: number): void,
- /**
- *
- */
- toHaveProperty(propPath: string, value?: any): void,
- /**
- * Use .toMatch to check that a string matches a regular expression or string.
- */
- toMatch(regexpOrString: RegExp | string): void,
- /**
- * Use .toMatchObject to check that a javascript object matches a subset of the properties of an object.
- */
- toMatchObject(object: Object | Array