Add filtering with help of DirCacheCheckout.getContent()

see: https://git-scm.com/docs/git-mergetool

* refactoring of content (FileElement) handling
* now the temporary files are already filled with filtered content in
the calling classes (PGM), that can be used with EGit content too

TODO:
 * keep the temporaries when no change detected and the user answers no
to the question if the merge was successful

Bug: 356832
Change-Id: I86a0a052d059957d4d152c1bb94c262902c377d2
Signed-off-by: Andre Bossert <andre.bossert@siemens.com>
This commit is contained in:
Andre Bossert 2020-01-19 20:54:17 +01:00 committed by Andrey Loskutov
parent d128c3112d
commit e81085944f
12 changed files with 487 additions and 261 deletions

View File

@ -21,10 +21,8 @@
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.internal.diffmergetool.CommandLineDiffTool; import org.eclipse.jgit.internal.diffmergetool.CommandLineDiffTool;
import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -49,9 +47,8 @@ public void testToolWithPrompt() throws Exception {
"y", // accept launching diff tool "y", // accept launching diff tool
}; };
RevCommit commit = createUnstagedChanges(); String[] conflictingFilenames = createUnstagedChanges();
List<DiffEntry> changes = getRepositoryChanges(commit); String[] expectedOutput = getExpectedCompareOutput(conflictingFilenames);
String[] expectedOutput = getExpectedCompareOutput(changes);
String option = "--tool"; String option = "--tool";
@ -68,10 +65,9 @@ public void testToolAbortLaunch() throws Exception {
"n", // don't launch diff tool "n", // don't launch diff tool
}; };
RevCommit commit = createUnstagedChanges(); String[] conflictingFilenames = createUnstagedChanges();
List<DiffEntry> changes = getRepositoryChanges(commit);
int abortIndex = 1; int abortIndex = 1;
String[] expectedOutput = getExpectedAbortOutput(changes, abortIndex); String[] expectedOutput = getExpectedAbortOutput(conflictingFilenames, abortIndex);
String option = "--tool"; String option = "--tool";
@ -92,9 +88,8 @@ public void testNotDefinedTool() throws Exception {
@Test @Test
public void testTool() throws Exception { public void testTool() throws Exception {
RevCommit commit = createUnstagedChanges(); String[] conflictFilenames = createUnstagedChanges();
List<DiffEntry> changes = getRepositoryChanges(commit); String[] expectedOutput = getExpectedToolOutputNoPrompt(conflictFilenames);
String[] expectedOutput = getExpectedToolOutputNoPrompt(changes);
String[] options = { String[] options = {
"--tool", "--tool",
@ -111,9 +106,8 @@ public void testTool() throws Exception {
@Test @Test
public void testToolTrustExitCode() throws Exception { public void testToolTrustExitCode() throws Exception {
RevCommit commit = createUnstagedChanges(); String[] conflictingFilenames = createUnstagedChanges();
List<DiffEntry> changes = getRepositoryChanges(commit); String[] expectedOutput = getExpectedToolOutputNoPrompt(conflictingFilenames);
String[] expectedOutput = getExpectedToolOutputNoPrompt(changes);
String[] options = { "--tool", "-t", }; String[] options = { "--tool", "-t", };
@ -126,9 +120,8 @@ expectedOutput, runAndCaptureUsingInitRaw(DIFF_TOOL,
@Test @Test
public void testToolNoGuiNoPromptNoTrustExitcode() throws Exception { public void testToolNoGuiNoPromptNoTrustExitcode() throws Exception {
RevCommit commit = createUnstagedChanges(); String[] conflictingFilenames = createUnstagedChanges();
List<DiffEntry> changes = getRepositoryChanges(commit); String[] expectedOutput = getExpectedToolOutputNoPrompt(conflictingFilenames);
String[] expectedOutput = getExpectedToolOutputNoPrompt(changes);
String[] options = { "--tool", "-t", }; String[] options = { "--tool", "-t", };
@ -142,9 +135,8 @@ expectedOutput, runAndCaptureUsingInitRaw(DIFF_TOOL,
@Test @Test
public void testToolCached() throws Exception { public void testToolCached() throws Exception {
RevCommit commit = createStagedChanges(); String[] conflictingFilenames = createStagedChanges();
List<DiffEntry> changes = getRepositoryChanges(commit); String[] expectedOutput = getExpectedToolOutputNoPrompt(conflictingFilenames);
String[] expectedOutput = getExpectedToolOutputNoPrompt(changes);
String[] options = { "--cached", "--staged", }; String[] options = { "--cached", "--staged", };
@ -201,23 +193,21 @@ private void configureEchoTool(String toolName) {
String.valueOf(false)); String.valueOf(false));
} }
private static String[] getExpectedToolOutputNoPrompt(List<DiffEntry> changes) { private static String[] getExpectedToolOutputNoPrompt(String[] conflictingFilenames) {
String[] expectedToolOutput = new String[changes.size()]; String[] expectedToolOutput = new String[conflictingFilenames.length];
for (int i = 0; i < changes.size(); ++i) { for (int i = 0; i < conflictingFilenames.length; ++i) {
DiffEntry change = changes.get(i); String newPath = conflictingFilenames[i];
String newPath = change.getNewPath();
String expectedLine = newPath; String expectedLine = newPath;
expectedToolOutput[i] = expectedLine; expectedToolOutput[i] = expectedLine;
} }
return expectedToolOutput; return expectedToolOutput;
} }
private static String[] getExpectedCompareOutput(List<DiffEntry> changes) { private static String[] getExpectedCompareOutput(String[] conflictingFilenames) {
List<String> expected = new ArrayList<>(); List<String> expected = new ArrayList<>();
int n = changes.size(); int n = conflictingFilenames.length;
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
DiffEntry change = changes.get(i); String newPath = conflictingFilenames[i];
String newPath = change.getNewPath();
expected.add( expected.add(
"Viewing (" + (i + 1) + "/" + n + "): '" + newPath + "'"); "Viewing (" + (i + 1) + "/" + n + "): '" + newPath + "'");
expected.add("Launch '" + TOOL_NAME + "' [Y/n]?"); expected.add("Launch '" + TOOL_NAME + "' [Y/n]?");
@ -226,13 +216,12 @@ private static String[] getExpectedCompareOutput(List<DiffEntry> changes) {
return expected.toArray(new String[0]); return expected.toArray(new String[0]);
} }
private static String[] getExpectedAbortOutput(List<DiffEntry> changes, private static String[] getExpectedAbortOutput(String[] conflictingFilenames,
int abortIndex) { int abortIndex) {
List<String> expected = new ArrayList<>(); List<String> expected = new ArrayList<>();
int n = changes.size(); int n = conflictingFilenames.length;
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
DiffEntry change = changes.get(i); String newPath = conflictingFilenames[i];
String newPath = change.getNewPath();
expected.add( expected.add(
"Viewing (" + (i + 1) + "/" + n + "): '" + newPath + "'"); "Viewing (" + (i + 1) + "/" + n + "): '" + newPath + "'");
expected.add("Launch '" + TOOL_NAME + "' [Y/n]?"); expected.add("Launch '" + TOOL_NAME + "' [Y/n]?");

View File

@ -94,15 +94,15 @@ protected String[] runAndCaptureUsingInitRaw(InputStream inputStream,
protected String[] createMergeConflict() throws Exception { protected String[] createMergeConflict() throws Exception {
// create files on initial branch // create files on initial branch
git.checkout().setName(TEST_BRANCH_NAME).call(); git.checkout().setName(TEST_BRANCH_NAME).call();
writeTrashFile("a", "Hello world a"); writeTrashFile("dir1/a", "Hello world a");
writeTrashFile("b", "Hello world b"); writeTrashFile("dir2/b", "Hello world b");
git.add().addFilepattern(".").call(); git.add().addFilepattern(".").call();
git.commit().setMessage("files a & b added").call(); git.commit().setMessage("files a & b added").call();
// create another branch and change files // create another branch and change files
git.branchCreate().setName("branch_1").call(); git.branchCreate().setName("branch_1").call();
git.checkout().setName("branch_1").call(); git.checkout().setName("branch_1").call();
writeTrashFile("a", "Hello world a 1"); writeTrashFile("dir1/a", "Hello world a 1");
writeTrashFile("b", "Hello world b 1"); writeTrashFile("dir2/b", "Hello world b 1");
git.add().addFilepattern(".").call(); git.add().addFilepattern(".").call();
RevCommit commit1 = git.commit() RevCommit commit1 = git.commit()
.setMessage("files a & b modified commit 1").call(); .setMessage("files a & b modified commit 1").call();
@ -111,28 +111,28 @@ protected String[] createMergeConflict() throws Exception {
// create another branch and change files // create another branch and change files
git.branchCreate().setName("branch_2").call(); git.branchCreate().setName("branch_2").call();
git.checkout().setName("branch_2").call(); git.checkout().setName("branch_2").call();
writeTrashFile("a", "Hello world a 2"); writeTrashFile("dir1/a", "Hello world a 2");
writeTrashFile("b", "Hello world b 2"); writeTrashFile("dir2/b", "Hello world b 2");
git.add().addFilepattern(".").call(); git.add().addFilepattern(".").call();
git.commit().setMessage("files a & b modified commit 2").call(); git.commit().setMessage("files a & b modified commit 2").call();
// cherry-pick conflicting changes // cherry-pick conflicting changes
git.cherryPick().include(commit1).call(); git.cherryPick().include(commit1).call();
String[] conflictingFilenames = { "a", "b" }; String[] conflictingFilenames = { "dir1/a", "dir2/b" };
return conflictingFilenames; return conflictingFilenames;
} }
protected String[] createDeletedConflict() throws Exception { protected String[] createDeletedConflict() throws Exception {
// create files on initial branch // create files on initial branch
git.checkout().setName(TEST_BRANCH_NAME).call(); git.checkout().setName(TEST_BRANCH_NAME).call();
writeTrashFile("a", "Hello world a"); writeTrashFile("dir1/a", "Hello world a");
writeTrashFile("b", "Hello world b"); writeTrashFile("dir2/b", "Hello world b");
git.add().addFilepattern(".").call(); git.add().addFilepattern(".").call();
git.commit().setMessage("files a & b added").call(); git.commit().setMessage("files a & b added").call();
// create another branch and change files // create another branch and change files
git.branchCreate().setName("branch_1").call(); git.branchCreate().setName("branch_1").call();
git.checkout().setName("branch_1").call(); git.checkout().setName("branch_1").call();
writeTrashFile("a", "Hello world a 1"); writeTrashFile("dir1/a", "Hello world a 1");
writeTrashFile("b", "Hello world b 1"); writeTrashFile("dir2/b", "Hello world b 1");
git.add().addFilepattern(".").call(); git.add().addFilepattern(".").call();
RevCommit commit1 = git.commit() RevCommit commit1 = git.commit()
.setMessage("files a & b modified commit 1").call(); .setMessage("files a & b modified commit 1").call();
@ -141,29 +141,30 @@ protected String[] createDeletedConflict() throws Exception {
// create another branch and change files // create another branch and change files
git.branchCreate().setName("branch_2").call(); git.branchCreate().setName("branch_2").call();
git.checkout().setName("branch_2").call(); git.checkout().setName("branch_2").call();
git.rm().addFilepattern("a").call(); git.rm().addFilepattern("dir1/a").call();
git.rm().addFilepattern("b").call(); git.rm().addFilepattern("dir2/b").call();
git.commit().setMessage("files a & b deleted commit 2").call(); git.commit().setMessage("files a & b deleted commit 2").call();
// cherry-pick conflicting changes // cherry-pick conflicting changes
git.cherryPick().include(commit1).call(); git.cherryPick().include(commit1).call();
String[] conflictingFilenames = { "a", "b" }; String[] conflictingFilenames = { "dir1/a", "dir2/b" };
return conflictingFilenames; return conflictingFilenames;
} }
protected RevCommit createUnstagedChanges() throws Exception { protected String[] createUnstagedChanges() throws Exception {
writeTrashFile("a", "Hello world a"); writeTrashFile("dir1/a", "Hello world a");
writeTrashFile("b", "Hello world b"); writeTrashFile("dir2/b", "Hello world b");
git.add().addFilepattern(".").call(); git.add().addFilepattern(".").call();
RevCommit commit = git.commit().setMessage("files a & b").call(); git.commit().setMessage("files a & b").call();
writeTrashFile("a", "New Hello world a"); writeTrashFile("dir1/a", "New Hello world a");
writeTrashFile("b", "New Hello world b"); writeTrashFile("dir2/b", "New Hello world b");
return commit; String[] conflictingFilenames = { "dir1/a", "dir2/b" };
return conflictingFilenames;
} }
protected RevCommit createStagedChanges() throws Exception { protected String[] createStagedChanges() throws Exception {
RevCommit commit = createUnstagedChanges(); String[] conflictingFilenames = createUnstagedChanges();
git.add().addFilepattern(".").call(); git.add().addFilepattern(".").call();
return commit; return conflictingFilenames;
} }
protected List<DiffEntry> getRepositoryChanges(RevCommit commit) protected List<DiffEntry> getRepositoryChanges(RevCommit commit)

View File

@ -11,9 +11,11 @@
package org.eclipse.jgit.pgm; package org.eclipse.jgit.pgm;
import static org.eclipse.jgit.lib.Constants.HEAD; import static org.eclipse.jgit.lib.Constants.HEAD;
import static org.eclipse.jgit.treewalk.TreeWalk.OperationType.CHECKOUT_OP;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -25,27 +27,36 @@
import org.eclipse.jgit.diff.ContentSource.Pair; import org.eclipse.jgit.diff.ContentSource.Pair;
import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffEntry.Side; import org.eclipse.jgit.diff.DiffEntry.Side;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.AmbiguousObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.RevisionSyntaxException;
import org.eclipse.jgit.internal.diffmergetool.DiffTools;
import org.eclipse.jgit.internal.diffmergetool.ExternalDiffTool;
import org.eclipse.jgit.internal.diffmergetool.FileElement;
import org.eclipse.jgit.internal.diffmergetool.ToolException; import org.eclipse.jgit.internal.diffmergetool.ToolException;
import org.eclipse.jgit.internal.diffmergetool.DiffTools;
import org.eclipse.jgit.internal.diffmergetool.FileElement;
import org.eclipse.jgit.internal.diffmergetool.ExternalDiffTool;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.dircache.DirCacheCheckout;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
import org.eclipse.jgit.errors.AmbiguousObjectException;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.errors.RevisionSyntaxException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.ObjectStream;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.TextProgressMonitor; import org.eclipse.jgit.lib.TextProgressMonitor;
import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
import org.eclipse.jgit.lib.internal.BooleanTriState; import org.eclipse.jgit.lib.internal.BooleanTriState;
import org.eclipse.jgit.pgm.internal.CLIText; import org.eclipse.jgit.pgm.internal.CLIText;
import org.eclipse.jgit.pgm.opt.PathTreeFilterHandler; import org.eclipse.jgit.pgm.opt.PathTreeFilterHandler;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser; import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.WorkingTreeIterator; import org.eclipse.jgit.treewalk.WorkingTreeIterator;
import org.eclipse.jgit.treewalk.WorkingTreeOptions;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.util.StringUtils; import org.eclipse.jgit.util.StringUtils;
import org.eclipse.jgit.util.FS.ExecutionResult; import org.eclipse.jgit.util.FS.ExecutionResult;
@ -164,12 +175,6 @@ private void compare(List<DiffEntry> files, boolean showPrompt,
if (mergedFilePath.equals(DiffEntry.DEV_NULL)) { if (mergedFilePath.equals(DiffEntry.DEV_NULL)) {
mergedFilePath = ent.getOldPath(); mergedFilePath = ent.getOldPath();
} }
FileElement local = new FileElement(ent.getOldPath(),
ent.getOldId().name(),
getObjectStream(sourcePair, Side.OLD, ent));
FileElement remote = new FileElement(ent.getNewPath(),
ent.getNewId().name(),
getObjectStream(sourcePair, Side.NEW, ent));
// check if user wants to launch compare // check if user wants to launch compare
boolean launchCompare = true; boolean launchCompare = true;
if (showPrompt) { if (showPrompt) {
@ -178,15 +183,20 @@ private void compare(List<DiffEntry> files, boolean showPrompt,
} }
if (launchCompare) { if (launchCompare) {
try { try {
// TODO: check how to return the exit-code of FileElement local = createFileElement(
// the FileElement.Type.LOCAL, sourcePair, Side.OLD,
// tool ent);
// to FileElement remote = createFileElement(
// jgit / java runtime ? FileElement.Type.REMOTE, sourcePair, Side.NEW,
ent);
FileElement merged = new FileElement(mergedFilePath,
FileElement.Type.MERGED);
// TODO: check how to return the exit-code of the tool
// to jgit / java runtime ?
// int rc =... // int rc =...
ExecutionResult result = diffTools.compare(db, local, ExecutionResult result = diffTools.compare(local,
remote, mergedFilePath, remote, merged, toolName, prompt, gui,
toolName, prompt, gui, trustExitCode); trustExitCode);
outw.println(new String(result.getStdout().toByteArray())); outw.println(new String(result.getStdout().toByteArray()));
errw.println( errw.println(
new String(result.getStderr().toByteArray())); new String(result.getStderr().toByteArray()));
@ -278,16 +288,46 @@ private List<DiffEntry> getFiles()
return files; return files;
} }
private ObjectStream getObjectStream(Pair pair, Side side, DiffEntry ent) { private FileElement createFileElement(FileElement.Type elementType,
ObjectStream stream = null; Pair pair, Side side, DiffEntry entry)
if (!pair.isWorkingTreeSource(side)) { throws NoWorkTreeException, CorruptObjectException, IOException,
try { ToolException {
stream = pair.open(side, ent).openStream(); String entryPath = side == Side.NEW ? entry.getNewPath()
} catch (Exception e) { : entry.getOldPath();
stream = null; FileElement fileElement = new FileElement(entryPath, elementType);
if (!pair.isWorkingTreeSource(side) && !fileElement.isNullPath()) {
try (RevWalk revWalk = new RevWalk(db);
TreeWalk treeWalk = new TreeWalk(db,
revWalk.getObjectReader())) {
treeWalk.setFilter(
PathFilterGroup.createFromStrings(entryPath));
if (side == Side.NEW) {
newTree.reset();
treeWalk.addTree(newTree);
} else {
oldTree.reset();
treeWalk.addTree(oldTree);
}
if (treeWalk.next()) {
final EolStreamType eolStreamType = treeWalk
.getEolStreamType(CHECKOUT_OP);
final String filterCommand = treeWalk.getFilterCommand(
Constants.ATTR_FILTER_TYPE_SMUDGE);
WorkingTreeOptions opt = db.getConfig()
.get(WorkingTreeOptions.KEY);
CheckoutMetadata checkoutMetadata = new CheckoutMetadata(
eolStreamType, filterCommand);
DirCacheCheckout.getContent(db, entryPath,
checkoutMetadata, pair.open(side, entry), opt,
new FileOutputStream(
fileElement.createTempFile(null)));
} else {
throw new ToolException("Cannot find path '" + entryPath //$NON-NLS-1$
+ "' in staging area!", null); //$NON-NLS-1$
}
} }
} }
return stream; return fileElement;
} }
private ContentSource source(AbstractTreeIterator iterator) { private ContentSource source(AbstractTreeIterator iterator) {

View File

@ -10,8 +10,11 @@
package org.eclipse.jgit.pgm; package org.eclipse.jgit.pgm;
import static org.eclipse.jgit.treewalk.TreeWalk.OperationType.CHECKOUT_OP;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.text.MessageFormat; import java.text.MessageFormat;
@ -26,8 +29,12 @@
import org.eclipse.jgit.api.StatusCommand; import org.eclipse.jgit.api.StatusCommand;
import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.diff.ContentSource; import org.eclipse.jgit.diff.ContentSource;
import org.eclipse.jgit.internal.diffmergetool.FileElement.Type;
import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheCheckout;
import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.errors.RevisionSyntaxException; import org.eclipse.jgit.errors.RevisionSyntaxException;
import org.eclipse.jgit.internal.diffmergetool.ExternalMergeTool; import org.eclipse.jgit.internal.diffmergetool.ExternalMergeTool;
@ -35,9 +42,15 @@
import org.eclipse.jgit.internal.diffmergetool.MergeTools; import org.eclipse.jgit.internal.diffmergetool.MergeTools;
import org.eclipse.jgit.internal.diffmergetool.ToolException; import org.eclipse.jgit.internal.diffmergetool.ToolException;
import org.eclipse.jgit.lib.IndexDiff.StageState; import org.eclipse.jgit.lib.IndexDiff.StageState;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.WorkingTreeOptions;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.internal.BooleanTriState; import org.eclipse.jgit.lib.internal.BooleanTriState;
import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
import org.eclipse.jgit.pgm.internal.CLIText; import org.eclipse.jgit.pgm.internal.CLIText;
import org.eclipse.jgit.util.FS.ExecutionResult; import org.eclipse.jgit.util.FS.ExecutionResult;
import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Argument;
@ -188,32 +201,67 @@ private MergeResult mergeModified(String mergedFilePath, boolean showPrompt,
ContentSource baseSource = ContentSource.create(db.newObjectReader()); ContentSource baseSource = ContentSource.create(db.newObjectReader());
ContentSource localSource = ContentSource.create(db.newObjectReader()); ContentSource localSource = ContentSource.create(db.newObjectReader());
ContentSource remoteSource = ContentSource.create(db.newObjectReader()); ContentSource remoteSource = ContentSource.create(db.newObjectReader());
// temporary directory if mergetool.writeToTemp == true
File tempDir = mergeTools.createTempDirectory();
// the parent directory for temp files (can be same as tempDir or just
// the worktree dir)
File tempFilesParent = tempDir != null ? tempDir : db.getWorkTree();
try { try {
FileElement base = null; FileElement base = null;
FileElement local = null; FileElement local = null;
FileElement remote = null; FileElement remote = null;
FileElement merged = new FileElement(mergedFilePath,
Type.MERGED);
DirCache cache = db.readDirCache(); DirCache cache = db.readDirCache();
int firstIndex = cache.findEntry(mergedFilePath); try (RevWalk revWalk = new RevWalk(db);
if (firstIndex >= 0) { TreeWalk treeWalk = new TreeWalk(db,
int nextIndex = cache.nextEntry(firstIndex); revWalk.getObjectReader())) {
for (; firstIndex < nextIndex; firstIndex++) { treeWalk.setFilter(
DirCacheEntry entry = cache.getEntry(firstIndex); PathFilterGroup.createFromStrings(mergedFilePath));
DirCacheIterator cacheIter = new DirCacheIterator(cache);
treeWalk.addTree(cacheIter);
while (treeWalk.next()) {
if (treeWalk.isSubtree()) {
treeWalk.enterSubtree();
continue;
}
final EolStreamType eolStreamType = treeWalk
.getEolStreamType(CHECKOUT_OP);
final String filterCommand = treeWalk.getFilterCommand(
Constants.ATTR_FILTER_TYPE_SMUDGE);
WorkingTreeOptions opt = db.getConfig()
.get(WorkingTreeOptions.KEY);
CheckoutMetadata checkoutMetadata = new CheckoutMetadata(
eolStreamType, filterCommand);
DirCacheEntry entry = treeWalk.getTree(DirCacheIterator.class).getDirCacheEntry();
if (entry == null) {
continue;
}
ObjectId id = entry.getObjectId(); ObjectId id = entry.getObjectId();
switch (entry.getStage()) { switch (entry.getStage()) {
case DirCacheEntry.STAGE_1: case DirCacheEntry.STAGE_1:
base = new FileElement(mergedFilePath, id.name(), base = new FileElement(mergedFilePath, Type.BASE);
baseSource.open(mergedFilePath, id) DirCacheCheckout.getContent(db, mergedFilePath,
.openStream()); checkoutMetadata,
baseSource.open(mergedFilePath, id), opt,
new FileOutputStream(
base.createTempFile(tempFilesParent)));
break; break;
case DirCacheEntry.STAGE_2: case DirCacheEntry.STAGE_2:
local = new FileElement(mergedFilePath, id.name(), local = new FileElement(mergedFilePath, Type.LOCAL);
localSource.open(mergedFilePath, id) DirCacheCheckout.getContent(db, mergedFilePath,
.openStream()); checkoutMetadata,
localSource.open(mergedFilePath, id), opt,
new FileOutputStream(
local.createTempFile(tempFilesParent)));
break; break;
case DirCacheEntry.STAGE_3: case DirCacheEntry.STAGE_3:
remote = new FileElement(mergedFilePath, id.name(), remote = new FileElement(mergedFilePath, Type.REMOTE);
remoteSource.open(mergedFilePath, id) DirCacheCheckout.getContent(db, mergedFilePath,
.openStream()); checkoutMetadata,
remoteSource.open(mergedFilePath, id), opt,
new FileOutputStream(remote
.createTempFile(tempFilesParent)));
break; break;
} }
} }
@ -222,14 +270,13 @@ private MergeResult mergeModified(String mergedFilePath, boolean showPrompt,
throw die(MessageFormat.format(CLIText.get().mergeToolDied, throw die(MessageFormat.format(CLIText.get().mergeToolDied,
mergedFilePath)); mergedFilePath));
} }
File merged = new File(mergedFilePath); long modifiedBefore = merged.getFile().lastModified();
long modifiedBefore = merged.lastModified();
try { try {
// TODO: check how to return the exit-code of the // TODO: check how to return the exit-code of the
// tool to jgit / java runtime ? // tool to jgit / java runtime ?
// int rc =... // int rc =...
ExecutionResult executionResult = mergeTools.merge(db, local, ExecutionResult executionResult = mergeTools.merge(local,
remote, base, mergedFilePath, toolName, prompt, gui); remote, merged, base, tempDir, toolName, prompt, gui);
outw.println( outw.println(
new String(executionResult.getStdout().toByteArray())); new String(executionResult.getStdout().toByteArray()));
outw.flush(); outw.flush();
@ -250,7 +297,7 @@ private MergeResult mergeModified(String mergedFilePath, boolean showPrompt,
} }
// if merge was successful check file modified // if merge was successful check file modified
if (isMergeSuccessful) { if (isMergeSuccessful) {
long modifiedAfter = merged.lastModified(); long modifiedAfter = merged.getFile().lastModified();
if (modifiedBefore == modifiedAfter) { if (modifiedBefore == modifiedAfter) {
outw.println(MessageFormat.format( outw.println(MessageFormat.format(
CLIText.get().mergeToolFileUnchanged, CLIText.get().mergeToolFileUnchanged,

View File

@ -54,8 +54,8 @@ public void testUserToolWithError() throws Exception {
BooleanTriState gui = BooleanTriState.UNSET; BooleanTriState gui = BooleanTriState.UNSET;
BooleanTriState trustExitCode = BooleanTriState.TRUE; BooleanTriState trustExitCode = BooleanTriState.TRUE;
manager.compare(db, local, remote, merged.getPath(), toolName, prompt, manager.compare(local, remote, merged, toolName, prompt, gui,
gui, trustExitCode); trustExitCode);
fail("Expected exception to be thrown due to external tool exiting with error code: " fail("Expected exception to be thrown due to external tool exiting with error code: "
+ errorReturnCode); + errorReturnCode);
@ -78,8 +78,8 @@ public void testUserToolWithCommandNotFoundError() throws Exception {
BooleanTriState gui = BooleanTriState.UNSET; BooleanTriState gui = BooleanTriState.UNSET;
BooleanTriState trustExitCode = BooleanTriState.FALSE; BooleanTriState trustExitCode = BooleanTriState.FALSE;
manager.compare(db, local, remote, merged.getPath(), toolName, prompt, manager.compare(local, remote, merged, toolName, prompt, gui,
gui, trustExitCode); trustExitCode);
fail("Expected exception to be thrown due to external tool exiting with error code: " fail("Expected exception to be thrown due to external tool exiting with error code: "
+ errorReturnCode); + errorReturnCode);
@ -183,8 +183,8 @@ public void testCompare() throws ToolException {
DiffTools manager = new DiffTools(db); DiffTools manager = new DiffTools(db);
int expectedCompareResult = 0; int expectedCompareResult = 0;
ExecutionResult compareResult = manager.compare(db, local, remote, ExecutionResult compareResult = manager.compare(local, remote, merged,
merged.getPath(), toolName, prompt, gui, trustExitCode); toolName, prompt, gui, trustExitCode);
assertEquals("Incorrect compare result for external diff tool", assertEquals("Incorrect compare result for external diff tool",
expectedCompareResult, compareResult.getRc()); expectedCompareResult, compareResult.getRc());
} }
@ -263,8 +263,8 @@ public void testUndefinedTool() throws Exception {
BooleanTriState gui = BooleanTriState.UNSET; BooleanTriState gui = BooleanTriState.UNSET;
BooleanTriState trustExitCode = BooleanTriState.UNSET; BooleanTriState trustExitCode = BooleanTriState.UNSET;
manager.compare(db, local, remote, merged.getPath(), toolName, prompt, manager.compare(local, remote, merged, toolName, prompt, gui,
gui, trustExitCode); trustExitCode);
fail("Expected exception to be thrown due to not defined external diff tool"); fail("Expected exception to be thrown due to not defined external diff tool");
} }

View File

@ -55,8 +55,7 @@ public void testUserToolWithError() throws Exception {
BooleanTriState prompt = BooleanTriState.UNSET; BooleanTriState prompt = BooleanTriState.UNSET;
BooleanTriState gui = BooleanTriState.UNSET; BooleanTriState gui = BooleanTriState.UNSET;
manager.merge(db, local, remote, base, merged.getPath(), toolName, manager.merge(local, remote, merged, base, null, toolName, prompt, gui);
prompt, gui);
fail("Expected exception to be thrown due to external tool exiting with error code: " fail("Expected exception to be thrown due to external tool exiting with error code: "
+ errorReturnCode); + errorReturnCode);
@ -78,8 +77,7 @@ public void testUserToolWithCommandNotFoundError() throws Exception {
BooleanTriState prompt = BooleanTriState.UNSET; BooleanTriState prompt = BooleanTriState.UNSET;
BooleanTriState gui = BooleanTriState.UNSET; BooleanTriState gui = BooleanTriState.UNSET;
manager.merge(db, local, remote, base, merged.getPath(), toolName, manager.merge(local, remote, merged, base, null, toolName, prompt, gui);
prompt, gui);
fail("Expected exception to be thrown due to external tool exiting with error code: " fail("Expected exception to be thrown due to external tool exiting with error code: "
+ errorReturnCode); + errorReturnCode);
@ -182,8 +180,8 @@ public void testCompare() throws ToolException {
MergeTools manager = new MergeTools(db); MergeTools manager = new MergeTools(db);
int expectedCompareResult = 0; int expectedCompareResult = 0;
ExecutionResult compareResult = manager.merge(db, local, remote, base, ExecutionResult compareResult = manager.merge(local, remote, merged,
merged.getPath(), toolName, prompt, gui); base, null, toolName, prompt, gui);
assertEquals("Incorrect compare result for external merge tool", assertEquals("Incorrect compare result for external merge tool",
expectedCompareResult, compareResult.getRc()); expectedCompareResult, compareResult.getRc());
} }
@ -262,8 +260,7 @@ public void testUndefinedTool() throws Exception {
BooleanTriState prompt = BooleanTriState.UNSET; BooleanTriState prompt = BooleanTriState.UNSET;
BooleanTriState gui = BooleanTriState.UNSET; BooleanTriState gui = BooleanTriState.UNSET;
manager.merge(db, local, remote, base, merged.getPath(), toolName, manager.merge(local, remote, merged, base, null, toolName, prompt, gui);
prompt, gui);
fail("Expected exception to be thrown due to not defined external merge tool"); fail("Expected exception to be thrown due to not defined external merge tool");
} }

View File

@ -60,10 +60,14 @@ public void setUp() throws Exception {
commandResult = writeTrashFile("commandResult.txt", ""); commandResult = writeTrashFile("commandResult.txt", "");
commandResult.deleteOnExit(); commandResult.deleteOnExit();
local = new FileElement(localFile.getAbsolutePath(), "LOCAL"); local = new FileElement(localFile.getAbsolutePath(),
remote = new FileElement(remoteFile.getAbsolutePath(), "REMOTE"); FileElement.Type.LOCAL);
merged = new FileElement(mergedFile.getAbsolutePath(), "MERGED"); remote = new FileElement(remoteFile.getAbsolutePath(),
base = new FileElement(baseFile.getAbsolutePath(), "BASE"); FileElement.Type.REMOTE);
merged = new FileElement(mergedFile.getAbsolutePath(),
FileElement.Type.MERGED);
base = new FileElement(baseFile.getAbsolutePath(),
FileElement.Type.BASE);
} }
@After @After

View File

@ -12,12 +12,10 @@
import java.util.TreeMap; import java.util.TreeMap;
import java.util.Collections; import java.util.Collections;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.internal.BooleanTriState; import org.eclipse.jgit.lib.internal.BooleanTriState;
import org.eclipse.jgit.util.FS.ExecutionResult; import org.eclipse.jgit.util.FS.ExecutionResult;
@ -28,6 +26,8 @@
*/ */
public class DiffTools { public class DiffTools {
private final Repository repo;
private final DiffToolConfig config; private final DiffToolConfig config;
private final Map<String, ExternalDiffTool> predefinedTools; private final Map<String, ExternalDiffTool> predefinedTools;
@ -41,6 +41,7 @@ public class DiffTools {
* the repository * the repository
*/ */
public DiffTools(Repository repo) { public DiffTools(Repository repo) {
this.repo = repo;
config = repo.getConfig().get(DiffToolConfig.KEY); config = repo.getConfig().get(DiffToolConfig.KEY);
predefinedTools = setupPredefinedTools(); predefinedTools = setupPredefinedTools();
userDefinedTools = setupUserDefinedTools(config, predefinedTools); userDefinedTools = setupUserDefinedTools(config, predefinedTools);
@ -49,14 +50,13 @@ public DiffTools(Repository repo) {
/** /**
* Compare two versions of a file. * Compare two versions of a file.
* *
* @param repo
* the repository
* @param localFile * @param localFile
* the local file element * the local file element
* @param remoteFile * @param remoteFile
* the remote file element * the remote file element
* @param mergedFilePath * @param mergedFile
* the path of 'merged' file, it equals local or remote path * the merged file element, it's path equals local or remote
* element path
* @param toolName * @param toolName
* the selected tool name (can be null) * the selected tool name (can be null)
* @param prompt * @param prompt
@ -68,36 +68,31 @@ public DiffTools(Repository repo) {
* @return the execution result from tool * @return the execution result from tool
* @throws ToolException * @throws ToolException
*/ */
public ExecutionResult compare(Repository repo, FileElement localFile, public ExecutionResult compare(FileElement localFile,
FileElement remoteFile, String mergedFilePath, String toolName, FileElement remoteFile, FileElement mergedFile, String toolName,
BooleanTriState prompt, BooleanTriState gui, BooleanTriState prompt, BooleanTriState gui,
BooleanTriState trustExitCode) throws ToolException { BooleanTriState trustExitCode) throws ToolException {
ExternalDiffTool tool = guessTool(toolName, gui);
try { try {
File workingDir = repo.getWorkTree(); // prepare the command (replace the file paths)
String localFilePath = localFile.getFile().getPath(); String command = ExternalToolUtils.prepareCommand(
String remoteFilePath = remoteFile.getFile().getPath(); guessTool(toolName, gui).getCommand(), localFile,
String command = tool.getCommand(); remoteFile, mergedFile, null);
command = command.replace("$LOCAL", localFilePath); //$NON-NLS-1$ // prepare the environment
command = command.replace("$REMOTE", remoteFilePath); //$NON-NLS-1$ Map<String, String> env = ExternalToolUtils.prepareEnvironment(repo,
command = command.replace("$MERGED", mergedFilePath); //$NON-NLS-1$ localFile, remoteFile, mergedFile, null);
Map<String, String> env = new TreeMap<>();
env.put(Constants.GIT_DIR_KEY,
repo.getDirectory().getAbsolutePath());
env.put("LOCAL", localFilePath); //$NON-NLS-1$
env.put("REMOTE", remoteFilePath); //$NON-NLS-1$
env.put("MERGED", mergedFilePath); //$NON-NLS-1$
boolean trust = config.isTrustExitCode(); boolean trust = config.isTrustExitCode();
if (trustExitCode != BooleanTriState.UNSET) { if (trustExitCode != BooleanTriState.UNSET) {
trust = trustExitCode == BooleanTriState.TRUE; trust = trustExitCode == BooleanTriState.TRUE;
} }
// execute the tool
CommandExecutor cmdExec = new CommandExecutor(repo.getFS(), trust); CommandExecutor cmdExec = new CommandExecutor(repo.getFS(), trust);
return cmdExec.run(command, workingDir, env); return cmdExec.run(command, repo.getWorkTree(), env);
} catch (IOException | InterruptedException e) { } catch (IOException | InterruptedException e) {
throw new ToolException(e); throw new ToolException(e);
} finally { } finally {
localFile.cleanTemporaries(); localFile.cleanTemporaries();
remoteFile.cleanTemporaries(); remoteFile.cleanTemporaries();
mergedFile.cleanTemporaries();
} }
} }

View File

@ -0,0 +1,81 @@
/*
* Copyright (C) 2018-2021, Andre Bossert <andre.bossert@siemens.com>
*
* 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 java.util.TreeMap;
import java.io.IOException;
import java.util.Map;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
/**
* Utilities for diff- and merge-tools.
*/
public class ExternalToolUtils {
/**
* Prepare command for execution.
*
* @param command
* the input "command" string
* @param localFile
* the local file (ours)
* @param remoteFile
* the remote file (theirs)
* @param mergedFile
* the merged file (worktree)
* @param baseFile
* the base file (can be null)
* @return the prepared (with replaced variables) command string
* @throws IOException
*/
public static String prepareCommand(String command, FileElement localFile,
FileElement remoteFile, FileElement mergedFile,
FileElement baseFile) throws IOException {
command = localFile.replaceVariable(command);
command = remoteFile.replaceVariable(command);
command = mergedFile.replaceVariable(command);
if (baseFile != null) {
command = baseFile.replaceVariable(command);
}
return command;
}
/**
* Prepare environment needed for execution.
*
* @param repo
* the repository
* @param localFile
* the local file (ours)
* @param remoteFile
* the remote file (theirs)
* @param mergedFile
* the merged file (worktree)
* @param baseFile
* the base file (can be null)
* @return the environment map with variables and values (file paths)
* @throws IOException
*/
public static Map<String, String> prepareEnvironment(Repository repo,
FileElement localFile, FileElement remoteFile,
FileElement mergedFile, FileElement baseFile) throws IOException {
Map<String, String> env = new TreeMap<>();
env.put(Constants.GIT_DIR_KEY, repo.getDirectory().getAbsolutePath());
localFile.addToEnv(env);
remoteFile.addToEnv(env);
mergedFile.addToEnv(env);
if (baseFile != null) {
baseFile.addToEnv(env);
}
return env;
}
}

View File

@ -14,10 +14,11 @@
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Map;
import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.lib.ObjectStream;
/** /**
* The element used as left or right file for compare. * The element used as left or right file for compare.
@ -25,36 +26,71 @@
*/ */
public class FileElement { public class FileElement {
/**
* The file element type.
*
*/
public enum Type {
/**
* The local file element (ours).
*/
LOCAL,
/**
* The remote file element (theirs).
*/
REMOTE,
/**
* The merged file element (path in worktree).
*/
MERGED,
/**
* The base file element (of ours and theirs).
*/
BASE,
/**
* The backup file element (copy of merged / conflicted).
*/
BACKUP
}
private final String path; private final String path;
private final String id; private final Type type;
private ObjectStream stream; private InputStream stream;
private File tempFile; private File tempFile;
/** /**
* Creates file element for path.
*
* @param path * @param path
* the file path * the file path
* @param id * @param type
* the file id * the element type
*/ */
public FileElement(final String path, final String id) { public FileElement(String path, Type type) {
this(path, id, null); this(path, type, null, null);
} }
/** /**
* Creates file element for path.
*
* @param path * @param path
* the file path * the file path
* @param id * @param type
* the file id * the element type
* @param tempFile
* the temporary file to be used (can be null and will be created
* then)
* @param stream * @param stream
* the object stream to load instead of file * the object stream to load instead of file
*/ */
public FileElement(final String path, final String id, public FileElement(String path, Type type, File tempFile,
ObjectStream stream) { InputStream stream) {
this.path = path; this.path = path;
this.id = id; this.type = type;
this.tempFile = tempFile;
this.stream = stream; this.stream = stream;
} }
@ -66,71 +102,101 @@ public String getPath() {
} }
/** /**
* @return the file id * @return the element type
*/ */
public String getId() { public Type getType() {
return id; return type;
} }
/** /**
* @param stream * Return a temporary file within passed directory and fills it with stream
* the object stream * if valid.
*/
public void setStream(ObjectStream stream) {
this.stream = stream;
}
/**
* Returns a temporary file with in passed working directory and fills it
* with stream if valid.
* *
* @param directory * @param directory
* the working directory where the temporary file is created * the directory where the temporary file is created
* @param midName * @param midName
* name added in the middle of generated temporary file name * name added in the middle of generated temporary file name
* @return the object stream * @return the object stream
* @throws IOException * @throws IOException
*/ */
public File getFile(File directory, String midName) throws IOException { public File getFile(File directory, String midName) throws IOException {
if (tempFile != null) { if ((tempFile != null) && (stream == null)) {
return tempFile; return tempFile;
} }
String[] fileNameAndExtension = splitBaseFileNameAndExtension( tempFile = getTempFile(path, directory, midName);
new File(path)); return copyFromStream(tempFile, stream);
tempFile = File.createTempFile(
fileNameAndExtension[0] + "_" + midName + "_", //$NON-NLS-1$ //$NON-NLS-2$
fileNameAndExtension[1], directory);
copyFromStream();
return tempFile;
} }
/** /**
* Returns a real file from work tree or a temporary file with content if * Return a real file from work tree or a temporary file with content if
* stream is valid or if path is "/dev/null" * stream is valid or if path is "/dev/null"
* *
* @return the object stream * @return the object stream
* @throws IOException * @throws IOException
*/ */
public File getFile() throws IOException { public File getFile() throws IOException {
if (tempFile != null) { if ((tempFile != null) && (stream == null)) {
return tempFile; return tempFile;
} }
File file = new File(path); File file = new File(path);
String name = file.getName();
// if we have a stream or file is missing ("/dev/null") then create // if we have a stream or file is missing ("/dev/null") then create
// temporary file // temporary file
if ((stream != null) || path.equals(DiffEntry.DEV_NULL)) { if ((stream != null) || isNullPath()) {
// TODO: avoid long random file name (number generated by tempFile = getTempFile(file);
// createTempFile) return copyFromStream(tempFile, stream);
tempFile = File.createTempFile(".__", "__" + name); //$NON-NLS-1$ //$NON-NLS-2$
copyFromStream();
return tempFile;
} }
return file; return file;
} }
/** /**
* Deletes and invalidates temporary file if necessary. * Check if path id "/dev/null"
*
* @return true if path is "/dev/null"
*/
public boolean isNullPath() {
return path.equals(DiffEntry.DEV_NULL);
}
/**
* Create temporary file in given or system temporary directory
*
* @param directory
* the directory for the file (can be null); if null system
* temporary directory is used
* @return temporary file in directory or in the system temporary directory
* @throws IOException
*/
public File createTempFile(File directory) throws IOException {
if (tempFile == null) {
File file = new File(path);
if (directory != null) {
tempFile = getTempFile(file, directory, type.name());
} else {
tempFile = getTempFile(file);
}
}
return tempFile;
}
private static File getTempFile(File file) throws IOException {
return File.createTempFile(".__", "__" + file.getName()); //$NON-NLS-1$ //$NON-NLS-2$
}
private static File getTempFile(File file, File directory, String midName)
throws IOException {
String[] fileNameAndExtension = splitBaseFileNameAndExtension(file);
return File.createTempFile(
fileNameAndExtension[0] + "_" + midName + "_", //$NON-NLS-1$ //$NON-NLS-2$
fileNameAndExtension[1], directory);
}
private static File getTempFile(String path, File directory, String midName)
throws IOException {
return getTempFile(new File(path), directory, midName);
}
/**
* Delete and invalidate temporary file if necessary.
*/ */
public void cleanTemporaries() { public void cleanTemporaries() {
if (tempFile != null && tempFile.exists()) if (tempFile != null && tempFile.exists())
@ -138,9 +204,10 @@ public void cleanTemporaries() {
tempFile = null; tempFile = null;
} }
private void copyFromStream() throws IOException, FileNotFoundException { private static File copyFromStream(File file, final InputStream stream)
throws IOException, FileNotFoundException {
if (stream != null) { if (stream != null) {
try (OutputStream outStream = new FileOutputStream(tempFile)) { try (OutputStream outStream = new FileOutputStream(file)) {
int read = 0; int read = 0;
byte[] bytes = new byte[8 * 1024]; byte[] bytes = new byte[8 * 1024];
while ((read = stream.read(bytes)) != -1) { while ((read = stream.read(bytes)) != -1) {
@ -149,23 +216,46 @@ private void copyFromStream() throws IOException, FileNotFoundException {
} finally { } finally {
// stream can only be consumed once --> close it // stream can only be consumed once --> close it
stream.close(); stream.close();
stream = null;
} }
} }
return file;
} }
private static String[] splitBaseFileNameAndExtension(File file) { private static String[] splitBaseFileNameAndExtension(File file) {
String[] result = new String[2]; String[] result = new String[2];
result[0] = file.getName(); result[0] = file.getName();
result[1] = ""; //$NON-NLS-1$ result[1] = ""; //$NON-NLS-1$
if (!result[0].startsWith(".")) { //$NON-NLS-1$ int idx = result[0].lastIndexOf("."); //$NON-NLS-1$
int idx = result[0].lastIndexOf("."); //$NON-NLS-1$ // if "." was found (>-1) and last-index is not first char (>0), then
if (idx != -1) { // split (same behavior like cgit)
result[1] = result[0].substring(idx, result[0].length()); if (idx > 0) {
result[0] = result[0].substring(0, idx); result[1] = result[0].substring(idx, result[0].length());
} result[0] = result[0].substring(0, idx);
} }
return result; return result;
} }
/**
* Replace variable in input
*
* @param input
* the input string
* @return the replaced input string
* @throws IOException
*/
public String replaceVariable(String input) throws IOException {
return input.replace("$" + type.name(), getFile().getPath()); //$NON-NLS-1$
}
/**
* Add variable to environment map.
*
* @param env
* the environment where this element should be added
* @throws IOException
*/
public void addToEnv(Map<String, String> env) throws IOException {
env.put(type.name(), getFile().getPath());
}
} }

View File

@ -19,7 +19,7 @@
import java.util.Set; import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.internal.diffmergetool.FileElement.Type;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.internal.BooleanTriState; import org.eclipse.jgit.lib.internal.BooleanTriState;
import org.eclipse.jgit.util.FS.ExecutionResult; import org.eclipse.jgit.util.FS.ExecutionResult;
@ -29,6 +29,8 @@
*/ */
public class MergeTools { public class MergeTools {
Repository repo;
private final MergeToolConfig config; private final MergeToolConfig config;
private final Map<String, ExternalMergeTool> predefinedTools; private final Map<String, ExternalMergeTool> predefinedTools;
@ -37,25 +39,27 @@ public class MergeTools {
/** /**
* @param repo * @param repo
* the repository database * the repository
*/ */
public MergeTools(Repository repo) { public MergeTools(Repository repo) {
this.repo = repo;
config = repo.getConfig().get(MergeToolConfig.KEY); config = repo.getConfig().get(MergeToolConfig.KEY);
predefinedTools = setupPredefinedTools(); predefinedTools = setupPredefinedTools();
userDefinedTools = setupUserDefinedTools(config, predefinedTools); userDefinedTools = setupUserDefinedTools(config, predefinedTools);
} }
/** /**
* @param repo
* the repository
* @param localFile * @param localFile
* the local file element * the local file element
* @param remoteFile * @param remoteFile
* the remote file element * the remote file element
* @param mergedFile
* the merged file element
* @param baseFile * @param baseFile
* the base file element (can be null) * the base file element (can be null)
* @param mergedFilePath * @param tempDir
* the path of 'merged' file * the temporary directory (needed for backup and auto-remove,
* can be null)
* @param toolName * @param toolName
* the selected tool name (can be null) * the selected tool name (can be null)
* @param prompt * @param prompt
@ -65,47 +69,35 @@ public MergeTools(Repository repo) {
* @return the execution result from tool * @return the execution result from tool
* @throws ToolException * @throws ToolException
*/ */
public ExecutionResult merge(Repository repo, FileElement localFile, public ExecutionResult merge(FileElement localFile, FileElement remoteFile,
FileElement remoteFile, FileElement baseFile, String mergedFilePath, FileElement mergedFile, FileElement baseFile, File tempDir,
String toolName, BooleanTriState prompt, BooleanTriState gui) String toolName, BooleanTriState prompt, BooleanTriState gui)
throws ToolException { throws ToolException {
ExternalMergeTool tool = guessTool(toolName, gui); ExternalMergeTool tool = guessTool(toolName, gui);
FileElement backup = null; FileElement backup = null;
File tempDir = null;
ExecutionResult result = null; ExecutionResult result = null;
try { try {
File workingDir = repo.getWorkTree(); File workingDir = repo.getWorkTree();
// crate temp-directory or use working directory
tempDir = config.isWriteToTemp()
? Files.createTempDirectory("jgit-mergetool-").toFile() //$NON-NLS-1$
: workingDir;
// create additional backup file (copy worktree file) // create additional backup file (copy worktree file)
backup = createBackupFile(mergedFilePath, tempDir); backup = createBackupFile(mergedFile.getPath(),
// get local, remote and base file paths tempDir != null ? tempDir : workingDir);
String localFilePath = localFile.getFile(tempDir, "LOCAL") //$NON-NLS-1$
.getPath();
String remoteFilePath = remoteFile.getFile(tempDir, "REMOTE") //$NON-NLS-1$
.getPath();
String baseFilePath = ""; //$NON-NLS-1$
if (baseFile != null) {
baseFilePath = baseFile.getFile(tempDir, "BASE").getPath(); //$NON-NLS-1$
}
// prepare the command (replace the file paths) // prepare the command (replace the file paths)
boolean trust = tool.getTrustExitCode() == BooleanTriState.TRUE; boolean trust = tool.getTrustExitCode() == BooleanTriState.TRUE;
String command = prepareCommand(mergedFilePath, localFilePath, String command = ExternalToolUtils.prepareCommand(
remoteFilePath, baseFilePath, tool.getCommand(baseFile != null), localFile, remoteFile,
tool.getCommand(baseFile != null)); mergedFile, baseFile);
// prepare the environment // prepare the environment
Map<String, String> env = prepareEnvironment(repo, mergedFilePath, Map<String, String> env = ExternalToolUtils.prepareEnvironment(repo,
localFilePath, remoteFilePath, baseFilePath); localFile, remoteFile, mergedFile, baseFile);
// execute the tool
CommandExecutor cmdExec = new CommandExecutor(repo.getFS(), trust); CommandExecutor cmdExec = new CommandExecutor(repo.getFS(), trust);
result = cmdExec.run(command, workingDir, env); result = cmdExec.run(command, workingDir, env);
// keep backup as .orig file // keep backup as .orig file
if (backup != null) { if (backup != null) {
keepBackupFile(mergedFilePath, backup); keepBackupFile(mergedFile.getPath(), backup);
} }
return result; return result;
} catch (Exception e) { } catch (IOException | InterruptedException e) {
throw new ToolException(e); throw new ToolException(e);
} finally { } finally {
// always delete backup file (ignore that it was may be already // always delete backup file (ignore that it was may be already
@ -131,18 +123,29 @@ public ExecutionResult merge(Repository repo, FileElement localFile,
} }
} }
private static FileElement createBackupFile(String mergedFilePath, private FileElement createBackupFile(String filePath, File parentDir)
File tempDir) throws IOException { throws IOException {
FileElement backup = null; FileElement backup = null;
Path path = Paths.get(tempDir.getPath(), mergedFilePath); Path path = Paths.get(filePath);
if (Files.exists(path)) { if (Files.exists(path)) {
backup = new FileElement(mergedFilePath, "NOID", null); //$NON-NLS-1$ backup = new FileElement(filePath, Type.BACKUP);
Files.copy(path, backup.getFile(tempDir, "BACKUP").toPath(), //$NON-NLS-1$ Files.copy(path, backup.createTempFile(parentDir).toPath(),
StandardCopyOption.REPLACE_EXISTING); StandardCopyOption.REPLACE_EXISTING);
} }
return backup; return backup;
} }
/**
* @return the created temporary directory if (mergetol.writeToTemp == true)
* or null if not configured or false.
* @throws IOException
*/
public File createTempDirectory() throws IOException {
return config.isWriteToTemp()
? Files.createTempDirectory("jgit-mergetool-").toFile() //$NON-NLS-1$
: null;
}
/** /**
* @return the tool names * @return the tool names
*/ */
@ -208,27 +211,6 @@ private ExternalMergeTool getTool(final String name) {
return tool; return tool;
} }
private String prepareCommand(String mergedFilePath, String localFilePath,
String remoteFilePath, String baseFilePath, String command) {
command = command.replace("$LOCAL", localFilePath); //$NON-NLS-1$
command = command.replace("$REMOTE", remoteFilePath); //$NON-NLS-1$
command = command.replace("$MERGED", mergedFilePath); //$NON-NLS-1$
command = command.replace("$BASE", baseFilePath); //$NON-NLS-1$
return command;
}
private Map<String, String> prepareEnvironment(Repository repo,
String mergedFilePath, String localFilePath, String remoteFilePath,
String baseFilePath) {
Map<String, String> env = new TreeMap<>();
env.put(Constants.GIT_DIR_KEY, repo.getDirectory().getAbsolutePath());
env.put("LOCAL", localFilePath); //$NON-NLS-1$
env.put("REMOTE", remoteFilePath); //$NON-NLS-1$
env.put("MERGED", mergedFilePath); //$NON-NLS-1$
env.put("BASE", baseFilePath); //$NON-NLS-1$
return env;
}
private void keepBackupFile(String mergedFilePath, FileElement backup) private void keepBackupFile(String mergedFilePath, FileElement backup)
throws IOException { throws IOException {
if (config.isKeepBackup()) { if (config.isKeepBackup()) {

View File

@ -113,7 +113,7 @@ public String getResultStderr() {
try { try {
return new String(result.getStderr().toByteArray()); return new String(result.getStderr().toByteArray());
} catch (Exception e) { } catch (Exception e) {
LOG.warn(e.getMessage()); LOG.warn("Failed to retrieve standard error output", e); //$NON-NLS-1$
} }
return ""; //$NON-NLS-1$ return ""; //$NON-NLS-1$
} }
@ -125,7 +125,7 @@ public String getResultStdout() {
try { try {
return new String(result.getStdout().toByteArray()); return new String(result.getStdout().toByteArray());
} catch (Exception e) { } catch (Exception e) {
LOG.warn(e.getMessage()); LOG.warn("Failed to retrieve standard output", e); //$NON-NLS-1$
} }
return ""; //$NON-NLS-1$ return ""; //$NON-NLS-1$
} }