diff --git a/docs/de/user/repo/assets/repository-code-changesetDetails.png b/docs/de/user/repo/assets/repository-code-changesetDetails.png index fce2803145..d014a8c7e8 100644 Binary files a/docs/de/user/repo/assets/repository-code-changesetDetails.png and b/docs/de/user/repo/assets/repository-code-changesetDetails.png differ diff --git a/docs/de/user/repo/code.md b/docs/de/user/repo/code.md index d9f1b78d1f..f3ef602bf0 100644 --- a/docs/de/user/repo/code.md +++ b/docs/de/user/repo/code.md @@ -52,11 +52,15 @@ Die Diffs können durch Klicken auf den blauen Balken schrittweise oder vollstä Falls sich Commit Links im Format "namespace/name@commitId" in der Changeset Beschreibung befinden, werden die zu relativen SCM-Manager Links erweitert. Beispielsweise wird der Text hitchhiker/HeartOfGold@1a2b3c4 zu einem Link zu dem Commit 1a2b3c4 im Repository hitchhiker/HeartOfGold umgewandelt. -In der Diff Ansicht gibt es mehrere Buttons. -- Der erste Button von Links ermöglicht eine direkte Gegenüberstellung von den Änderungen. -- Mit der Lupe kann man die Änderungen auf den ganzen Bildschirm betrachten. -- Der nächste Button schaltet Leerzeichen und Tabs ein bzw. aus. -- Der letzte Button bringt Sie zur Quelldatei. +Für das Changeset gibt es zwei Buttons: +- Mit dem ersten Button können die Whitespaces-Änderungen ein- und ausgeblendet werden. +- Der zweite ermöglicht das Ein- und Ausblenden aller Changesets. + +Jeder Changeset Diff hat mehrere Buttons: +- Der erste Button von Links ermöglicht einen direkten Vergleich der Änderungen. +- Mit der Lupe können die Änderungen über die gesamte Breite des Fensters betrachtet werden. +- Der nächste Button schaltet Leerzeichen und Tabs ein und aus. +- Der letzte Button führt zur Quelldatei. ![Repository-Code-Changesets](assets/repository-code-changesetDetails.png) diff --git a/docs/en/user/repo/assets/repository-code-changesetDetails.png b/docs/en/user/repo/assets/repository-code-changesetDetails.png index fa35d32672..d014a8c7e8 100644 Binary files a/docs/en/user/repo/assets/repository-code-changesetDetails.png and b/docs/en/user/repo/assets/repository-code-changesetDetails.png differ diff --git a/docs/en/user/repo/code.md b/docs/en/user/repo/code.md index b65bd00449..9af97b2274 100644 --- a/docs/en/user/repo/code.md +++ b/docs/en/user/repo/code.md @@ -54,10 +54,14 @@ You can expand the diffs gradually or completely by clicking on the blue bars. If commit links formatted like "namespace/name@commitId" are used in the changeset description they will be rendered to internal links. For example the text hitchhiker/HeartOfGold@1a2b3c4 will be transformed to a link directing to the commit 1a2b3c4 of the repository hitchhiker/heartOfGold. -The details view has several buttons. +There are two buttons for the changeset: +- The first button show and hide the whitespace changes. +- The second button can collapse all changesets. + +Every changeset diff has several buttons: - The first button on the left allows a direct comparison of the changes. -- With the magnifying glass you can view the changes on the entire screen. -- The next button turns whitespaces and tabs on or off. +- With the magnifying glass you can view the changes across the width of the window. +- The next button switches spaces and tabs on and off. - The last button takes you to the source file. ![Repository-Code-Changesets](assets/repository-code-changesetDetails.png) diff --git a/gradle/changelog/added_ignore_whitespace.yaml b/gradle/changelog/added_ignore_whitespace.yaml new file mode 100644 index 0000000000..ed6722be48 --- /dev/null +++ b/gradle/changelog/added_ignore_whitespace.yaml @@ -0,0 +1,2 @@ +- type: added + description: Global button to ignore whitespaces and tabs inside of diffs diff --git a/scm-core/src/main/java/sonia/scm/repository/api/AbstractDiffCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/AbstractDiffCommandBuilder.java index 728c156a84..e28993cb9a 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/AbstractDiffCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/AbstractDiffCommandBuilder.java @@ -91,4 +91,9 @@ abstract class AbstractDiffCommandBuilder { default Optional getLimit() { return empty(); } + + default IgnoreWhitespaceLevel getIgnoreWhitespace() { + return IgnoreWhitespaceLevel.NONE; + } } diff --git a/scm-core/src/main/java/sonia/scm/repository/api/IgnoreWhitespaceLevel.java b/scm-core/src/main/java/sonia/scm/repository/api/IgnoreWhitespaceLevel.java new file mode 100644 index 0000000000..890c82ebd1 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/api/IgnoreWhitespaceLevel.java @@ -0,0 +1,31 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.api; + +public enum IgnoreWhitespaceLevel { + ALL, + NONE; +} + diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/DiffCommandRequest.java b/scm-core/src/main/java/sonia/scm/repository/spi/DiffCommandRequest.java index 9ad5035a6e..2dfd16921c 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/DiffCommandRequest.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/DiffCommandRequest.java @@ -29,6 +29,7 @@ import com.google.common.base.Strings; import lombok.EqualsAndHashCode; import sonia.scm.Validateable; import sonia.scm.repository.api.DiffFormat; +import sonia.scm.repository.api.IgnoreWhitespaceLevel; /** * @@ -44,6 +45,8 @@ public class DiffCommandRequest extends FileBaseCommandRequest private String ancestorChangeset; + private IgnoreWhitespaceLevel ignoreWhitespace; + @Override public DiffCommandRequest clone() { @@ -100,4 +103,10 @@ public class DiffCommandRequest extends FileBaseCommandRequest return ancestorChangeset; } + public IgnoreWhitespaceLevel getIgnoreWhitespaceLevel() { return ignoreWhitespace; } + + public void setIgnoreWhitespaceLevel(IgnoreWhitespaceLevel ignoreWhitespace) { + this.ignoreWhitespace = ignoreWhitespace; + } + } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java index a4b85eab1f..794dca6c65 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java @@ -28,8 +28,10 @@ import com.google.inject.assistedinject.Assisted; import jakarta.inject.Inject; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffFormatter; +import org.eclipse.jgit.diff.RawTextComparator; import org.eclipse.jgit.util.QuotedString; import sonia.scm.repository.api.DiffCommandBuilder; +import sonia.scm.repository.api.IgnoreWhitespaceLevel; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; @@ -49,13 +51,16 @@ public class GitDiffCommand extends AbstractGitCommand implements DiffCommand { @Override public DiffCommandBuilder.OutputStreamConsumer getDiffResult(DiffCommandRequest request) throws IOException { @SuppressWarnings("squid:S2095") // repository will be closed with the RepositoryService - org.eclipse.jgit.lib.Repository repository = open(); + org.eclipse.jgit.lib.Repository repository = open(); Differ.Diff diff = Differ.diff(repository, request); return output -> { try (DiffFormatter formatter = new DiffFormatter(new DequoteOutputStream(output))) { formatter.setRepository(repository); + if (request.getIgnoreWhitespaceLevel() == IgnoreWhitespaceLevel.ALL) { + formatter.setDiffComparator(RawTextComparator.WS_IGNORE_ALL); + } for (DiffEntry e : diff.getEntries()) { if (idOrPathChanged(e)) { diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResult.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResult.java index a7cd48f680..5ab23585de 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResult.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResult.java @@ -26,6 +26,7 @@ package sonia.scm.repository.spi; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffFormatter; +import org.eclipse.jgit.diff.RawTextComparator; import org.eclipse.jgit.lib.ObjectId; import sonia.scm.repository.GitUtil; import sonia.scm.repository.InternalRepositoryException; @@ -33,6 +34,7 @@ import sonia.scm.repository.Repository; import sonia.scm.repository.api.DiffFile; import sonia.scm.repository.api.DiffResult; import sonia.scm.repository.api.Hunk; +import sonia.scm.repository.api.IgnoreWhitespaceLevel; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -51,14 +53,21 @@ public class GitDiffResult implements DiffResult { private final Differ.Diff diff; private final List diffEntries; + private final IgnoreWhitespaceLevel ignoreWhitespaceLevel; private final int offset; private final Integer limit; - public GitDiffResult(Repository scmRepository, org.eclipse.jgit.lib.Repository repository, Differ.Diff diff, int offset, Integer limit) { + public GitDiffResult(Repository scmRepository, + org.eclipse.jgit.lib.Repository repository, + Differ.Diff diff, + IgnoreWhitespaceLevel ignoreWhitespaceLevel, + int offset, + Integer limit) { this.scmRepository = scmRepository; this.repository = repository; this.diff = diff; this.offset = offset; + this.ignoreWhitespaceLevel = ignoreWhitespaceLevel; this.limit = limit; this.diffEntries = diff.getEntries(); } @@ -163,6 +172,9 @@ public class GitDiffResult implements DiffResult { private String format(org.eclipse.jgit.lib.Repository repository, DiffEntry entry) { try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); DiffFormatter formatter = new DiffFormatter(baos)) { + if (ignoreWhitespaceLevel == IgnoreWhitespaceLevel.ALL) { + formatter.setDiffComparator(RawTextComparator.WS_IGNORE_ALL); + } formatter.setRepository(repository); formatter.format(entry); return baos.toString(StandardCharsets.UTF_8); @@ -170,6 +182,10 @@ public class GitDiffResult implements DiffResult { throw new InternalRepositoryException(scmRepository, "failed to format diff entry", ex); } } + } + @Override + public IgnoreWhitespaceLevel getIgnoreWhitespace() { + return ignoreWhitespaceLevel; } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResultCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResultCommand.java index 8753b20000..67bc632746 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResultCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResultCommand.java @@ -40,9 +40,16 @@ public class GitDiffResultCommand extends AbstractGitCommand implements DiffResu super(context); } - public DiffResult getDiffResult(DiffCommandRequest diffCommandRequest) throws IOException { + public DiffResult getDiffResult(DiffCommandRequest request) throws IOException { org.eclipse.jgit.lib.Repository repository = open(); - return new GitDiffResult(this.repository, repository, Differ.diff(repository, diffCommandRequest), 0, null); + return new GitDiffResult( + this.repository, + repository, + Differ.diff(repository, request), + request.getIgnoreWhitespaceLevel(), + 0, + null + ); } @Override @@ -50,7 +57,14 @@ public class GitDiffResultCommand extends AbstractGitCommand implements DiffResu org.eclipse.jgit.lib.Repository repository = open(); int offset = request.getOffset() == null ? 0 : request.getOffset(); try { - return new GitDiffResult(this.repository, repository, Differ.diff(repository, request), offset, request.getLimit()); + return new GitDiffResult( + this.repository, + repository, + Differ.diff(repository, request), + request.getIgnoreWhitespaceLevel(), + offset, + request.getLimit() + ); } catch (AmbiguousObjectException ex) { throw new NotUniqueRevisionException(Repository.class, context.getRepository().getId()); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/ModificationsComputer.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/ModificationsComputer.java index b8c19541b1..59217ad625 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/ModificationsComputer.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/ModificationsComputer.java @@ -41,6 +41,7 @@ import sonia.scm.repository.Modifications; import sonia.scm.repository.Modified; import sonia.scm.repository.Removed; import sonia.scm.repository.Renamed; +import sonia.scm.repository.api.IgnoreWhitespaceLevel; import java.io.IOException; import java.text.MessageFormat; diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommandTest.java index fea4a33f15..c3c01fb03d 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommandTest.java @@ -25,6 +25,7 @@ package sonia.scm.repository.spi; import org.junit.Test; +import sonia.scm.repository.api.IgnoreWhitespaceLevel; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -77,6 +78,20 @@ public class GitDiffCommandTest extends AbstractGitCommandTestBase { " b\n" + "+change\n"; + public static final String DIFF_IGNORE_WHITESPACE = "diff --git a/a.txt b/a.txt\n" + + "index 2f8bc28..fc3f0ba 100644\n" + + "--- a/a.txt\n" + + "+++ b/a.txt\n"; + + public static final String DIFF_WITH_WHITESPACE = "diff --git a/a.txt b/a.txt\n" + + "index 2f8bc28..fc3f0ba 100644\n" + + "--- a/a.txt\n" + + "+++ b/a.txt\n" + + "@@ -1,2 +1,2 @@\n" + + " a\n" + + "-line for blame\n" + + "+line for blame\n"; + @Test public void diffForOneRevisionShouldCreateDiff() throws IOException { GitDiffCommand gitDiffCommand = new GitDiffCommand(createContext()); @@ -97,6 +112,28 @@ public class GitDiffCommandTest extends AbstractGitCommandTestBase { assertEquals(DIFF_FILE_A + DIFF_FILE_B, output.toString()); } + @Test + public void shouldIgnoreWhiteSpace() throws IOException { + GitDiffCommand gitDiffCommand = new GitDiffCommand(createContext()); + DiffCommandRequest diffCommandRequest = new DiffCommandRequest(); + diffCommandRequest.setIgnoreWhitespaceLevel(IgnoreWhitespaceLevel.ALL); + diffCommandRequest.setRevision("whitespace"); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + gitDiffCommand.getDiffResult(diffCommandRequest).accept(output); + assertEquals(DIFF_IGNORE_WHITESPACE, output.toString()); + } + + @Test + public void shouldNotIgnoreWhiteSpace() throws IOException { + GitDiffCommand gitDiffCommand = new GitDiffCommand(createContext()); + DiffCommandRequest diffCommandRequest = new DiffCommandRequest(); + diffCommandRequest.setIgnoreWhitespaceLevel(IgnoreWhitespaceLevel.NONE); + diffCommandRequest.setRevision("whitespace"); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + gitDiffCommand.getDiffResult(diffCommandRequest).accept(output); + assertEquals(DIFF_WITH_WHITESPACE, output.toString()); + } + @Test public void diffForPathShouldCreateLimitedDiff() throws IOException { GitDiffCommand gitDiffCommand = new GitDiffCommand(createContext()); @@ -156,4 +193,9 @@ public class GitDiffCommandTest extends AbstractGitCommandTestBase { .contains("rename from b.txt") .contains("rename to b-copy.txt"); } + + @Override + protected String getZippedRepositoryResource() { + return "sonia/scm/repository/spi/scm-git-spi-whitespace-test.zip"; + } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffResultCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffResultCommandTest.java index e10b24a44d..625977a40a 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffResultCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffResultCommandTest.java @@ -28,11 +28,14 @@ import org.junit.Test; import sonia.scm.repository.api.DiffFile; import sonia.scm.repository.api.DiffResult; import sonia.scm.repository.api.Hunk; +import sonia.scm.repository.api.IgnoreWhitespaceLevel; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Iterator; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; public class GitDiffResultCommandTest extends AbstractGitCommandTestBase { @@ -162,6 +165,48 @@ public class GitDiffResultCommandTest extends AbstractGitCommandTestBase { assertThat(diffResult.getOffset()).isZero(); } + @Test + public void shouldIgnoreWhiteSpace() throws IOException { + GitDiffResultCommand gitDiffResultCommand = new GitDiffResultCommand(createContext()); + DiffResultCommandRequest diffCommandRequest = new DiffResultCommandRequest(); + diffCommandRequest.setRevision("whitespace"); + diffCommandRequest.setIgnoreWhitespaceLevel(IgnoreWhitespaceLevel.ALL); + + DiffResult diffResult = gitDiffResultCommand.getDiffResult(diffCommandRequest); + Iterator iterator = diffResult.iterator(); + + DiffFile a = iterator.next(); + Iterator hunks = a.iterator(); + + assertThat(hunks).isExhausted(); + } + + @Test + public void shouldNotIgnoreWhiteSpace() throws IOException { + GitDiffResultCommand gitDiffResultCommand = new GitDiffResultCommand(createContext()); + DiffResultCommandRequest diffCommandRequest = new DiffResultCommandRequest(); + diffCommandRequest.setRevision("whitespace"); + diffCommandRequest.setIgnoreWhitespaceLevel(IgnoreWhitespaceLevel.NONE); + + DiffResult diffResult = gitDiffResultCommand.getDiffResult(diffCommandRequest); + Iterator iterator = diffResult.iterator(); + + DiffFile a = iterator.next(); + Iterator hunks = a.iterator(); + + Hunk hunk = hunks.next(); + assertThat(hunk.getOldStart()).isEqualTo(1); + assertThat(hunk.getOldLineCount()).isEqualTo(2); + assertThat(hunk.iterator()) + .toIterable() + .extracting("content") + .containsExactly( + "a", + "line for blame", + "line for blame" + ); + } + private DiffResult createDiffResult(String s) throws IOException { return createDiffResult(s, null, null); } @@ -175,4 +220,9 @@ public class GitDiffResultCommandTest extends AbstractGitCommandTestBase { return gitDiffResultCommand.getDiffResult(diffCommandRequest); } + + @Override + protected String getZippedRepositoryResource() { + return "sonia/scm/repository/spi/scm-git-spi-whitespace-test.zip"; + } } diff --git a/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-whitespace-test.zip b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-whitespace-test.zip new file mode 100644 index 0000000000..381e679efe Binary files /dev/null and b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-whitespace-test.zip differ diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgDiffCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgDiffCommand.java index b707d778c9..89f2cf2b99 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgDiffCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgDiffCommand.java @@ -32,6 +32,7 @@ import jakarta.inject.Inject; import org.javahg.Repository; import sonia.scm.repository.api.DiffCommandBuilder; import sonia.scm.repository.api.DiffFormat; +import sonia.scm.repository.api.IgnoreWhitespaceLevel; import sonia.scm.repository.spi.javahg.HgDiffInternalCommand; import sonia.scm.web.HgUtil; @@ -82,6 +83,9 @@ public class HgDiffCommand extends AbstractCommand implements DiffCommand { cmd.git(); } String revision = HgUtil.getRevision(request.getRevision()); + if (request.getIgnoreWhitespaceLevel() == IgnoreWhitespaceLevel.ALL) { + cmd.cmdAppend("-w"); + } if (request.getAncestorChangeset() != null) { String ancestor = HgUtil.getRevision(request.getAncestorChangeset()); cmd.cmdAppend(String.format("-r ancestor(%s,%s):%s", ancestor, revision, revision)); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgDiffCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgDiffCommandTest.java index 7d68976bfb..27eb1f5637 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgDiffCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgDiffCommandTest.java @@ -27,6 +27,7 @@ package sonia.scm.repository.spi; import org.junit.Test; import sonia.scm.repository.api.DiffCommandBuilder; import sonia.scm.repository.api.DiffFormat; +import sonia.scm.repository.api.IgnoreWhitespaceLevel; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -66,7 +67,7 @@ public class HgDiffCommandTest extends AbstractHgCommandTestBase { } @Test - public void shouldNotCloseInternalStream() throws IOException { + public void shouldNotCloseInternalStream() { HgCommandContext context = spy(cmdContext); DiffCommandRequest request = new DiffCommandRequest(); request.setRevision("3049df33fdbbded08b707bac3eccd0f7b453c58b"); @@ -106,6 +107,25 @@ public class HgDiffCommandTest extends AbstractHgCommandTestBase { verify(context).close(); } + @Test + public void shouldNotIgnoreWhitespaceInDefaultDiff() throws IOException { + String content = diff(cmdContext, "2b6f8a90b33f"); + assertThat(content).contains(""" +@@ -1,2 +1,2 @@ + a +-line for blame ++line for blame"""); + } + + @Test + public void shouldIgnoreWhitespaceInDiff() throws IOException { + DiffCommandRequest request = new DiffCommandRequest(); + request.setIgnoreWhitespaceLevel(IgnoreWhitespaceLevel.ALL); + request.setRevision("2b6f8a90b33f"); + String content = diff(cmdContext, request); + assertThat(content).isEmpty(); + } + private String diff(HgCommandContext context, String revision) throws IOException { DiffCommandRequest request = new DiffCommandRequest(); request.setRevision(revision); @@ -124,4 +144,8 @@ public class HgDiffCommandTest extends AbstractHgCommandTestBase { return baos.toString(UTF_8); } + @Override + protected String getZippedRepositoryResource() { + return "sonia/scm/repository/spi/scm-hg-spi-diff-test.zip"; + } } diff --git a/scm-plugins/scm-hg-plugin/src/test/resources/sonia/scm/repository/spi/scm-hg-spi-diff-test.zip b/scm-plugins/scm-hg-plugin/src/test/resources/sonia/scm/repository/spi/scm-hg-spi-diff-test.zip new file mode 100644 index 0000000000..707d2ddd30 Binary files /dev/null and b/scm-plugins/scm-hg-plugin/src/test/resources/sonia/scm/repository/spi/scm-hg-spi-diff-test.zip differ diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnDiffCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnDiffCommand.java index a7375a6f8f..cd95e8b213 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnDiffCommand.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnDiffCommand.java @@ -34,17 +34,18 @@ import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.internal.wc2.ng.SvnNewDiffGenerator; import org.tmatesoft.svn.core.wc.SVNClientManager; import org.tmatesoft.svn.core.wc.SVNDiffClient; +import org.tmatesoft.svn.core.wc.SVNDiffOptions; import org.tmatesoft.svn.core.wc.SVNRevision; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.SvnUtil; import sonia.scm.repository.api.DiffCommandBuilder; import sonia.scm.repository.api.DiffFormat; +import sonia.scm.repository.api.IgnoreWhitespaceLevel; import sonia.scm.util.Util; public class SvnDiffCommand extends AbstractSvnCommand implements DiffCommand { - private static final Logger logger = LoggerFactory.getLogger(SvnDiffCommand.class); @@ -67,8 +68,11 @@ public class SvnDiffCommand extends AbstractSvnCommand implements DiffCommand { } clientManager = SVNClientManager.newInstance(); SVNDiffClient diffClient = clientManager.getDiffClient(); - diffClient.setDiffGenerator(new SvnNewDiffGenerator(new SCMSvnDiffGenerator())); - + SCMSvnDiffGenerator generator = new SCMSvnDiffGenerator(); + diffClient.setDiffGenerator(new SvnNewDiffGenerator(generator)); + if (request.getIgnoreWhitespaceLevel() == IgnoreWhitespaceLevel.ALL) { + generator.setDiffOptions(new SVNDiffOptions(true, true, true)); + } long currentRev = SvnUtil.getRevisionNumber(request.getRevision(), repository); diffClient.setGitDiffFormat(request.getFormat() == DiffFormat.GIT); diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnDiffCommandTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnDiffCommandTest.java index aa976dd091..330492e910 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnDiffCommandTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnDiffCommandTest.java @@ -38,10 +38,12 @@ import org.tmatesoft.svn.core.wc.SVNClientManager; import org.tmatesoft.svn.core.wc.SVNRevision; import sonia.scm.repository.RepositoryTestData; import sonia.scm.repository.api.DiffFormat; +import sonia.scm.repository.api.IgnoreWhitespaceLevel; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.Base64; import java.util.Map; @@ -64,6 +66,47 @@ class SvnDiffCommandTest { workingCopy = directory.resolve("working-copy").toFile(); } + @Test + void shouldCreateDiffForSimpleFile() throws SVNException, IOException { + createRepository(); + Path newFile = workingCopy.toPath().resolve("a.txt"); + Files.write(newFile, "Some nice content\n".getBytes()); + client.getWCClient().doAdd(newFile.toFile(), false, false, false, SVNDepth.INFINITY, false, false); + commit("add a.txt"); + + Files.write(newFile, "Some more content\n".getBytes()); + commit("modify a.txt"); + + String diff = gitDiff("2"); + + assertThat(diff).isEqualTo(""" +diff --git a/a.txt b/a.txt +--- a/a.txt ++++ b/a.txt +@@ -1 +1 @@ +-Some nice content ++Some more content +"""); + } + + @Test + void shouldIgnoreWhitespaceChanges() throws SVNException, IOException { + createRepository(); + Path newFile = workingCopy.toPath().resolve("a.txt"); + Files.write(newFile, "Some nice content\n".getBytes()); + client.getWCClient().doAdd(newFile.toFile(), false, false, false, SVNDepth.INFINITY, false, false); + commit("add a.txt"); + + Files.write(newFile, "Some nice content \n".getBytes()); + commit("modify a.txt"); + + DiffCommandRequest request = createSimpleDiffRequest("2"); + request.setIgnoreWhitespaceLevel(IgnoreWhitespaceLevel.ALL); + String diff = executeDiff(request); + + assertThat(diff).isEqualTo("diff --git a/a.txt b/a.txt\n"); + } + @Test void shouldCreateGitCompatibleDiffForSinglePropChanges() throws SVNException, IOException { createRepository(); @@ -150,16 +193,25 @@ class SvnDiffCommandTest { @Nonnull private String gitDiff(String revision) throws IOException { + DiffCommandRequest request = createSimpleDiffRequest(revision); + return executeDiff(request); + } + + private String executeDiff(DiffCommandRequest request) throws IOException { SvnDiffCommand command = createCommand(); - DiffCommandRequest request = new DiffCommandRequest(); - request.setFormat(DiffFormat.GIT); - request.setRevision(revision); ByteArrayOutputStream baos = new ByteArrayOutputStream(); command.getDiffResult(request).accept(baos); return baos.toString(); } + private static DiffCommandRequest createSimpleDiffRequest(String revision) { + DiffCommandRequest request = new DiffCommandRequest(); + request.setFormat(DiffFormat.GIT); + request.setRevision(revision); + return request; + } + private SvnDiffCommand createCommand() { return new SvnDiffCommand(new SvnContext(RepositoryTestData.createHeartOfGold(), repository)); } diff --git a/scm-ui/ui-api/src/diff.test.ts b/scm-ui/ui-api/src/diff.test.ts index c40ba9b636..5141b18deb 100644 --- a/scm-ui/ui-api/src/diff.test.ts +++ b/scm-ui/ui-api/src/diff.test.ts @@ -221,11 +221,11 @@ describe("Test diff", () => { }); it("should append query parameters to url which has already query params", async () => { - fetchMock.getOnce("/api/v2/diff?format=GIT&limit=25", { + fetchMock.getOnce("/api/v2/diff?format=GIT&limit=25&ignoreWhitespace=NONE", { body: simpleDiff, headers: { "Content-Type": "application/vnd.scmm-diffparsed+json;v=2" }, }); - const { result, waitFor } = renderHook(() => useDiff("/diff?format=GIT", { limit: 25 }), { + const { result, waitFor } = renderHook(() => useDiff("/diff?format=GIT", { limit: 25, ignoreWhitespace: "NONE" }), { wrapper: createWrapper(), }); await waitFor(() => !!result.current.data); diff --git a/scm-ui/ui-api/src/diff.ts b/scm-ui/ui-api/src/diff.ts index d97457d98c..7bdb615471 100644 --- a/scm-ui/ui-api/src/diff.ts +++ b/scm-ui/ui-api/src/diff.ts @@ -31,6 +31,7 @@ import { Diff, Link } from "@scm-manager/ui-types"; type UseDiffOptions = { limit?: number; refetchOnWindowFocus?: boolean; + ignoreWhitespace?: string; }; const defaultOptions: UseDiffOptions = { @@ -41,10 +42,10 @@ export const useDiff = (link: string, options: UseDiffOptions = defaultOptions) let initialLink = link; if (options.limit) { const separator = initialLink.includes("?") ? "&" : "?"; - initialLink = `${initialLink}${separator}limit=${options.limit}`; + initialLink = `${initialLink}${separator}limit=${options.limit}&ignoreWhitespace=${options.ignoreWhitespace}`; } const { isLoading, error, data, isFetchingNextPage, fetchNextPage } = useInfiniteQuery( - ["link", link], + ["link", link, options.ignoreWhitespace], ({ pageParam }) => { return apiClient.get(pageParam || initialLink).then((response) => { const contentType = response.headers.get("Content-Type"); diff --git a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap index 6f31c57b7d..ef3fa63b4c 100644 --- a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap +++ b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap @@ -22830,11 +22830,9 @@ exports[`Storyshots Repositories/Diff Binaries 1`] = ` alt="diff.hideContent" aria-hidden="true" className="icon" - color="inherit" - name="angle-down" >