diff options
Diffstat (limited to 'org.eclipse.jgit.test')
12 files changed, 1460 insertions, 95 deletions
diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF index 581395d90c..381a71957f 100644 --- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Bundle-ManifestVersion: 2 Bundle-Name: %Bundle-Name Automatic-Module-Name: org.eclipse.jgit.test Bundle-SymbolicName: org.eclipse.jgit.test -Bundle-Version: 6.1.1.qualifier +Bundle-Version: 6.2.1.qualifier Bundle-Localization: plugin Bundle-Vendor: %Bundle-Vendor Bundle-RequiredExecutionEnvironment: JavaSE-11 @@ -16,61 +16,61 @@ Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", org.apache.commons.compress.compressors.gzip;version="[1.15.0,2.0)", org.apache.commons.compress.compressors.xz;version="[1.15.0,2.0)", org.assertj.core.api;version="[3.14.0,4.0.0)", - org.eclipse.jgit.annotations;version="[6.1.1,6.2.0)", - org.eclipse.jgit.api;version="[6.1.1,6.2.0)", - org.eclipse.jgit.api.errors;version="[6.1.1,6.2.0)", - org.eclipse.jgit.archive;version="[6.1.1,6.2.0)", - org.eclipse.jgit.attributes;version="[6.1.1,6.2.0)", - org.eclipse.jgit.awtui;version="[6.1.1,6.2.0)", - org.eclipse.jgit.blame;version="[6.1.1,6.2.0)", - org.eclipse.jgit.diff;version="[6.1.1,6.2.0)", - org.eclipse.jgit.dircache;version="[6.1.1,6.2.0)", - org.eclipse.jgit.errors;version="[6.1.1,6.2.0)", - org.eclipse.jgit.events;version="[6.1.1,6.2.0)", - org.eclipse.jgit.fnmatch;version="[6.1.1,6.2.0)", - org.eclipse.jgit.gitrepo;version="[6.1.1,6.2.0)", - org.eclipse.jgit.hooks;version="[6.1.1,6.2.0)", - org.eclipse.jgit.ignore;version="[6.1.1,6.2.0)", - org.eclipse.jgit.ignore.internal;version="[6.1.1,6.2.0)", - org.eclipse.jgit.internal;version="[6.1.1,6.2.0)", - org.eclipse.jgit.internal.diffmergetool;version="[6.1.1,6.2.0)", - org.eclipse.jgit.internal.fsck;version="[6.1.1,6.2.0)", - org.eclipse.jgit.internal.revwalk;version="[6.1.1,6.2.0)", - org.eclipse.jgit.internal.storage.dfs;version="[6.1.1,6.2.0)", - org.eclipse.jgit.internal.storage.file;version="[6.1.1,6.2.0)", - org.eclipse.jgit.internal.storage.io;version="[6.1.1,6.2.0)", - org.eclipse.jgit.internal.storage.pack;version="[6.1.1,6.2.0)", - org.eclipse.jgit.internal.storage.reftable;version="[6.1.1,6.2.0)", - org.eclipse.jgit.internal.transport.connectivity;version="[6.1.1,6.2.0)", - org.eclipse.jgit.internal.transport.http;version="[6.1.1,6.2.0)", - org.eclipse.jgit.internal.transport.parser;version="[6.1.1,6.2.0)", - org.eclipse.jgit.internal.transport.ssh;version="[6.1.1,6.2.0)", - org.eclipse.jgit.junit;version="[6.1.1,6.2.0)", - org.eclipse.jgit.junit.time;version="[6.1.1,6.2.0)", - org.eclipse.jgit.lfs;version="[6.1.1,6.2.0)", - org.eclipse.jgit.lib;version="[6.1.1,6.2.0)", - org.eclipse.jgit.lib.internal;version="[6.1.1,6.2.0)", - org.eclipse.jgit.logging;version="[6.1.1,6.2.0)", - org.eclipse.jgit.merge;version="[6.1.1,6.2.0)", - org.eclipse.jgit.nls;version="[6.1.1,6.2.0)", - org.eclipse.jgit.notes;version="[6.1.1,6.2.0)", - org.eclipse.jgit.patch;version="[6.1.1,6.2.0)", - org.eclipse.jgit.pgm;version="[6.1.1,6.2.0)", - org.eclipse.jgit.pgm.internal;version="[6.1.1,6.2.0)", - org.eclipse.jgit.revplot;version="[6.1.1,6.2.0)", - org.eclipse.jgit.revwalk;version="[6.1.1,6.2.0)", - org.eclipse.jgit.revwalk.filter;version="[6.1.1,6.2.0)", - org.eclipse.jgit.storage.file;version="[6.1.1,6.2.0)", - org.eclipse.jgit.storage.pack;version="[6.1.1,6.2.0)", - org.eclipse.jgit.submodule;version="[6.1.1,6.2.0)", - org.eclipse.jgit.transport;version="[6.1.1,6.2.0)", - org.eclipse.jgit.transport.http;version="[6.1.1,6.2.0)", - org.eclipse.jgit.transport.resolver;version="[6.1.1,6.2.0)", - org.eclipse.jgit.treewalk;version="[6.1.1,6.2.0)", - org.eclipse.jgit.treewalk.filter;version="[6.1.1,6.2.0)", - org.eclipse.jgit.util;version="[6.1.1,6.2.0)", - org.eclipse.jgit.util.io;version="[6.1.1,6.2.0)", - org.eclipse.jgit.util.sha1;version="[6.1.1,6.2.0)", + org.eclipse.jgit.annotations;version="[6.2.1,6.3.0)", + org.eclipse.jgit.api;version="[6.2.1,6.3.0)", + org.eclipse.jgit.api.errors;version="[6.2.1,6.3.0)", + org.eclipse.jgit.archive;version="[6.2.1,6.3.0)", + org.eclipse.jgit.attributes;version="[6.2.1,6.3.0)", + org.eclipse.jgit.awtui;version="[6.2.1,6.3.0)", + org.eclipse.jgit.blame;version="[6.2.1,6.3.0)", + org.eclipse.jgit.diff;version="[6.2.1,6.3.0)", + org.eclipse.jgit.dircache;version="[6.2.1,6.3.0)", + org.eclipse.jgit.errors;version="[6.2.1,6.3.0)", + org.eclipse.jgit.events;version="[6.2.1,6.3.0)", + org.eclipse.jgit.fnmatch;version="[6.2.1,6.3.0)", + org.eclipse.jgit.gitrepo;version="[6.2.1,6.3.0)", + org.eclipse.jgit.hooks;version="[6.2.1,6.3.0)", + org.eclipse.jgit.ignore;version="[6.2.1,6.3.0)", + org.eclipse.jgit.ignore.internal;version="[6.2.1,6.3.0)", + org.eclipse.jgit.internal;version="[6.2.1,6.3.0)", + org.eclipse.jgit.internal.diffmergetool;version="[6.2.1,6.3.0)", + org.eclipse.jgit.internal.fsck;version="[6.2.1,6.3.0)", + org.eclipse.jgit.internal.revwalk;version="[6.2.1,6.3.0)", + org.eclipse.jgit.internal.storage.dfs;version="[6.2.1,6.3.0)", + org.eclipse.jgit.internal.storage.file;version="[6.2.1,6.3.0)", + org.eclipse.jgit.internal.storage.io;version="[6.2.1,6.3.0)", + org.eclipse.jgit.internal.storage.pack;version="[6.2.1,6.3.0)", + org.eclipse.jgit.internal.storage.reftable;version="[6.2.1,6.3.0)", + org.eclipse.jgit.internal.transport.connectivity;version="[6.2.1,6.3.0)", + org.eclipse.jgit.internal.transport.http;version="[6.2.1,6.3.0)", + org.eclipse.jgit.internal.transport.parser;version="[6.2.1,6.3.0)", + org.eclipse.jgit.internal.transport.ssh;version="[6.2.1,6.3.0)", + org.eclipse.jgit.junit;version="[6.2.1,6.3.0)", + org.eclipse.jgit.junit.time;version="[6.2.1,6.3.0)", + org.eclipse.jgit.lfs;version="[6.2.1,6.3.0)", + org.eclipse.jgit.lib;version="[6.2.1,6.3.0)", + org.eclipse.jgit.lib.internal;version="[6.2.1,6.3.0)", + org.eclipse.jgit.logging;version="[6.2.1,6.3.0)", + org.eclipse.jgit.merge;version="[6.2.1,6.3.0)", + org.eclipse.jgit.nls;version="[6.2.1,6.3.0)", + org.eclipse.jgit.notes;version="[6.2.1,6.3.0)", + org.eclipse.jgit.patch;version="[6.2.1,6.3.0)", + org.eclipse.jgit.pgm;version="[6.2.1,6.3.0)", + org.eclipse.jgit.pgm.internal;version="[6.2.1,6.3.0)", + org.eclipse.jgit.revplot;version="[6.2.1,6.3.0)", + org.eclipse.jgit.revwalk;version="[6.2.1,6.3.0)", + org.eclipse.jgit.revwalk.filter;version="[6.2.1,6.3.0)", + org.eclipse.jgit.storage.file;version="[6.2.1,6.3.0)", + org.eclipse.jgit.storage.pack;version="[6.2.1,6.3.0)", + org.eclipse.jgit.submodule;version="[6.2.1,6.3.0)", + org.eclipse.jgit.transport;version="[6.2.1,6.3.0)", + org.eclipse.jgit.transport.http;version="[6.2.1,6.3.0)", + org.eclipse.jgit.transport.resolver;version="[6.2.1,6.3.0)", + org.eclipse.jgit.treewalk;version="[6.2.1,6.3.0)", + org.eclipse.jgit.treewalk.filter;version="[6.2.1,6.3.0)", + org.eclipse.jgit.util;version="[6.2.1,6.3.0)", + org.eclipse.jgit.util.io;version="[6.2.1,6.3.0)", + org.eclipse.jgit.util.sha1;version="[6.2.1,6.3.0)", org.hamcrest;version="[1.1.0,3.0.0)", org.hamcrest.collection;version="[1.1.0,3.0.0)", org.junit;version="[4.13,5.0.0)", diff --git a/org.eclipse.jgit.test/pom.xml b/org.eclipse.jgit.test/pom.xml index 6b66919894..efba3e0ed9 100644 --- a/org.eclipse.jgit.test/pom.xml +++ b/org.eclipse.jgit.test/pom.xml @@ -19,7 +19,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>6.1.1-SNAPSHOT</version> + <version>6.2.1-SNAPSHOT</version> </parent> <artifactId>org.eclipse.jgit.test</artifactId> diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java index b608afa5c7..3ec454cfc3 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java @@ -116,6 +116,53 @@ public class FetchCommandTest extends RepositoryTestCase { } @Test + public void testFetchSimpleNegativeRefSpec() throws Exception { + remoteGit.commit().setMessage("commit").call(); + + FetchResult res = git.fetch().setRemote("test") + .setRefSpecs("refs/heads/master:refs/heads/test", + "^:refs/heads/test") + .call(); + assertNull(res.getTrackingRefUpdate("refs/heads/test")); + + res = git.fetch().setRemote("test") + .setRefSpecs("refs/heads/master:refs/heads/test", + "^refs/heads/master") + .call(); + assertNull(res.getTrackingRefUpdate("refs/heads/test")); + } + + @Test + public void negativeRefSpecFilterBySource() throws Exception { + remoteGit.commit().setMessage("commit").call(); + remoteGit.branchCreate().setName("test").call(); + remoteGit.commit().setMessage("commit1").call(); + remoteGit.branchCreate().setName("dev").call(); + + FetchResult res = git.fetch().setRemote("test") + .setRefSpecs("refs/*:refs/origins/*", "^refs/*/test") + .call(); + assertNotNull(res.getTrackingRefUpdate("refs/origins/heads/master")); + assertNull(res.getTrackingRefUpdate("refs/origins/heads/test")); + assertNotNull(res.getTrackingRefUpdate("refs/origins/heads/dev")); + } + + @Test + public void negativeRefSpecFilterByDestination() throws Exception { + remoteGit.commit().setMessage("commit").call(); + remoteGit.branchCreate().setName("meta").call(); + remoteGit.commit().setMessage("commit1").call(); + remoteGit.branchCreate().setName("data").call(); + + FetchResult res = git.fetch().setRemote("test") + .setRefSpecs("refs/*:refs/secret/*", "^:refs/secret/*/meta") + .call(); + assertNotNull(res.getTrackingRefUpdate("refs/secret/heads/master")); + assertNull(res.getTrackingRefUpdate("refs/secret/heads/meta")); + assertNotNull(res.getTrackingRefUpdate("refs/secret/heads/data")); + } + + @Test public void fetchAddsBranches() throws Exception { final String branch1 = "b1"; final String branch2 = "b2"; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LsRemoteCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LsRemoteCommandTest.java index 12ec2aae57..05af175cfa 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LsRemoteCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LsRemoteCommandTest.java @@ -21,6 +21,8 @@ import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.util.SystemReader; import org.junit.Test; public class LsRemoteCommandTest extends RepositoryTestCase { @@ -107,6 +109,20 @@ public class LsRemoteCommandTest extends RepositoryTestCase { } @Test + public void testLsRemoteWithoutLocalRepositoryUrlInsteadOf() + throws Exception { + String uri = fileUri(); + StoredConfig userConfig = SystemReader.getInstance().getUserConfig(); + userConfig.load(); + userConfig.setString("url", uri, "insteadOf", "file:///foo"); + userConfig.save(); + Collection<Ref> refs = Git.lsRemoteRepository().setRemote("file:///foo") + .setHeads(true).call(); + assertNotNull(refs); + assertEquals(2, refs.size()); + } + + @Test public void testLsRemoteWithSymRefs() throws Exception { File directory = createTempDirectory("testRepository"); CloneCommand command = Git.cloneRepository(); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java index 64475f5d50..917b6c3297 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java @@ -36,6 +36,7 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryState; import org.eclipse.jgit.lib.Sets; +import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.merge.ContentMergeStrategy; import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason; @@ -2018,6 +2019,73 @@ public class MergeCommandTest extends RepositoryTestCase { } } + @Test + public void testMergeConflictWithMessageAndCommentChar() throws Exception { + try (Git git = new Git(db)) { + writeTrashFile("a", "1\na\n3\n"); + git.add().addFilepattern("a").call(); + RevCommit initialCommit = git.commit().setMessage("initial").call(); + + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + + writeTrashFile("a", "1\na(side)\n3\n"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("side").call(); + + checkoutBranch("refs/heads/master"); + + writeTrashFile("a", "1\na(main)\n3\n"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("main").call(); + + StoredConfig config = db.getConfig(); + config.setString("core", null, "commentChar", "^"); + + Ref sideBranch = db.exactRef("refs/heads/side"); + + git.merge().include(sideBranch).setStrategy(MergeStrategy.RESOLVE) + .setMessage("user message").call(); + + assertEquals("user message\n\n^ Conflicts:\n^\ta\n", + db.readMergeCommitMsg()); + } + } + + @Test + public void testMergeConflictWithMessageAndCommentCharAuto() + throws Exception { + try (Git git = new Git(db)) { + writeTrashFile("a", "1\na\n3\n"); + git.add().addFilepattern("a").call(); + RevCommit initialCommit = git.commit().setMessage("initial").call(); + + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + + writeTrashFile("a", "1\na(side)\n3\n"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("side").call(); + + checkoutBranch("refs/heads/master"); + + writeTrashFile("a", "1\na(main)\n3\n"); + git.add().addFilepattern("a").call(); + git.commit().setMessage("main").call(); + + StoredConfig config = db.getConfig(); + config.setString("core", null, "commentChar", "auto"); + + Ref sideBranch = db.exactRef("refs/heads/side"); + + git.merge().include(sideBranch).setStrategy(MergeStrategy.RESOLVE) + .setMessage("#user message").call(); + + assertEquals("#user message\n\n; Conflicts:\n;\ta\n", + db.readMergeCommitMsg()); + } + } + private static void setExecutable(Git git, String path, boolean executable) { FS.DETECTED.setExecute( new File(git.getRepository().getWorkTree(), path), executable); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java index c64ff0b1c3..d574e45f6f 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java @@ -30,6 +30,7 @@ import java.util.List; import org.eclipse.jgit.api.MergeResult.MergeStatus; import org.eclipse.jgit.api.RebaseCommand.InteractiveHandler; +import org.eclipse.jgit.api.RebaseCommand.InteractiveHandler2; import org.eclipse.jgit.api.RebaseCommand.Operation; import org.eclipse.jgit.api.RebaseResult.Status; import org.eclipse.jgit.api.errors.InvalidRebaseStepException; @@ -46,6 +47,7 @@ import org.eclipse.jgit.events.ChangeRecorder; import org.eclipse.jgit.events.ListenerHandle; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.lib.AbbreviatedObjectId; +import org.eclipse.jgit.lib.CommitConfig.CleanupMode; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; @@ -56,6 +58,7 @@ import org.eclipse.jgit.lib.RebaseTodoLine.Action; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.ReflogEntry; import org.eclipse.jgit.lib.RepositoryState; +import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevSort; @@ -3410,6 +3413,99 @@ public class RebaseCommandTest extends RepositoryTestCase { } + @Test + public void testInteractiveRebaseSquashFixupSequence() throws Exception { + // create file1, add and commit + writeTrashFile(FILE1, "file1"); + git.add().addFilepattern(FILE1).call(); + git.commit().setMessage("commit1").call(); + + // modify file1, add and commit + writeTrashFile(FILE1, "modified file1"); + git.add().addFilepattern(FILE1).call(); + git.commit().setMessage("commit2").call(); + + // modify file1, add and commit + writeTrashFile(FILE1, "modified file1 a second time"); + git.add().addFilepattern(FILE1).call(); + // Make it difficult; use git standard comment characters in the commit + // messages + git.commit().setMessage("#commit3").call(); + + // modify file1, add and commit + writeTrashFile(FILE1, "modified file1 a third time"); + git.add().addFilepattern(FILE1).call(); + git.commit().setMessage("@commit4").call(); + + // modify file1, add and commit + writeTrashFile(FILE1, "modified file1 a fourth time"); + git.add().addFilepattern(FILE1).call(); + git.commit().setMessage(";commit5").call(); + + StoredConfig config = git.getRepository().getConfig(); + config.setString("core", null, "commentChar", "auto"); + // With "auto", we should end up with '@' being used as comment + // character (commit4 is skipped, so it should not advance the + // character). + RebaseResult result = git.rebase().setUpstream("HEAD~4") + .runInteractively(new InteractiveHandler2() { + + @Override + public void prepareSteps(List<RebaseTodoLine> steps) { + try { + steps.get(0).setAction(Action.PICK); + steps.get(1).setAction(Action.SQUASH); + steps.get(2).setAction(Action.FIXUP); + steps.get(3).setAction(Action.SQUASH); + } catch (IllegalTodoFileModification e) { + fail("unexpected exception: " + e); + } + } + + @Override + public String modifyCommitMessage(String commit) { + fail("should not be called"); + return commit; + } + + @Override + public ModifyResult editCommitMessage(String message, + CleanupMode mode, char commentChar) { + assertEquals('@', commentChar); + assertEquals("@ This is a combination of 4 commits.\n" + + "@ The first commit's message is:\n" + + "commit2\n" + + "@ This is the 2nd commit message:\n" + + "#commit3\n" + + "@ The 3rd commit message will be skipped:\n" + + "@ @commit4\n" + + "@ This is the 4th commit message:\n" + + ";commit5", message); + return new ModifyResult() { + + @Override + public String getMessage() { + return message; + } + + @Override + public CleanupMode getCleanupMode() { + return mode; + } + + @Override + public boolean shouldAddChangeId() { + return false; + } + }; + } + }).call(); + assertEquals(Status.OK, result.getStatus()); + Iterator<RevCommit> logIterator = git.log().all().call().iterator(); + String actualCommitMsg = logIterator.next().getFullMessage(); + assertEquals("commit2\n#commit3\n;commit5", actualCommitMsg); + } + private File getTodoFile() { File todoFile = new File(db.getDirectory(), GIT_REBASE_TODO); return todoFile; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalDiffToolTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalDiffToolTest.java index c9ebec7638..f69a1794ef 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalDiffToolTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalDiffToolTest.java @@ -10,21 +10,36 @@ package org.eclipse.jgit.internal.diffmergetool; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DIFFTOOL_SECTION; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DIFF_SECTION; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CMD; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_GUITOOL; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PATH; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PROMPT; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TOOL; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TRUST_EXIT_CODE; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.internal.BooleanTriState; import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.util.FS.ExecutionResult; import org.junit.Test; /** @@ -32,20 +47,119 @@ import org.junit.Test; */ public class ExternalDiffToolTest extends ExternalToolTestCase { + @Test(expected = ToolException.class) + public void testUserToolWithError() throws Exception { + String toolName = "customTool"; + + int errorReturnCode = 1; + String command = "exit " + errorReturnCode; + + FileBasedConfig config = db.getConfig(); + config.setString(CONFIG_DIFFTOOL_SECTION, toolName, CONFIG_KEY_CMD, + command); + + invokeCompare(toolName); + + fail("Expected exception to be thrown due to external tool exiting with error code: " + + errorReturnCode); + } + + @Test(expected = ToolException.class) + public void testUserToolWithCommandNotFoundError() throws Exception { + String toolName = "customTool"; + + int errorReturnCode = 127; // command not found + String command = "exit " + errorReturnCode; + + FileBasedConfig config = db.getConfig(); + config.setString(CONFIG_DIFFTOOL_SECTION, toolName, CONFIG_KEY_CMD, + command); + + invokeCompare(toolName); + fail("Expected exception to be thrown due to external tool exiting with error code: " + + errorReturnCode); + } + + @Test + public void testUserDefinedTool() throws Exception { + String command = getEchoCommand(); + + FileBasedConfig config = db.getConfig(); + String customToolName = "customTool"; + config.setString(CONFIG_DIFFTOOL_SECTION, customToolName, + CONFIG_KEY_CMD, command); + + DiffTools manager = new DiffTools(db); + + Map<String, ExternalDiffTool> tools = manager.getUserDefinedTools(); + ExternalDiffTool externalTool = tools.get(customToolName); + boolean trustExitCode = true; + manager.compare(local, remote, externalTool, trustExitCode); + + assertEchoCommandHasCorrectOutput(); + } + + @Test + public void testUserDefinedToolWithPrompt() throws Exception { + String command = getEchoCommand(); + + FileBasedConfig config = db.getConfig(); + String customToolName = "customTool"; + config.setString(CONFIG_DIFFTOOL_SECTION, customToolName, + CONFIG_KEY_CMD, command); + + DiffTools manager = new DiffTools(db); + + PromptHandler promptHandler = PromptHandler.acceptPrompt(); + MissingToolHandler noToolHandler = new MissingToolHandler(); + + manager.compare(local, remote, Optional.of(customToolName), + BooleanTriState.TRUE, false, BooleanTriState.TRUE, + promptHandler, noToolHandler); + + assertEchoCommandHasCorrectOutput(); + + List<String> actualToolPrompts = promptHandler.toolPrompts; + List<String> expectedToolPrompts = Arrays.asList("customTool"); + assertEquals("Expected a user prompt for custom tool call", + expectedToolPrompts, actualToolPrompts); + + assertEquals("Expected to no informing about missing tools", + Collections.EMPTY_LIST, noToolHandler.missingTools); + } + @Test - public void testToolNames() { + public void testUserDefinedToolWithCancelledPrompt() throws Exception { + String command = getEchoCommand(); + + FileBasedConfig config = db.getConfig(); + String customToolName = "customTool"; + config.setString(CONFIG_DIFFTOOL_SECTION, customToolName, + CONFIG_KEY_CMD, command); + DiffTools manager = new DiffTools(db); - Set<String> actualToolNames = manager.getToolNames(); - Set<String> expectedToolNames = Collections.emptySet(); - assertEquals("Incorrect set of external diff tool names", - expectedToolNames, actualToolNames); + + PromptHandler promptHandler = PromptHandler.cancelPrompt(); + MissingToolHandler noToolHandler = new MissingToolHandler(); + + Optional<ExecutionResult> result = manager.compare(local, remote, + Optional.of(customToolName), BooleanTriState.TRUE, false, + BooleanTriState.TRUE, promptHandler, noToolHandler); + assertFalse("Expected no result if user cancels the operation", + result.isPresent()); } @Test public void testAllTools() { + FileBasedConfig config = db.getConfig(); + String customToolName = "customTool"; + config.setString(CONFIG_DIFFTOOL_SECTION, customToolName, + CONFIG_KEY_CMD, "echo"); + DiffTools manager = new DiffTools(db); - Set<String> actualToolNames = manager.getAvailableTools().keySet(); + Set<String> actualToolNames = manager.getAllToolNames(); Set<String> expectedToolNames = new LinkedHashSet<>(); + expectedToolNames.add(customToolName); CommandLineDiffTool[] defaultTools = CommandLineDiffTool.values(); for (CommandLineDiffTool defaultTool : defaultTools) { String toolName = defaultTool.name(); @@ -86,11 +200,11 @@ public class ExternalDiffToolTest extends ExternalToolTestCase { config.setString(CONFIG_DIFFTOOL_SECTION, customToolname, CONFIG_KEY_PATH, "/usr/bin/echo"); config.setString(CONFIG_DIFFTOOL_SECTION, customToolname, - CONFIG_KEY_PROMPT, "--no-prompt"); + CONFIG_KEY_PROMPT, String.valueOf(false)); config.setString(CONFIG_DIFFTOOL_SECTION, customToolname, - CONFIG_KEY_GUITOOL, "--no-gui"); + CONFIG_KEY_GUITOOL, String.valueOf(false)); config.setString(CONFIG_DIFFTOOL_SECTION, customToolname, - CONFIG_KEY_TRUST_EXIT_CODE, "--no-trust-exit-code"); + CONFIG_KEY_TRUST_EXIT_CODE, String.valueOf(false)); DiffTools manager = new DiffTools(db); Set<String> actualToolNames = manager.getUserDefinedTools().keySet(); Set<String> expectedToolNames = new LinkedHashSet<>(); @@ -100,59 +214,240 @@ public class ExternalDiffToolTest extends ExternalToolTestCase { } @Test - public void testNotAvailableTools() { - DiffTools manager = new DiffTools(db); - Set<String> actualToolNames = manager.getNotAvailableTools().keySet(); - Set<String> expectedToolNames = Collections.emptySet(); - assertEquals("Incorrect set of not available external diff tools", - expectedToolNames, actualToolNames); - } + public void testCompare() throws ToolException { + String toolName = "customTool"; - @Test - public void testCompare() { - DiffTools manager = new DiffTools(db); + FileBasedConfig config = db.getConfig(); + // the default diff tool is configured without a subsection + String subsection = null; + config.setString(CONFIG_DIFF_SECTION, subsection, CONFIG_KEY_TOOL, + toolName); - String newPath = ""; - String oldPath = ""; - String newId = ""; - String oldId = ""; - String toolName = ""; - BooleanTriState prompt = BooleanTriState.UNSET; - BooleanTriState gui = BooleanTriState.UNSET; - BooleanTriState trustExitCode = BooleanTriState.UNSET; + String command = getEchoCommand(); + config.setString(CONFIG_DIFFTOOL_SECTION, toolName, CONFIG_KEY_CMD, + command); + Optional<ExecutionResult> result = invokeCompare(toolName); + assertTrue("Expected external diff tool result to be available", + result.isPresent()); int expectedCompareResult = 0; - int compareResult = manager.compare(newPath, oldPath, newId, oldId, - toolName, prompt, gui, trustExitCode); assertEquals("Incorrect compare result for external diff tool", - expectedCompareResult, compareResult); + expectedCompareResult, result.get().getRc()); } @Test public void testDefaultTool() throws Exception { + String toolName = "customTool"; + String guiToolName = "customGuiTool"; + FileBasedConfig config = db.getConfig(); // the default diff tool is configured without a subsection String subsection = null; - config.setString("diff", subsection, "tool", "customTool"); + config.setString(CONFIG_DIFF_SECTION, subsection, CONFIG_KEY_TOOL, + toolName); DiffTools manager = new DiffTools(db); - BooleanTriState gui = BooleanTriState.UNSET; + boolean gui = false; String defaultToolName = manager.getDefaultToolName(gui); assertEquals( "Expected configured difftool to be the default external diff tool", - "my_default_toolname", defaultToolName); + toolName, defaultToolName); - gui = BooleanTriState.TRUE; + gui = true; String defaultGuiToolName = manager.getDefaultToolName(gui); assertEquals( - "Expected configured difftool to be the default external diff tool", - "my_gui_tool", defaultGuiToolName); + "Expected default gui difftool to be the default tool if no gui tool is set", + toolName, defaultGuiToolName); - config.setString("diff", subsection, "guitool", "customGuiTool"); + config.setString(CONFIG_DIFF_SECTION, subsection, CONFIG_KEY_GUITOOL, + guiToolName); manager = new DiffTools(db); defaultGuiToolName = manager.getDefaultToolName(gui); assertEquals( "Expected configured difftool to be the default external diff guitool", - "my_gui_tool", defaultGuiToolName); + guiToolName, defaultGuiToolName); + } + + @Test + public void testOverridePreDefinedToolPath() { + String newToolPath = "/tmp/path/"; + + CommandLineDiffTool[] defaultTools = CommandLineDiffTool.values(); + assertTrue("Expected to find pre-defined external diff tools", + defaultTools.length > 0); + + CommandLineDiffTool overridenTool = defaultTools[0]; + String overridenToolName = overridenTool.name(); + String overridenToolPath = newToolPath + overridenToolName; + FileBasedConfig config = db.getConfig(); + config.setString(CONFIG_DIFFTOOL_SECTION, overridenToolName, + CONFIG_KEY_PATH, overridenToolPath); + + DiffTools manager = new DiffTools(db); + Map<String, ExternalDiffTool> availableTools = manager + .getPredefinedTools(true); + ExternalDiffTool externalDiffTool = availableTools + .get(overridenToolName); + String actualDiffToolPath = externalDiffTool.getPath(); + assertEquals( + "Expected pre-defined external diff tool to have overriden path", + overridenToolPath, actualDiffToolPath); + String expectedDiffToolCommand = overridenToolPath + " " + + overridenTool.getParameters(); + String actualDiffToolCommand = externalDiffTool.getCommand(); + assertEquals( + "Expected pre-defined external diff tool to have overriden command", + expectedDiffToolCommand, actualDiffToolCommand); + } + + @Test(expected = ToolException.class) + public void testUndefinedTool() throws Exception { + String toolName = "undefined"; + invokeCompare(toolName); + fail("Expected exception to be thrown due to not defined external diff tool"); + } + + @Test + public void testDefaultToolExecutionWithPrompt() throws Exception { + FileBasedConfig config = db.getConfig(); + // the default diff tool is configured without a subsection + String subsection = null; + config.setString("diff", subsection, "tool", "customTool"); + + String command = getEchoCommand(); + + config.setString("difftool", "customTool", "cmd", command); + + DiffTools manager = new DiffTools(db); + + PromptHandler promptHandler = PromptHandler.acceptPrompt(); + MissingToolHandler noToolHandler = new MissingToolHandler(); + + manager.compare(local, remote, Optional.empty(), BooleanTriState.TRUE, + false, BooleanTriState.TRUE, promptHandler, noToolHandler); + + assertEchoCommandHasCorrectOutput(); + } + + @Test + public void testNoDefaultToolName() { + DiffTools manager = new DiffTools(db); + boolean gui = false; + String defaultToolName = manager.getDefaultToolName(gui); + assertNull("Expected no default tool when none is configured", + defaultToolName); + + gui = true; + defaultToolName = manager.getDefaultToolName(gui); + assertNull("Expected no default tool when none is configured", + defaultToolName); + } + + @Test + public void testExternalToolInGitAttributes() throws Exception { + String content = "attributes:\n*.txt difftool=customTool"; + File gitattributes = writeTrashFile(".gitattributes", content); + gitattributes.deleteOnExit(); + try (TestRepository<Repository> testRepository = new TestRepository<>( + db)) { + FileBasedConfig config = db.getConfig(); + config.setString("difftool", "customTool", "cmd", "echo"); + testRepository.git().add().addFilepattern(localFile.getName()) + .call(); + + testRepository.git().add().addFilepattern(".gitattributes").call(); + + testRepository.branch("master").commit().message("first commit") + .create(); + + DiffTools manager = new DiffTools(db); + Optional<String> tool = manager + .getExternalToolFromAttributes(localFile.getName()); + assertTrue("Failed to find user defined tool", tool.isPresent()); + assertEquals("Failed to find user defined tool", "customTool", + tool.get()); + } finally { + Files.delete(gitattributes.toPath()); + } + } + + @Test + public void testNotExternalToolInGitAttributes() throws Exception { + String content = ""; + File gitattributes = writeTrashFile(".gitattributes", content); + gitattributes.deleteOnExit(); + try (TestRepository<Repository> testRepository = new TestRepository<>( + db)) { + FileBasedConfig config = db.getConfig(); + config.setString("difftool", "customTool", "cmd", "echo"); + testRepository.git().add().addFilepattern(localFile.getName()) + .call(); + + testRepository.git().add().addFilepattern(".gitattributes").call(); + + testRepository.branch("master").commit().message("first commit") + .create(); + + DiffTools manager = new DiffTools(db); + Optional<String> tool = manager + .getExternalToolFromAttributes(localFile.getName()); + assertFalse( + "Expected no external tool if no default tool is specified in .gitattributes", + tool.isPresent()); + } finally { + Files.delete(gitattributes.toPath()); + } + } + + @Test(expected = ToolException.class) + public void testNullTool() throws Exception { + DiffTools manager = new DiffTools(db); + + boolean trustExitCode = true; + ExternalDiffTool tool = null; + manager.compare(local, remote, tool, trustExitCode); + } + + @Test(expected = ToolException.class) + public void testNullToolWithPrompt() throws Exception { + DiffTools manager = new DiffTools(db); + + PromptHandler promptHandler = PromptHandler.cancelPrompt(); + MissingToolHandler noToolHandler = new MissingToolHandler(); + + Optional<String> tool = null; + manager.compare(local, remote, tool, BooleanTriState.TRUE, false, + BooleanTriState.TRUE, promptHandler, noToolHandler); + } + + private Optional<ExecutionResult> invokeCompare(String toolName) + throws ToolException { + DiffTools manager = new DiffTools(db); + + BooleanTriState prompt = BooleanTriState.UNSET; + boolean gui = false; + BooleanTriState trustExitCode = BooleanTriState.TRUE; + PromptHandler promptHandler = PromptHandler.acceptPrompt(); + MissingToolHandler noToolHandler = new MissingToolHandler(); + + Optional<ExecutionResult> result = manager.compare(local, remote, + Optional.of(toolName), prompt, gui, trustExitCode, + promptHandler, noToolHandler); + return result; + } + + private String getEchoCommand() { + return "(echo \"$LOCAL\" \"$REMOTE\") > " + + commandResult.getAbsolutePath(); + } + + private void assertEchoCommandHasCorrectOutput() throws IOException { + List<String> actualLines = Files.readAllLines(commandResult.toPath()); + String actualContent = String.join(System.lineSeparator(), actualLines); + actualLines = Arrays.asList(actualContent.split(" ")); + List<String> expectedLines = Arrays.asList(localFile.getAbsolutePath(), + remoteFile.getAbsolutePath()); + assertEquals("Dummy test tool called with unexpected arguments", + expectedLines, actualLines); } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalMergeToolTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalMergeToolTest.java new file mode 100644 index 0000000000..94b67b374b --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalMergeToolTest.java @@ -0,0 +1,433 @@ +/* + * Copyright (C) 2020-2022, Simeon Andreev <simeon.danailov.andreev@gmail.com> and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.internal.diffmergetool; + +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CMD; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_GUITOOL; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PATH; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PROMPT; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TOOL; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TRUST_EXIT_CODE; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGETOOL_SECTION; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGE_SECTION; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; + +import java.io.IOException; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.eclipse.jgit.lib.internal.BooleanTriState; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.util.FS.ExecutionResult; +import org.junit.Test; + +/** + * Testing external merge tools. + */ +public class ExternalMergeToolTest extends ExternalToolTestCase { + + @Test(expected = ToolException.class) + public void testUserToolWithError() throws Exception { + String toolName = "customTool"; + + int errorReturnCode = 1; + String command = "exit " + errorReturnCode; + + FileBasedConfig config = db.getConfig(); + config.setString(CONFIG_MERGETOOL_SECTION, toolName, CONFIG_KEY_CMD, + command); + config.setString(CONFIG_MERGETOOL_SECTION, toolName, + CONFIG_KEY_TRUST_EXIT_CODE, String.valueOf(Boolean.TRUE)); + + invokeMerge(toolName); + + fail("Expected exception to be thrown due to external tool exiting with error code: " + + errorReturnCode); + } + + @Test(expected = ToolException.class) + public void testUserToolWithCommandNotFoundError() throws Exception { + String toolName = "customTool"; + + int errorReturnCode = 127; // command not found + String command = "exit " + errorReturnCode; + + FileBasedConfig config = db.getConfig(); + config.setString(CONFIG_MERGETOOL_SECTION, toolName, CONFIG_KEY_CMD, + command); + + invokeMerge(toolName); + + fail("Expected exception to be thrown due to external tool exiting with error code: " + + errorReturnCode); + } + + @Test + public void testKdiff3() throws Exception { + assumePosixPlatform(); + + CommandLineMergeTool autoMergingTool = CommandLineMergeTool.kdiff3; + assumeMergeToolIsAvailable(autoMergingTool); + + CommandLineMergeTool tool = autoMergingTool; + PreDefinedMergeTool externalTool = new PreDefinedMergeTool(tool.name(), + tool.getPath(), tool.getParameters(true), + tool.getParameters(false), + tool.isExitCodeTrustable() ? BooleanTriState.TRUE + : BooleanTriState.FALSE); + + MergeTools manager = new MergeTools(db); + ExecutionResult result = manager.merge(local, remote, merged, null, + null, externalTool); + assertEquals("Expected merge tool to succeed", 0, result.getRc()); + + List<String> actualLines = Files.readAllLines(mergedFile.toPath()); + String actualMergeResult = String.join(System.lineSeparator(), + actualLines); + String expectedMergeResult = DEFAULT_CONTENT; + assertEquals( + "Failed to merge equal local and remote versions with pre-defined tool: " + + tool.getPath(), + expectedMergeResult, actualMergeResult); + } + + @Test + public void testUserDefinedTool() throws Exception { + String customToolName = "customTool"; + String command = getEchoCommand(); + + FileBasedConfig config = db.getConfig(); + config.setString(CONFIG_MERGETOOL_SECTION, customToolName, + CONFIG_KEY_CMD, command); + + MergeTools manager = new MergeTools(db); + Map<String, ExternalMergeTool> tools = manager.getUserDefinedTools(); + ExternalMergeTool externalTool = tools.get(customToolName); + manager.merge(local, remote, merged, base, null, externalTool); + + assertEchoCommandHasCorrectOutput(); + } + + @Test + public void testUserDefinedToolWithPrompt() throws Exception { + String customToolName = "customTool"; + String command = getEchoCommand(); + + FileBasedConfig config = db.getConfig(); + config.setString(CONFIG_MERGETOOL_SECTION, customToolName, + CONFIG_KEY_CMD, command); + + MergeTools manager = new MergeTools(db); + + PromptHandler promptHandler = PromptHandler.acceptPrompt(); + MissingToolHandler noToolHandler = new MissingToolHandler(); + + manager.merge(local, remote, merged, base, null, + Optional.of(customToolName), BooleanTriState.TRUE, false, + promptHandler, noToolHandler); + + assertEchoCommandHasCorrectOutput(); + + List<String> actualToolPrompts = promptHandler.toolPrompts; + List<String> expectedToolPrompts = Arrays.asList("customTool"); + assertEquals("Expected a user prompt for custom tool call", + expectedToolPrompts, actualToolPrompts); + + assertEquals("Expected to no informing about missing tools", + Collections.EMPTY_LIST, noToolHandler.missingTools); + } + + @Test + public void testUserDefinedToolWithCancelledPrompt() throws Exception { + MergeTools manager = new MergeTools(db); + + PromptHandler promptHandler = PromptHandler.cancelPrompt(); + MissingToolHandler noToolHandler = new MissingToolHandler(); + + Optional<ExecutionResult> result = manager.merge(local, remote, merged, + base, null, Optional.empty(), BooleanTriState.TRUE, false, + promptHandler, noToolHandler); + assertFalse("Expected no result if user cancels the operation", + result.isPresent()); + } + + @Test + public void testAllTools() { + FileBasedConfig config = db.getConfig(); + String customToolName = "customTool"; + config.setString(CONFIG_MERGETOOL_SECTION, customToolName, + CONFIG_KEY_CMD, "echo"); + + MergeTools manager = new MergeTools(db); + Set<String> actualToolNames = manager.getAllToolNames(); + Set<String> expectedToolNames = new LinkedHashSet<>(); + expectedToolNames.add(customToolName); + CommandLineMergeTool[] defaultTools = CommandLineMergeTool.values(); + for (CommandLineMergeTool defaultTool : defaultTools) { + String toolName = defaultTool.name(); + expectedToolNames.add(toolName); + } + assertEquals("Incorrect set of external merge tools", expectedToolNames, + actualToolNames); + } + + @Test + public void testOverridePredefinedToolPath() { + String toolName = CommandLineMergeTool.guiffy.name(); + String customToolPath = "/usr/bin/echo"; + + FileBasedConfig config = db.getConfig(); + config.setString(CONFIG_MERGETOOL_SECTION, toolName, CONFIG_KEY_CMD, + "echo"); + config.setString(CONFIG_MERGETOOL_SECTION, toolName, CONFIG_KEY_PATH, + customToolPath); + + MergeTools manager = new MergeTools(db); + Map<String, ExternalMergeTool> tools = manager.getUserDefinedTools(); + ExternalMergeTool mergeTool = tools.get(toolName); + assertNotNull("Expected tool \"" + toolName + "\" to be user defined", + mergeTool); + + String toolPath = mergeTool.getPath(); + assertEquals("Expected external merge tool to have an overriden path", + customToolPath, toolPath); + } + + @Test + public void testUserDefinedTools() { + FileBasedConfig config = db.getConfig(); + String customToolname = "customTool"; + config.setString(CONFIG_MERGETOOL_SECTION, customToolname, + CONFIG_KEY_CMD, "echo"); + config.setString(CONFIG_MERGETOOL_SECTION, customToolname, + CONFIG_KEY_PATH, "/usr/bin/echo"); + config.setString(CONFIG_MERGETOOL_SECTION, customToolname, + CONFIG_KEY_PROMPT, String.valueOf(false)); + config.setString(CONFIG_MERGETOOL_SECTION, customToolname, + CONFIG_KEY_GUITOOL, String.valueOf(false)); + config.setString(CONFIG_MERGETOOL_SECTION, customToolname, + CONFIG_KEY_TRUST_EXIT_CODE, String.valueOf(false)); + MergeTools manager = new MergeTools(db); + Set<String> actualToolNames = manager.getUserDefinedTools().keySet(); + Set<String> expectedToolNames = new LinkedHashSet<>(); + expectedToolNames.add(customToolname); + assertEquals("Incorrect set of external merge tools", expectedToolNames, + actualToolNames); + } + + @Test + public void testCompare() throws ToolException { + String toolName = "customTool"; + + FileBasedConfig config = db.getConfig(); + // the default merge tool is configured without a subsection + String subsection = null; + config.setString(CONFIG_MERGE_SECTION, subsection, CONFIG_KEY_TOOL, + toolName); + + String command = getEchoCommand(); + + config.setString(CONFIG_MERGETOOL_SECTION, toolName, CONFIG_KEY_CMD, + command); + + Optional<ExecutionResult> result = invokeMerge(toolName); + assertTrue("Expected external merge tool result to be available", + result.isPresent()); + int expectedCompareResult = 0; + assertEquals("Incorrect compare result for external merge tool", + expectedCompareResult, result.get().getRc()); + } + + @Test + public void testDefaultTool() throws Exception { + String toolName = "customTool"; + String guiToolName = "customGuiTool"; + + FileBasedConfig config = db.getConfig(); + // the default merge tool is configured without a subsection + String subsection = null; + config.setString(CONFIG_MERGE_SECTION, subsection, CONFIG_KEY_TOOL, + toolName); + + MergeTools manager = new MergeTools(db); + boolean gui = false; + String defaultToolName = manager.getDefaultToolName(gui); + assertEquals( + "Expected configured mergetool to be the default external merge tool", + toolName, defaultToolName); + + gui = true; + String defaultGuiToolName = manager.getDefaultToolName(gui); + assertNull("Expected default mergetool to not be set", + defaultGuiToolName); + + config.setString(CONFIG_MERGE_SECTION, subsection, CONFIG_KEY_GUITOOL, + guiToolName); + manager = new MergeTools(db); + defaultGuiToolName = manager.getDefaultToolName(gui); + assertEquals( + "Expected configured mergetool to be the default external merge guitool", + guiToolName, defaultGuiToolName); + } + + @Test + public void testOverridePreDefinedToolPath() { + String newToolPath = "/tmp/path/"; + + CommandLineMergeTool[] defaultTools = CommandLineMergeTool.values(); + assertTrue("Expected to find pre-defined external merge tools", + defaultTools.length > 0); + + CommandLineMergeTool overridenTool = defaultTools[0]; + String overridenToolName = overridenTool.name(); + String overridenToolPath = newToolPath + overridenToolName; + FileBasedConfig config = db.getConfig(); + config.setString(CONFIG_MERGETOOL_SECTION, overridenToolName, + CONFIG_KEY_PATH, overridenToolPath); + + MergeTools manager = new MergeTools(db); + Map<String, ExternalMergeTool> availableTools = manager + .getPredefinedTools(true); + ExternalMergeTool externalMergeTool = availableTools + .get(overridenToolName); + String actualMergeToolPath = externalMergeTool.getPath(); + assertEquals( + "Expected pre-defined external merge tool to have overriden path", + overridenToolPath, actualMergeToolPath); + boolean withBase = true; + String expectedMergeToolCommand = overridenToolPath + " " + + overridenTool.getParameters(withBase); + String actualMergeToolCommand = externalMergeTool.getCommand(); + assertEquals( + "Expected pre-defined external merge tool to have overriden command", + expectedMergeToolCommand, actualMergeToolCommand); + } + + @Test(expected = ToolException.class) + public void testUndefinedTool() throws Exception { + String toolName = "undefined"; + invokeMerge(toolName); + fail("Expected exception to be thrown due to not defined external merge tool"); + } + + @Test + public void testDefaultToolExecutionWithPrompt() throws Exception { + FileBasedConfig config = db.getConfig(); + // the default diff tool is configured without a subsection + String subsection = null; + config.setString("merge", subsection, "tool", "customTool"); + + String command = getEchoCommand(); + + config.setString("mergetool", "customTool", "cmd", command); + + MergeTools manager = new MergeTools(db); + + PromptHandler promptHandler = PromptHandler.acceptPrompt(); + MissingToolHandler noToolHandler = new MissingToolHandler(); + + manager.merge(local, remote, merged, base, null, Optional.empty(), + BooleanTriState.TRUE, false, promptHandler, noToolHandler); + + assertEchoCommandHasCorrectOutput(); + } + + @Test + public void testNoDefaultToolName() { + MergeTools manager = new MergeTools(db); + boolean gui = false; + String defaultToolName = manager.getDefaultToolName(gui); + assertNull("Expected no default tool when none is configured", + defaultToolName); + + gui = true; + defaultToolName = manager.getDefaultToolName(gui); + assertNull("Expected no default tool when none is configured", + defaultToolName); + } + + @Test(expected = ToolException.class) + public void testNullTool() throws Exception { + MergeTools manager = new MergeTools(db); + + PromptHandler promptHandler = null; + MissingToolHandler noToolHandler = null; + + Optional<String> tool = null; + + manager.merge(local, remote, merged, base, null, tool, + BooleanTriState.TRUE, false, promptHandler, noToolHandler); + } + + @Test(expected = ToolException.class) + public void testNullToolWithPrompt() throws Exception { + MergeTools manager = new MergeTools(db); + + PromptHandler promptHandler = PromptHandler.cancelPrompt(); + MissingToolHandler noToolHandler = new MissingToolHandler(); + + Optional<String> tool = null; + + manager.merge(local, remote, merged, base, null, tool, + BooleanTriState.TRUE, false, promptHandler, noToolHandler); + } + + private Optional<ExecutionResult> invokeMerge(String toolName) + throws ToolException { + BooleanTriState prompt = BooleanTriState.UNSET; + boolean gui = false; + + MergeTools manager = new MergeTools(db); + + PromptHandler promptHandler = PromptHandler.acceptPrompt(); + MissingToolHandler noToolHandler = new MissingToolHandler(); + + Optional<ExecutionResult> result = manager.merge(local, remote, merged, + base, null, Optional.of(toolName), prompt, gui, promptHandler, + noToolHandler); + return result; + } + + private void assumeMergeToolIsAvailable( + CommandLineMergeTool autoMergingTool) { + boolean isAvailable = ExternalToolUtils.isToolAvailable(db.getFS(), + db.getDirectory(), db.getWorkTree(), autoMergingTool.getPath()); + assumeTrue("Assuming external tool is available: " + + autoMergingTool.name(), isAvailable); + } + + private String getEchoCommand() { + return "(echo $LOCAL $REMOTE $MERGED $BASE) > " + + commandResult.getAbsolutePath(); + } + + private void assertEchoCommandHasCorrectOutput() throws IOException { + List<String> actualLines = Files.readAllLines(commandResult.toPath()); + String actualContent = String.join(System.lineSeparator(), actualLines); + actualLines = Arrays.asList(actualContent.split(" ")); + List<String> expectedLines = Arrays.asList(localFile.getAbsolutePath(), + remoteFile.getAbsolutePath(), mergedFile.getAbsolutePath(), + baseFile.getAbsolutePath()); + assertEquals("Dummy test tool called with unexpected arguments", + expectedLines, actualLines); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalToolTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalToolTestCase.java index 0cc12978a8..7a6ff46578 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalToolTestCase.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalToolTestCase.java @@ -11,6 +11,8 @@ package org.eclipse.jgit.internal.diffmergetool; import java.io.File; import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.util.FS; @@ -36,6 +38,14 @@ public abstract class ExternalToolTestCase extends RepositoryTestCase { protected File commandResult; + protected FileElement local; + + protected FileElement remote; + + protected FileElement merged; + + protected FileElement base; + @Before @Override public void setUp() throws Exception { @@ -51,6 +61,15 @@ public abstract class ExternalToolTestCase extends RepositoryTestCase { baseFile.deleteOnExit(); commandResult = writeTrashFile("commandResult.txt", ""); commandResult.deleteOnExit(); + + local = new FileElement(localFile.getAbsolutePath(), + FileElement.Type.LOCAL); + remote = new FileElement(remoteFile.getAbsolutePath(), + FileElement.Type.REMOTE); + merged = new FileElement(mergedFile.getAbsolutePath(), + FileElement.Type.MERGED); + base = new FileElement(baseFile.getAbsolutePath(), + FileElement.Type.BASE); } @After @@ -71,4 +90,39 @@ public abstract class ExternalToolTestCase extends RepositoryTestCase { "This test can run only in Linux tests", FS.DETECTED instanceof FS_POSIX); } + + protected static class PromptHandler implements PromptContinueHandler { + + private final boolean promptResult; + + final List<String> toolPrompts = new ArrayList<>(); + + private PromptHandler(boolean promptResult) { + this.promptResult = promptResult; + } + + static PromptHandler acceptPrompt() { + return new PromptHandler(true); + } + + static PromptHandler cancelPrompt() { + return new PromptHandler(false); + } + + @Override + public boolean prompt(String toolName) { + toolPrompts.add(toolName); + return promptResult; + } + } + + protected static class MissingToolHandler implements InformNoToolHandler { + + final List<String> missingTools = new ArrayList<>(); + + @Override + public void inform(List<String> toolNames) { + missingTools.addAll(toolNames); + } + } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/CommitConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/CommitConfigTest.java index d95d7814e4..7066f9d422 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/CommitConfigTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/CommitConfigTest.java @@ -11,7 +11,10 @@ package org.eclipse.jgit.lib; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.CommitConfig.CleanupMode; @@ -169,6 +172,82 @@ public class CommitConfigTest { CommitConfig.cleanText(message, CleanupMode.SCISSORS, '#')); } + @Test + public void testCommentCharDefault() throws Exception { + CommitConfig cfg = parse(""); + assertEquals('#', cfg.getCommentChar()); + assertFalse(cfg.isAutoCommentChar()); + } + + @Test + public void testCommentCharAuto() throws Exception { + CommitConfig cfg = parse("[core]\n\tcommentChar = auto\n"); + assertEquals('#', cfg.getCommentChar()); + assertTrue(cfg.isAutoCommentChar()); + } + + @Test + public void testCommentCharEmpty() throws Exception { + CommitConfig cfg = parse("[core]\n\tcommentChar =\n"); + assertEquals('#', cfg.getCommentChar()); + } + + @Test + public void testCommentCharInvalid() throws Exception { + CommitConfig cfg = parse("[core]\n\tcommentChar = \" \"\n"); + assertEquals('#', cfg.getCommentChar()); + } + + @Test + public void testCommentCharNonAscii() throws Exception { + CommitConfig cfg = parse("[core]\n\tcommentChar = รถ\n"); + assertEquals('#', cfg.getCommentChar()); + } + + @Test + public void testCommentChar() throws Exception { + CommitConfig cfg = parse("[core]\n\tcommentChar = _\n"); + assertEquals('_', cfg.getCommentChar()); + } + + @Test + public void testDetermineCommentChar() throws Exception { + String text = "A commit message\n\nBody\n"; + assertEquals('#', CommitConfig.determineCommentChar(text)); + } + + @Test + public void testDetermineCommentChar2() throws Exception { + String text = "A commit message\n\nBody\n\n# Conflicts:\n#\tfoo.txt\n"; + char ch = CommitConfig.determineCommentChar(text); + assertNotEquals('#', ch); + assertTrue(ch > ' ' && ch < 127); + } + + @Test + public void testDetermineCommentChar3() throws Exception { + String text = "A commit message\n\n;Body\n\n# Conflicts:\n#\tfoo.txt\n"; + char ch = CommitConfig.determineCommentChar(text); + assertNotEquals('#', ch); + assertNotEquals(';', ch); + assertTrue(ch > ' ' && ch < 127); + } + + @Test + public void testDetermineCommentChar4() throws Exception { + String text = "A commit message\n\nBody\n\n # Conflicts:\n\t #\tfoo.txt\n"; + char ch = CommitConfig.determineCommentChar(text); + assertNotEquals('#', ch); + assertTrue(ch > ' ' && ch < 127); + } + + @Test + public void testDetermineCommentChar5() throws Exception { + String text = "A commit message\n\nBody\n\n#a\n;b\n@c\n!d\n$\n%\n^\n&\n|\n:"; + char ch = CommitConfig.determineCommentChar(text); + assertEquals(0, ch); + } + private static CommitConfig parse(String content) throws ConfigInvalidException { Config c = new Config(); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java index b56308cb72..ef0817adb8 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java @@ -443,6 +443,26 @@ public class RefSpecTest { a.setDestination("refs/remotes/origin/*/*"); } + @Test(expected = IllegalArgumentException.class) + public void invalidNegativeAndForce() { + assertNotNull(new RefSpec("^+refs/heads/master")); + } + + @Test(expected = IllegalArgumentException.class) + public void invalidForceAndNegative() { + assertNotNull(new RefSpec("+^refs/heads/master")); + } + + @Test(expected = IllegalArgumentException.class) + public void invalidNegativeNoSrcDest() { + assertNotNull(new RefSpec("^")); + } + + @Test(expected = IllegalArgumentException.class) + public void invalidNegativeBothSrcDest() { + assertNotNull(new RefSpec("^refs/heads/*:refs/heads/*")); + } + @Test public void sourceOnlywithWildcard() { RefSpec a = new RefSpec("refs/heads/*", @@ -480,4 +500,32 @@ public class RefSpecTest { assertTrue(a.isMatching()); assertTrue(a.isForceUpdate()); } + + @Test + public void negativeRefSpecWithDest() { + RefSpec a = new RefSpec("^:refs/readonly/*"); + assertTrue(a.isNegative()); + assertNull(a.getSource()); + assertEquals(a.getDestination(), "refs/readonly/*"); + } + + // Because of some of the API's existing behavior, without a colon at the + // end of the refspec, dest will be null. + @Test + public void negativeRefSpecWithSrcAndNullDest() { + RefSpec a = new RefSpec("^refs/testdata/*"); + assertTrue(a.isNegative()); + assertNull(a.getDestination()); + assertEquals(a.getSource(), "refs/testdata/*"); + } + + // Because of some of the API's existing behavior, with a colon at the end + // of the refspec, dest will be empty. + @Test + public void negativeRefSpecWithSrcAndEmptyDest() { + RefSpec a = new RefSpec("^refs/testdata/*:"); + assertTrue(a.isNegative()); + assertTrue(a.getDestination().isEmpty()); + assertEquals(a.getSource(), "refs/testdata/*"); + } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandInputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandInputStreamTest.java new file mode 100644 index 0000000000..7ac83195fb --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandInputStreamTest.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2022 Thomas Wolf <thomas.wolf@paranor.ch> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.transport; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; + +import org.junit.Before; +import org.junit.Test; + +public class SideBandInputStreamTest { + + private StringWriter messages; + + private SideBandInputStream sideband; + + @Before + public void setup() { + messages = new StringWriter(); + } + + @Test + public void progressSingleCR() throws IOException { + init(packet("message\r")); + assertTrue(sideband.read() < 0); + assertEquals("message\r", messages.toString()); + } + + @Test + public void progressSingleLF() throws IOException { + init(packet("message\n")); + assertTrue(sideband.read() < 0); + assertEquals("message\n", messages.toString()); + } + + @Test + public void progressSingleCRLF() throws IOException { + init(packet("message\r\n")); + assertTrue(sideband.read() < 0); + assertEquals("message\r\n", messages.toString()); + } + + @Test + public void progressMultiCR() throws IOException { + init(packet("message 0%\rmessage 100%\r")); + assertTrue(sideband.read() < 0); + assertEquals("message 0%\rmessage 100%\r", messages.toString()); + } + + @Test + public void progressMultiLF() throws IOException { + init(packet("message 0%\nmessage 100%\n")); + assertTrue(sideband.read() < 0); + assertEquals("message 0%\nmessage 100%\n", messages.toString()); + } + + @Test + public void progressMultiCRLF() throws IOException { + init(packet("message 0%\r\nmessage 100%\r\n")); + assertTrue(sideband.read() < 0); + assertEquals("message 0%\r\nmessage 100%\r\n", messages.toString()); + } + + @Test + public void progressPartial() throws IOException { + init(packet("message")); + assertTrue(sideband.read() < 0); + assertEquals("", messages.toString()); + sideband.drainMessages(); + assertEquals("message\n", messages.toString()); + } + + @Test + public void progressPartialTwoCR() throws IOException { + init(packet("message") + packet("message\r")); + assertTrue(sideband.read() < 0); + assertEquals("messagemessage\r", messages.toString()); + } + + @Test + public void progressPartialTwoLF() throws IOException { + init(packet("message") + packet("message\n")); + assertTrue(sideband.read() < 0); + assertEquals("messagemessage\n", messages.toString()); + } + + @Test + public void progressPartialTwoCRLF() throws IOException { + init(packet("message") + packet("message\r\n")); + assertTrue(sideband.read() < 0); + assertEquals("messagemessage\r\n", messages.toString()); + } + + @Test + public void progressPartialThreeCR() throws IOException { + init(packet("message") + packet("message") + packet("message\r")); + assertTrue(sideband.read() < 0); + assertEquals("messagemessagemessage\r", messages.toString()); + } + + @Test + public void progressPartialThreeLF() throws IOException { + init(packet("message") + packet("message") + packet("message\n")); + assertTrue(sideband.read() < 0); + assertEquals("messagemessagemessage\n", messages.toString()); + } + + @Test + public void progressPartialThreeCRLF() throws IOException { + init(packet("message") + packet("message") + packet("message\r\n")); + assertTrue(sideband.read() < 0); + assertEquals("messagemessagemessage\r\n", messages.toString()); + } + + @Test + public void progressPartialCR() throws IOException { + init(packet("message 0%\rmessage 100%")); + assertTrue(sideband.read() < 0); + assertEquals("message 0%\r", messages.toString()); + sideband.drainMessages(); + assertEquals("message 0%\rmessage 100%\n", messages.toString()); + } + + @Test + public void progressPartialLF() throws IOException { + init(packet("message 0%\nmessage 100%")); + assertTrue(sideband.read() < 0); + assertEquals("message 0%\n", messages.toString()); + sideband.drainMessages(); + assertEquals("message 0%\nmessage 100%\n", messages.toString()); + } + + @Test + public void progressPartialCRLF() throws IOException { + init(packet("message 0%\r\nmessage 100%")); + assertTrue(sideband.read() < 0); + assertEquals("message 0%\r\n", messages.toString()); + sideband.drainMessages(); + assertEquals("message 0%\r\nmessage 100%\n", messages.toString()); + } + + @Test + public void progressPartialSplitCR() throws IOException { + init(packet("message") + "0006\001a" + packet(" 0%\rmessa") + + packet("ge 100%")); + assertEquals('a', sideband.read()); + assertEquals("", messages.toString()); + assertTrue(sideband.read() < 0); + assertEquals("message 0%\r", messages.toString()); + sideband.drainMessages(); + assertEquals("message 0%\rmessage 100%\n", messages.toString()); + } + + @Test + public void progressPartialSplitLF() throws IOException { + init(packet("message") + "0006\001a" + packet(" 0%\nmessa") + + packet("ge 100%")); + assertEquals('a', sideband.read()); + assertEquals("", messages.toString()); + assertTrue(sideband.read() < 0); + assertEquals("message 0%\n", messages.toString()); + sideband.drainMessages(); + assertEquals("message 0%\nmessage 100%\n", messages.toString()); + } + + @Test + public void progressPartialSplitCRLF() throws IOException { + init(packet("message") + "0006\001a" + packet(" 0%\r\nmessa") + + packet("ge 100%")); + assertEquals('a', sideband.read()); + assertEquals("", messages.toString()); + assertTrue(sideband.read() < 0); + assertEquals("message 0%\r\n", messages.toString()); + sideband.drainMessages(); + assertEquals("message 0%\r\nmessage 100%\n", messages.toString()); + } + + @Test + public void progressInterleaved() throws IOException { + init(packet("message 0%\r") + "0006\001a" + packet("message 10%") + + "0006\001b" + packet("\rmessage 100%\n")); + assertEquals('a', sideband.read()); + assertEquals("message 0%\r", messages.toString()); + assertEquals('b', sideband.read()); + assertEquals("message 0%\r", messages.toString()); + assertTrue(sideband.read() < 0); + assertEquals("message 0%\rmessage 10%\rmessage 100%\n", + messages.toString()); + } + + @Test + public void progressInterleavedPartial() throws IOException { + init(packet("message 0%\r") + "0006\001a" + packet("message 10%") + + "0006\001b" + packet("\rmessage 100%")); + assertEquals('a', sideband.read()); + assertEquals("message 0%\r", messages.toString()); + assertEquals('b', sideband.read()); + assertEquals("message 0%\r", messages.toString()); + assertTrue(sideband.read() < 0); + assertEquals("message 0%\rmessage 10%\r", messages.toString()); + sideband.drainMessages(); + assertEquals("message 0%\rmessage 10%\rmessage 100%\n", + messages.toString()); + } + + private String packet(String data) { + return String.format("%04x\002%s", Integer.valueOf(data.length() + 5), + data); + } + + private void init(String packets) { + InputStream rawIn = new ByteArrayInputStream( + (packets + "0000").getBytes(StandardCharsets.UTF_8)); + sideband = new SideBandInputStream(rawIn, null, messages, null); + } +} |