/* * Copyright (C) 2018-2021, Andre Bossert * * 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.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.Map; import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FS.ExecutionResult; import org.eclipse.jgit.util.FS_POSIX; import org.eclipse.jgit.util.FS_Win32; import org.eclipse.jgit.util.FS_Win32_Cygwin; import org.eclipse.jgit.util.StringUtils; import org.eclipse.jgit.util.SystemReader; /** * Runs a command with help of FS. */ public class CommandExecutor { private FS fs; private boolean checkExitCode; private File commandFile; private boolean useMsys2; /** * @param fs * the file system * @param checkExitCode * should the exit code be checked for errors ? */ public CommandExecutor(FS fs, boolean checkExitCode) { this.fs = fs; this.checkExitCode = checkExitCode; } /** * Run command * * @param command * the command string * @param workingDir * the working directory * @param env * the environment * @return the execution result * @throws ToolException * if a tool raised an error * @throws InterruptedException * if thread was interrupted * @throws IOException * if an IO error occurred */ public ExecutionResult run(String command, File workingDir, Map env) throws ToolException, IOException, InterruptedException { String[] commandArray = createCommandArray(command); try { ProcessBuilder pb = fs.runInShell(commandArray[0], Arrays.copyOfRange(commandArray, 1, commandArray.length)); pb.directory(workingDir); Map envp = pb.environment(); if (env != null) { envp.putAll(env); } ExecutionResult result = fs.execute(pb, null); int rc = result.getRc(); if (rc != 0) { boolean execError = isCommandExecutionError(rc); if (checkExitCode || execError) { throw new ToolException( "JGit: tool execution return code: " + rc + "\n" //$NON-NLS-1$ //$NON-NLS-2$ + "checkExitCode: " + checkExitCode + "\n" //$NON-NLS-1$ //$NON-NLS-2$ + "execError: " + execError + "\n" //$NON-NLS-1$ //$NON-NLS-2$ + "stderr: \n" //$NON-NLS-1$ + new String( result.getStderr().toByteArray(), SystemReader.getInstance() .getDefaultCharset()), result, execError); } } return result; } finally { deleteCommandArray(); } } /** * Check whether executable file is available * * @param path * the executable path * @param workingDir * the working directory * @param env * the environment * @return the execution result * @throws ToolException * if a tool raised an error * @throws InterruptedException * if thread was interrupted * @throws IOException * if an IO error occurred */ public boolean checkExecutable(String path, File workingDir, Map env) throws ToolException, IOException, InterruptedException { checkUseMsys2(path); String command = null; if (fs instanceof FS_Win32 && !useMsys2) { Path p = Paths.get(path); // Win32 (and not cygwin or MSYS2) where accepts only command / exe // name as parameter // so check if exists and executable in this case if (p.isAbsolute() && Files.isExecutable(p)) { return true; } // try where command for all other cases command = "where " + ExternalToolUtils.quotePath(path); //$NON-NLS-1$ } else { command = "which " + ExternalToolUtils.quotePath(path); //$NON-NLS-1$ } boolean available = true; try { ExecutionResult rc = run(command, workingDir, env); if (rc.getRc() != 0) { available = false; } } catch (IOException | InterruptedException | NoWorkTreeException | ToolException e) { // no op: is true to not hide possible tools from user } return available; } private void deleteCommandArray() { deleteCommandFile(); } private String[] createCommandArray(String command) throws ToolException, IOException { String[] commandArray = null; checkUseMsys2(command); createCommandFile(command); if (fs instanceof FS_POSIX) { commandArray = new String[1]; commandArray[0] = commandFile.getCanonicalPath(); } else if (fs instanceof FS_Win32_Cygwin) { commandArray = new String[1]; commandArray[0] = commandFile.getCanonicalPath().replace("\\", "/"); //$NON-NLS-1$ //$NON-NLS-2$ } else if (fs instanceof FS_Win32) { if (useMsys2) { commandArray = new String[3]; commandArray[0] = "bash.exe"; //$NON-NLS-1$ commandArray[1] = "-c"; //$NON-NLS-1$ commandArray[2] = commandFile.getCanonicalPath().replace("\\", //$NON-NLS-1$ "/"); //$NON-NLS-1$ } else { commandArray = new String[1]; commandArray[0] = commandFile.getCanonicalPath(); } } else { throw new ToolException( "JGit: file system not supported: " + fs.toString()); //$NON-NLS-1$ } return commandArray; } private void checkUseMsys2(String command) { useMsys2 = false; String useMsys2Str = System.getProperty("jgit.usemsys2bash"); //$NON-NLS-1$ if (!StringUtils.isEmptyOrNull(useMsys2Str)) { if (useMsys2Str.equalsIgnoreCase("auto")) { //$NON-NLS-1$ useMsys2 = command.contains(".sh"); //$NON-NLS-1$ } else { useMsys2 = Boolean.parseBoolean(useMsys2Str); } } } private void createCommandFile(String command) throws ToolException, IOException { String fileExtension = null; if (useMsys2 || fs instanceof FS_POSIX || fs instanceof FS_Win32_Cygwin) { fileExtension = ".sh"; //$NON-NLS-1$ } else if (fs instanceof FS_Win32) { fileExtension = ".cmd"; //$NON-NLS-1$ command = "@echo off" + System.lineSeparator() + command //$NON-NLS-1$ + System.lineSeparator() + "exit /B %ERRORLEVEL%"; //$NON-NLS-1$ } else { throw new ToolException( "JGit: file system not supported: " + fs.toString()); //$NON-NLS-1$ } commandFile = File.createTempFile(".__", //$NON-NLS-1$ "__jgit_tool" + fileExtension); //$NON-NLS-1$ try (OutputStream outStream = new FileOutputStream(commandFile)) { byte[] strToBytes = command .getBytes(SystemReader.getInstance().getDefaultCharset()); outStream.write(strToBytes); outStream.close(); } commandFile.setExecutable(true); } private void deleteCommandFile() { if (commandFile != null && commandFile.exists()) { commandFile.delete(); } } private boolean isCommandExecutionError(int rc) { if (useMsys2 || fs instanceof FS_POSIX || fs instanceof FS_Win32_Cygwin) { // 126: permission for executing command denied // 127: command not found if ((rc == 126) || (rc == 127)) { return true; } } else if (fs instanceof FS_Win32) { // 9009, 0x2331: Program is not recognized as an internal or // external command, operable program or batch file. Indicates that // command, application name or path has been misspelled when // configuring the Action. if (rc == 9009) { return true; } } return false; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 6798 Content-Disposition: inline; filename="CommandLineDiffTool.java" Last-Modified: Tue, 06 May 2025 14:13:29 GMT Expires: Tue, 06 May 2025 14:18:29 GMT ETag: "1e09796d230aca50129dfa0b7f0998a2ecce92e2" /* * Copyright (C) 2018-2021, Andre Bossert * * 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; /** * Pre-defined command line diff tools. * * Adds same diff tools as also pre-defined in C-Git *

* see "git-core\mergetools\" *

*

* see links to command line parameter description for the tools *

* *
 * araxis
 * bc
 * bc3
 * codecompare
 * deltawalker
 * diffmerge
 * diffuse
 * ecmerge
 * emerge
 * examdiff
 * guiffy
 * gvimdiff
 * gvimdiff2
 * gvimdiff3
 * kdiff3
 * kompare
 * meld
 * opendiff
 * p4merge
 * tkdiff
 * vimdiff
 * vimdiff2
 * vimdiff3
 * winmerge
 * xxdiff
 * 
* */ @SuppressWarnings("nls") public enum CommandLineDiffTool { /** * See: https://www.araxis.com/merge/documentation-windows/command-line.en */ araxis("compare", "-wait -2 \"$LOCAL\" \"$REMOTE\""), /** * See: https://www.scootersoftware.com/v4help/index.html?command_line_reference.html */ bc("bcomp", "\"$LOCAL\" \"$REMOTE\""), /** * See: https://www.scootersoftware.com/v4help/index.html?command_line_reference.html */ bc3("bcompare", bc), /** * See: https://www.devart.com/codecompare/docs/index.html?comparing_via_command_line.htm */ codecompare("CodeCompare", "\"$LOCAL\" \"$REMOTE\""), /** * See: https://www.deltawalker.com/integrate/command-line */ deltawalker("DeltaWalker", "\"$LOCAL\" \"$REMOTE\""), /** * See: https://sourcegear.com/diffmerge/webhelp/sec__clargs__diff.html */ diffmerge("diffmerge", "\"$LOCAL\" \"$REMOTE\""), /** * See: http://diffuse.sourceforge.net/manual.html#introduction-usage */ diffuse("diffuse", "\"$LOCAL\" \"$REMOTE\""), /** * See: http://www.elliecomputing.com/en/OnlineDoc/ecmerge_en/44205167.asp */ ecmerge("ecmerge", "--default --mode=diff2 \"$LOCAL\" \"$REMOTE\""), /** * See: https://www.gnu.org/software/emacs/manual/html_node/emacs/Overview-of-Emerge.html */ emerge("emacs", "-f emerge-files-command \"$LOCAL\" \"$REMOTE\""), /** * See: https://www.prestosoft.com/ps.asp?page=htmlhelp/edp/command_line_options */ examdiff("ExamDiff", "\"$LOCAL\" \"$REMOTE\" -nh"), /** * See: https://www.guiffy.com/help/GuiffyHelp/GuiffyCmd.html */ guiffy("guiffy", "\"$LOCAL\" \"$REMOTE\""), /** * See: http://vimdoc.sourceforge.net/htmldoc/diff.html */ gvimdiff("gvimdiff", "\"$LOCAL\" \"$REMOTE\""), /** * See: http://vimdoc.sourceforge.net/htmldoc/diff.html */ gvimdiff2(gvimdiff), /** * See: http://vimdoc.sourceforge.net/htmldoc/diff.html */ gvimdiff3(gvimdiff), /** * See: http://kdiff3.sourceforge.net/doc/documentation.html */ kdiff3("kdiff3", "--L1 \"$MERGED (A)\" --L2 \"$MERGED (B)\" \"$LOCAL\" \"$REMOTE\""), /** * See: https://docs.kde.org/trunk5/en/kdesdk/kompare/commandline-options.html */ kompare("kompare", "\"$LOCAL\" \"$REMOTE\""), /** * See: http://meldmerge.org/help/file-mode.html */ meld("meld", "\"$LOCAL\" \"$REMOTE\""), /** * See: http://www.manpagez.com/man/1/opendiff/ *

* Hint: check the ' | cat' for the call *

*/ opendiff("opendiff", "\"$LOCAL\" \"$REMOTE\""), /** * See: https://www.perforce.com/manuals/v15.1/cmdref/p4_merge.html */ p4merge("p4merge", "\"$LOCAL\" \"$REMOTE\""), /** * See: http://linux.math.tifr.res.in/manuals/man/tkdiff.html */ tkdiff("tkdiff", "\"$LOCAL\" \"$REMOTE\""), /** * See: http://vimdoc.sourceforge.net/htmldoc/diff.html */ vimdiff("vimdiff", gvimdiff), /** * See: http://vimdoc.sourceforge.net/htmldoc/diff.html */ vimdiff2(vimdiff), /** * See: http://vimdoc.sourceforge.net/htmldoc/diff.html */ vimdiff3(vimdiff), /** * See: http://manual.winmerge.org/Command_line.html *

* Hint: check how 'mergetool_find_win32_cmd "WinMergeU.exe" "WinMerge"' * works *

*/ winmerge("WinMergeU", "-u -e \"$LOCAL\" \"$REMOTE\""), /** * See: http://furius.ca/xxdiff/doc/xxdiff-doc.html */ xxdiff("xxdiff", "-R 'Accel.Search: \"Ctrl+F\"' -R 'Accel.SearchForward: \"Ctrl+G\"' \"$LOCAL\" \"$REMOTE\""); CommandLineDiffTool(String path, String parameters) { this.path = path; this.parameters = parameters; } CommandLineDiffTool(CommandLineDiffTool from) { this(from.getPath(), from.getParameters()); } CommandLineDiffTool(String path, CommandLineDiffTool from) { this(path, from.getParameters()); } private final String path; private final String parameters; /** * Get path * * @return path */ public String getPath() { return path; } /** * Get parameters * * @return parameters as one string */ public String getParameters() { return parameters; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 10865 Content-Disposition: inline; filename="CommandLineMergeTool.java" Last-Modified: Tue, 06 May 2025 14:13:29 GMT Expires: Tue, 06 May 2025 14:18:29 GMT ETag: "d8287f45d1ebe746cf46c767b75b3df3760fadfa" /* * Copyright (C) 2018-2022, Andre Bossert * * 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; /** * Pre-defined merge tools. * * Adds same merge tools as also pre-defined in C-Git see "git-core\mergetools\" * see links to command line parameter description for the tools * *
 * araxis
 * bc
 * bc3
 * codecompare
 * deltawalker
 * diffmerge
 * diffuse
 * ecmerge
 * emerge
 * examdiff
 * guiffy
 * gvimdiff
 * gvimdiff2
 * gvimdiff3
 * kdiff3
 * kompare
 * meld
 * opendiff
 * p4merge
 * tkdiff
 * tortoisemerge
 * vimdiff
 * vimdiff2
 * vimdiff3
 * winmerge
 * xxdiff
 * 
* */ @SuppressWarnings("nls") public enum CommandLineMergeTool { /** * See: https://www.araxis.com/merge/documentation-windows/command-line.en */ araxis("compare", "-wait -merge -3 -a1 \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\"", "-wait -2 \"$LOCAL\" \"$REMOTE\" \"$MERGED\"", false), /** * See: https://www.scootersoftware.com/v4help/index.html?command_line_reference.html */ bc("bcomp", "\"$LOCAL\" \"$REMOTE\" \"$BASE\" --mergeoutput=\"$MERGED\"", "\"$LOCAL\" \"$REMOTE\" --mergeoutput=\"$MERGED\"", false), /** * See: https://www.scootersoftware.com/v4help/index.html?command_line_reference.html */ bc3("bcompare", bc), /** * See: https://www.devart.com/codecompare/docs/index.html?merging_via_command_line.htm */ codecompare("CodeMerge", "-MF=\"$LOCAL\" -TF=\"$REMOTE\" -BF=\"$BASE\" -RF=\"$MERGED\"", "-MF=\"$LOCAL\" -TF=\"$REMOTE\" -RF=\"$MERGED\"", false), /** * See: https://www.deltawalker.com/integrate/command-line *

* Hint: $(pwd) command must be defined *

*/ deltawalker("DeltaWalker", "\"$LOCAL\" \"$REMOTE\" \"$BASE\" -pwd=\"$(pwd)\" -merged=\"$MERGED\"", "\"$LOCAL\" \"$REMOTE\" -pwd=\"$(pwd)\" -merged=\"$MERGED\"", true), /** * See: https://sourcegear.com/diffmerge/webhelp/sec__clargs__diff.html */ diffmerge("diffmerge", //$NON-NLS-1$ "--merge --result=\"$MERGED\" \"$LOCAL\" \"$BASE\" \"$REMOTE\"", "--merge --result=\"$MERGED\" \"$LOCAL\" \"$REMOTE\"", true), /** * See: http://diffuse.sourceforge.net/manual.html#introduction-usage *

* Hint: check the ' | cat' for the call *

*/ diffuse("diffuse", "\"$LOCAL\" \"$MERGED\" \"$REMOTE\" \"$BASE\"", "\"$LOCAL\" \"$MERGED\" \"$REMOTE\"", false), /** * See: http://www.elliecomputing.com/en/OnlineDoc/ecmerge_en/44205167.asp */ ecmerge("ecmerge", "--default --mode=merge3 \"$BASE\" \"$LOCAL\" \"$REMOTE\" --to=\"$MERGED\"", "--default --mode=merge2 \"$LOCAL\" \"$REMOTE\" --to=\"$MERGED\"", false), /** * See: https://www.gnu.org/software/emacs/manual/html_node/emacs/Overview-of-Emerge.html *

* Hint: $(basename) command must be defined *

*/ emerge("emacs", "-f emerge-files-with-ancestor-command \"$LOCAL\" \"$REMOTE\" \"$BASE\" \"$(basename \"$MERGED\")\"", "-f emerge-files-command \"$LOCAL\" \"$REMOTE\" \"$(basename \"$MERGED\")\"", true), /** * See: https://www.prestosoft.com/ps.asp?page=htmlhelp/edp/command_line_options */ examdiff("ExamDiff", "-merge \"$LOCAL\" \"$BASE\" \"$REMOTE\" -o:\"$MERGED\" -nh", "-merge \"$LOCAL\" \"$REMOTE\" -o:\"$MERGED\" -nh", false), /** * See: https://www.guiffy.com/help/GuiffyHelp/GuiffyCmd.html */ guiffy("guiffy", "-s \"$LOCAL\" \"$REMOTE\" \"$BASE\" \"$MERGED\"", "-m \"$LOCAL\" \"$REMOTE\" \"$MERGED\"", true), /** * See: http://vimdoc.sourceforge.net/htmldoc/diff.html */ gvimdiff("gvim", "-f -d -c '4wincmd w | wincmd J' \"$LOCAL\" \"$BASE\" \"$REMOTE\" \"$MERGED\"", "-f -d -c 'wincmd l' \"$LOCAL\" \"$MERGED\" \"$REMOTE\"", true), /** * See: http://vimdoc.sourceforge.net/htmldoc/diff.html */ gvimdiff2("gvim", "-f -d -c 'wincmd l' \"$LOCAL\" \"$MERGED\" \"$REMOTE\"", "-f -d -c 'wincmd l' \"$LOCAL\" \"$MERGED\" \"$REMOTE\"", true), /** * See: */ gvimdiff3("gvim", "-f -d -c 'hid | hid | hid' \"$LOCAL\" \"$REMOTE\" \"$BASE\" \"$MERGED\"", "-f -d -c 'hid | hid' \"$LOCAL\" \"$REMOTE\" \"$MERGED\"", true), /** * See: http://kdiff3.sourceforge.net/doc/documentation.html */ kdiff3("kdiff3", "--auto --L1 \"$MERGED (Base)\" --L2 \"$MERGED (Local)\" --L3 \"$MERGED (Remote)\" -o \"$MERGED\" \"$BASE\" \"$LOCAL\" \"$REMOTE\"", "--auto --L1 \"$MERGED (Local)\" --L2 \"$MERGED (Remote)\" -o \"$MERGED\" \"$LOCAL\" \"$REMOTE\"", true), /** * See: http://meldmerge.org/help/file-mode.html *

* Hint: use meld with output option only (new versions) *

*/ meld("meld", "--output=\"$MERGED\" \"$LOCAL\" \"$BASE\" \"$REMOTE\"", "\"$LOCAL\" \"$MERGED\" \"$REMOTE\"", false), /** * See: http://www.manpagez.com/man/1/opendiff/ *

* Hint: check the ' | cat' for the call *

*/ opendiff("opendiff", "\"$LOCAL\" \"$REMOTE\" -ancestor \"$BASE\" -merge \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\" -merge \"$MERGED\"", false), /** * See: https://www.perforce.com/manuals/v15.1/cmdref/p4_merge.html *

* Hint: check how to fix "no base present" / create_virtual_base problem *

*/ p4merge("p4merge", "\"$BASE\" \"$REMOTE\" \"$LOCAL\" \"$MERGED\"", "\"$REMOTE\" \"$LOCAL\" \"$MERGED\"", false), /** * See: http://linux.math.tifr.res.in/manuals/man/tkdiff.html */ tkdiff("tkdiff", "-a \"$BASE\" -o \"$MERGED\" \"$LOCAL\" \"$REMOTE\"", "-o \"$MERGED\" \"$LOCAL\" \"$REMOTE\"", true), /** * See: https://tortoisegit.org/docs/tortoisegitmerge/tme-automation.html#tme-automation-basics *

* Hint: merge without base is not supported *

*

* Hint: cannot diff *

*/ tortoisegitmerge("tortoisegitmerge", "-base \"$BASE\" -mine \"$LOCAL\" -theirs \"$REMOTE\" -merged \"$MERGED\"", null, false), /** * See: https://tortoisegit.org/docs/tortoisegitmerge/tme-automation.html#tme-automation-basics *

* Hint: merge without base is not supported *

*

* Hint: cannot diff *

*/ tortoisemerge("tortoisemerge", "-base:\"$BASE\" -mine:\"$LOCAL\" -theirs:\"$REMOTE\" -merged:\"$MERGED\"", null, false), /** * See: http://vimdoc.sourceforge.net/htmldoc/diff.html */ vimdiff("vim", gvimdiff), /** * See: http://vimdoc.sourceforge.net/htmldoc/diff.html */ vimdiff2("vim", gvimdiff2), /** * See: http://vimdoc.sourceforge.net/htmldoc/diff.html */ vimdiff3("vim", gvimdiff3), /** * See: http://manual.winmerge.org/Command_line.html *

* Hint: check how 'mergetool_find_win32_cmd "WinMergeU.exe" "WinMerge"' * works *

*/ winmerge("WinMergeU", "-u -e -dl Local -dr Remote \"$LOCAL\" \"$REMOTE\" \"$MERGED\"", "-u -e -dl Local -dr Remote \"$LOCAL\" \"$REMOTE\" \"$MERGED\"", false), /** * See: http://furius.ca/xxdiff/doc/xxdiff-doc.html */ xxdiff("xxdiff", "-X --show-merged-pane -R 'Accel.SaveAsMerged: \"Ctrl+S\"' -R 'Accel.Search: \"Ctrl+F\"' -R 'Accel.SearchForward: \"Ctrl+G\"' --merged-file \"$MERGED\" \"$LOCAL\" \"$BASE\" \"$REMOTE\"", "-X -R 'Accel.SaveAsMerged: \"Ctrl+S\"' -R 'Accel.Search: \"Ctrl+F\"' -R 'Accel.SearchForward: \"Ctrl+G\"' --merged-file \"$MERGED\" \"$LOCAL\" \"$REMOTE\"", false); CommandLineMergeTool(String path, String parametersWithBase, String parametersWithoutBase, boolean exitCodeTrustable) { this.path = path; this.parametersWithBase = parametersWithBase; this.parametersWithoutBase = parametersWithoutBase; this.exitCodeTrustable = exitCodeTrustable; } CommandLineMergeTool(String path, CommandLineMergeTool from) { this(path, from.getParameters(true), from.getParameters(false), from.isExitCodeTrustable()); } private final String path; private final String parametersWithBase; private final String parametersWithoutBase; private final boolean exitCodeTrustable; /** * Get path * * @return path */ public String getPath() { return path; } /** * Get parameters * * @param withBase * return parameters with base present? * @return parameters with or without base present */ public String getParameters(boolean withBase) { if (withBase) { return parametersWithBase; } return parametersWithoutBase; } /** * Whether exit code can be trusted * * @return parameters */ public boolean isExitCodeTrustable() { return exitCodeTrustable; } /** * Whether command with with base present is valid * * @return true if command with base present is valid, false otherwise */ public boolean canMergeWithoutBasePresent() { return parametersWithoutBase != null; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3540 Content-Disposition: inline; filename="DiffToolConfig.java" Last-Modified: Tue, 06 May 2025 14:13:29 GMT Expires: Tue, 06 May 2025 14:18:29 GMT ETag: "e74337a8a156c6e110dbb99b7ca5d3cb0afc9229" /* * Copyright (C) 2018-2021, Andre Bossert * * 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_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 java.util.HashMap; import java.util.Map; import java.util.Set; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config.SectionParser; import org.eclipse.jgit.lib.internal.BooleanTriState; /** * Keeps track of difftool related configuration options. */ public class DiffToolConfig { /** Key for {@link Config#get(SectionParser)}. */ public static final Config.SectionParser KEY = DiffToolConfig::new; private final String toolName; private final String guiToolName; private final boolean prompt; private final BooleanTriState trustExitCode; private final Map tools; private DiffToolConfig(Config rc) { toolName = rc.getString(CONFIG_DIFF_SECTION, null, CONFIG_KEY_TOOL); guiToolName = rc.getString(CONFIG_DIFF_SECTION, null, CONFIG_KEY_GUITOOL); prompt = rc.getBoolean(CONFIG_DIFFTOOL_SECTION, toolName, CONFIG_KEY_PROMPT, true); String trustStr = rc.getString(CONFIG_DIFFTOOL_SECTION, toolName, CONFIG_KEY_TRUST_EXIT_CODE); if (trustStr != null) { trustExitCode = Boolean.parseBoolean(trustStr) ? BooleanTriState.TRUE : BooleanTriState.FALSE; } else { trustExitCode = BooleanTriState.UNSET; } tools = new HashMap<>(); Set subsections = rc.getSubsections(CONFIG_DIFFTOOL_SECTION); for (String name : subsections) { String cmd = rc.getString(CONFIG_DIFFTOOL_SECTION, name, CONFIG_KEY_CMD); String path = rc.getString(CONFIG_DIFFTOOL_SECTION, name, CONFIG_KEY_PATH); if ((cmd != null) || (path != null)) { tools.put(name, new UserDefinedDiffTool(name, path, cmd)); } } } /** * Get default tool name * * @return the default diff tool name (diff.tool) */ public String getDefaultToolName() { return toolName; } /** * Get default GUI tool name * * @return the default GUI diff tool name (diff.guitool) */ public String getDefaultGuiToolName() { return guiToolName; } /** * Get difftool.prompt option * * @return the diff tool "prompt" option (difftool.prompt) */ public boolean isPrompt() { return prompt; } /** * Get difftool.trustExitCode option * * @return the diff tool "trust exit code" option (difftool.trustExitCode) */ public boolean isTrustExitCode() { return trustExitCode == BooleanTriState.TRUE; } /** * Get tools map * * @return the tools map */ public Map getTools() { return tools; } /** * Get tool names * * @return the tool names */ public Set getToolNames() { return tools.keySet(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 11177 Content-Disposition: inline; filename="DiffTools.java" Last-Modified: Tue, 06 May 2025 14:13:29 GMT Expires: Tue, 06 May 2025 14:18:29 GMT ETag: "6a67bf3a1aee4b5fd25ffd4008f0103ee3755430" /* * Copyright (C) 2018-2022, Andre Bossert * Copyright (C) 2019, Tim Neumann * * 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.io.File; import java.io.IOException; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.TreeMap; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.lib.internal.BooleanTriState; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FS.ExecutionResult; import org.eclipse.jgit.util.StringUtils; /** * Manages diff tools. */ public class DiffTools { private final FS fs; private final File gitDir; private final File workTree; private final DiffToolConfig config; private final Repository repo; private final Map predefinedTools; private final Map userDefinedTools; /** * Creates the external diff-tools manager for given repository. * * @param repo * the repository */ public DiffTools(Repository repo) { this(repo, repo.getConfig()); } /** * Creates the external merge-tools manager for given configuration. * * @param config * the git configuration */ public DiffTools(StoredConfig config) { this(null, config); } private DiffTools(Repository repo, StoredConfig config) { this.repo = repo; this.config = config.get(DiffToolConfig.KEY); this.gitDir = repo == null ? null : repo.getDirectory(); this.fs = repo == null ? FS.DETECTED : repo.getFS(); this.workTree = repo == null ? null : repo.getWorkTree(); predefinedTools = setupPredefinedTools(); userDefinedTools = setupUserDefinedTools(predefinedTools); } /** * Compare two versions of a file. * * @param localFile * The local/left version of the file. * @param remoteFile * The remote/right version of the file. * @param toolName * Optionally the name of the tool to use. If not given the * default tool will be used. * @param prompt * Optionally a flag whether to prompt the user before compare. * If not given the default will be used. * @param gui * A flag whether to prefer a gui tool. * @param trustExitCode * Optionally a flag whether to trust the exit code of the tool. * If not given the default will be used. * @param promptHandler * The handler to use when needing to prompt the user if he wants * to continue. * @param noToolHandler * The handler to use when needing to inform the user, that no * tool is configured. * @return the optional result of executing the tool if it was executed * @throws ToolException * when the tool fails */ public Optional compare(FileElement localFile, FileElement remoteFile, Optional toolName, BooleanTriState prompt, boolean gui, BooleanTriState trustExitCode, PromptContinueHandler promptHandler, InformNoToolHandler noToolHandler) throws ToolException { String toolNameToUse; if (toolName == null) { throw new ToolException(JGitText.get().diffToolNullError); } if (toolName.isPresent()) { toolNameToUse = toolName.get(); } else { toolNameToUse = getDefaultToolName(gui); } if (StringUtils.isEmptyOrNull(toolNameToUse)) { throw new ToolException(JGitText.get().diffToolNotGivenError); } boolean doPrompt; if (prompt != BooleanTriState.UNSET) { doPrompt = prompt == BooleanTriState.TRUE; } else { doPrompt = isInteractive(); } if (doPrompt) { if (!promptHandler.prompt(toolNameToUse)) { return Optional.empty(); } } boolean trust; if (trustExitCode != BooleanTriState.UNSET) { trust = trustExitCode == BooleanTriState.TRUE; } else { trust = config.isTrustExitCode(); } ExternalDiffTool tool = getTool(toolNameToUse); if (tool == null) { throw new ToolException( "External diff tool is not defined: " + toolNameToUse); //$NON-NLS-1$ } return Optional.of( compare(localFile, remoteFile, tool, trust)); } /** * Compare two versions of a file. * * @param localFile * the local file element * @param remoteFile * the remote file element * @param tool * the selected tool * @param trustExitCode * the "trust exit code" option * @return the execution result from tool * @throws ToolException * when the tool fails */ public ExecutionResult compare(FileElement localFile, FileElement remoteFile, ExternalDiffTool tool, boolean trustExitCode) throws ToolException { try { if (tool == null) { throw new ToolException(JGitText .get().diffToolNotSpecifiedInGitAttributesError); } // prepare the command (replace the file paths) String command = ExternalToolUtils.prepareCommand(tool.getCommand(), localFile, remoteFile, null, null); // prepare the environment Map env = ExternalToolUtils.prepareEnvironment( gitDir, localFile, remoteFile, null, null); // execute the tool CommandExecutor cmdExec = new CommandExecutor(fs, trustExitCode); return cmdExec.run(command, workTree, env); } catch (IOException | InterruptedException e) { throw new ToolException(e); } finally { localFile.cleanTemporaries(); remoteFile.cleanTemporaries(); } } /** * Get user defined tool names. * * @return the user defined tool names */ public Set getUserDefinedToolNames() { return userDefinedTools.keySet(); } /** * Get predefined tool names. * * @return the predefined tool names */ public Set getPredefinedToolNames() { return predefinedTools.keySet(); } /** * Get all tool names. * * @return the all tool names (default or available tool name is the first * in the set) */ public Set getAllToolNames() { String defaultName = getDefaultToolName(false); if (defaultName == null) { defaultName = getFirstAvailableTool(); } return ExternalToolUtils.createSortedToolSet(defaultName, getUserDefinedToolNames(), getPredefinedToolNames()); } /** * Provides {@link Optional} with the name of an external diff tool if * specified in git configuration for a path. * * The formed git configuration results from global rules as well as merged * rules from info and worktree attributes. * * Triggers {@link TreeWalk} until specified path found in the tree. * * @param path * path to the node in repository to parse git attributes for * @return name of the difftool if set * @throws ToolException * when the tool failed */ public Optional getExternalToolFromAttributes(final String path) throws ToolException { return ExternalToolUtils.getExternalToolFromAttributes(repo, path, ExternalToolUtils.KEY_DIFF_TOOL); } /** * Checks the availability of the predefined tools in the system. * * @return set of predefined available tools */ public Set getPredefinedAvailableTools() { Map defTools = getPredefinedTools(true); Set availableTools = new LinkedHashSet<>(); for (Entry elem : defTools.entrySet()) { if (elem.getValue().isAvailable()) { availableTools.add(elem.getKey()); } } return availableTools; } /** * Get user defined tools map. * * @return the user defined tools */ public Map getUserDefinedTools() { return Collections.unmodifiableMap(userDefinedTools); } /** * Get predefined tools map. * * @param checkAvailability * true: for checking if tools can be executed; ATTENTION: this * check took some time, do not execute often (store the map for * other actions); false: availability is NOT checked: * isAvailable() returns default false is this case! * @return the predefined tools with optionally checked availability (long * running operation) */ public Map getPredefinedTools( boolean checkAvailability) { if (checkAvailability) { for (ExternalDiffTool tool : predefinedTools.values()) { PreDefinedDiffTool predefTool = (PreDefinedDiffTool) tool; predefTool.setAvailable(ExternalToolUtils.isToolAvailable(fs, gitDir, workTree, predefTool.getPath())); } } return Collections.unmodifiableMap(predefinedTools); } /** * Get first available tool name. * * @return the name of first available predefined tool or null */ public String getFirstAvailableTool() { for (ExternalDiffTool tool : predefinedTools.values()) { if (ExternalToolUtils.isToolAvailable(fs, gitDir, workTree, tool.getPath())) { return tool.getName(); } } return null; } /** * Get default (gui-)tool name. * * @param gui * use the diff.guitool setting ? * @return the default tool name */ public String getDefaultToolName(boolean gui) { String guiToolName; if (gui) { guiToolName = config.getDefaultGuiToolName(); if (guiToolName != null) { return guiToolName; } } return config.getDefaultToolName(); } /** * Is interactive diff (prompt enabled) ? * * @return is interactive (config prompt enabled) ? */ public boolean isInteractive() { return config.isPrompt(); } private ExternalDiffTool getTool(final String name) { ExternalDiffTool tool = userDefinedTools.get(name); if (tool == null) { tool = predefinedTools.get(name); } return tool; } private static Map setupPredefinedTools() { Map tools = new TreeMap<>(); for (CommandLineDiffTool tool : CommandLineDiffTool.values()) { tools.put(tool.name(), new PreDefinedDiffTool(tool)); } return tools; } private Map setupUserDefinedTools( Map predefTools) { Map tools = new TreeMap<>(); Map userTools = config.getTools(); for (String name : userTools.keySet()) { ExternalDiffTool userTool = userTools.get(name); // if difftool..cmd is defined we have user defined tool if (userTool.getCommand() != null) { tools.put(name, userTool); } else if (userTool.getPath() != null) { // if difftool..path is defined we just overload the path // of predefined tool PreDefinedDiffTool predefTool = (PreDefinedDiffTool) predefTools .get(name); if (predefTool != null) { predefTool.setPath(userTool.getPath()); } } } return tools; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 866 Content-Disposition: inline; filename="ExternalDiffTool.java" Last-Modified: Tue, 06 May 2025 14:13:29 GMT Expires: Tue, 06 May 2025 14:18:29 GMT ETag: "e02697b7727635f79fd6aa42f52ca1f9100d7e2a" /* * Copyright (C) 2018-2021, Andre Bossert * * 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; /** * The external tool interface. */ public interface ExternalDiffTool { /** * Get tool name * * @return the tool name */ String getName(); /** * Get tool path * * @return the tool path */ String getPath(); /** * Get tool command * * @return the tool command */ String getCommand(); /** * Whether tool is available * * @return availability of the tool: true if tool can be executed and false * if not */ boolean isAvailable(); } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 898 Content-Disposition: inline; filename="ExternalMergeTool.java" Last-Modified: Tue, 06 May 2025 14:13:29 GMT Expires: Tue, 06 May 2025 14:18:29 GMT ETag: "022cd27f448e8f06e8c19abf0daf5c46b09979c9" /* * Copyright (C) 2018-2022, Andre Bossert * * 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 org.eclipse.jgit.lib.internal.BooleanTriState; /** * The merge tool interface. */ public interface ExternalMergeTool extends ExternalDiffTool { /** * Get the tool "trust exit code" option * * @return the tool "trust exit code" option */ BooleanTriState getTrustExitCode(); /** * Get tool command * * @param withBase * get command with base present (true) or without base present * (false) * @return the tool command */ String getCommand(boolean withBase); } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 7229 Content-Disposition: inline; filename="ExternalToolUtils.java" Last-Modified: Tue, 06 May 2025 14:13:29 GMT Expires: Tue, 06 May 2025 14:18:29 GMT ETag: "e5947102ebab6345fa661026d7864d7408aba9ce" /* * Copyright (C) 2018-2021, Andre Bossert * * 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.File; import java.io.IOException; import java.util.LinkedHashSet; import java.util.Map; import java.util.Optional; import java.util.Set; import org.eclipse.jgit.attributes.Attributes; import org.eclipse.jgit.errors.RevisionSyntaxException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.WorkingTreeIterator; import org.eclipse.jgit.treewalk.filter.NotIgnoredFilter; import org.eclipse.jgit.util.FS; /** * Utilities for diff- and merge-tools. */ public class ExternalToolUtils { /** * Key for merge tool git configuration section */ public static final String KEY_MERGE_TOOL = "mergetool"; //$NON-NLS-1$ /** * Key for diff tool git configuration section */ public static final String KEY_DIFF_TOOL = "difftool"; //$NON-NLS-1$ /** * 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 * if an IO error occurred */ public static String prepareCommand(String command, FileElement localFile, FileElement remoteFile, FileElement mergedFile, FileElement baseFile) throws IOException { if (localFile != null) { command = localFile.replaceVariable(command); } if (remoteFile != null) { command = remoteFile.replaceVariable(command); } if (mergedFile != null) { command = mergedFile.replaceVariable(command); } if (baseFile != null) { command = baseFile.replaceVariable(command); } return command; } /** * Prepare environment needed for execution. * * @param gitDir * the .git directory * @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 * if an IO error occurred */ public static Map prepareEnvironment(File gitDir, FileElement localFile, FileElement remoteFile, FileElement mergedFile, FileElement baseFile) throws IOException { Map env = new TreeMap<>(); if (gitDir != null) { env.put(Constants.GIT_DIR_KEY, gitDir.getAbsolutePath()); } if (localFile != null) { localFile.addToEnv(env); } if (remoteFile != null) { remoteFile.addToEnv(env); } if (mergedFile != null) { mergedFile.addToEnv(env); } if (baseFile != null) { baseFile.addToEnv(env); } return env; } /** * Quote path * * @param path * the path to be quoted * @return quoted path if it contains spaces */ @SuppressWarnings("nls") public static String quotePath(String path) { // handling of spaces in path if (path.contains(" ")) { // add quotes before if needed if (!path.startsWith("\"")) { path = "\"" + path; } // add quotes after if needed if (!path.endsWith("\"")) { path = path + "\""; } } return path; } /** * Whether tool is available * * @param fs * the file system abstraction * @param gitDir * the .git directory * @param directory * the working directory * @param path * the tool path * @return true if tool available and false otherwise */ public static boolean isToolAvailable(FS fs, File gitDir, File directory, String path) { boolean available = true; try { CommandExecutor cmdExec = new CommandExecutor(fs, false); available = cmdExec.checkExecutable(path, directory, prepareEnvironment(gitDir, null, null, null, null)); } catch (Exception e) { available = false; } return available; } /** * Create sorted tool set * * @param defaultName * the default tool name * @param userDefinedNames * the user defined tool names * @param preDefinedNames * the pre defined tool names * @return the sorted tool names set: first element is default tool name if * valid, then user defined tool names and then pre defined tool * names */ public static Set createSortedToolSet(String defaultName, Set userDefinedNames, Set preDefinedNames) { Set names = new LinkedHashSet<>(); if (defaultName != null) { // remove defaultName from both sets Set namesPredef = new LinkedHashSet<>(); Set namesUser = new LinkedHashSet<>(); namesUser.addAll(userDefinedNames); namesUser.remove(defaultName); namesPredef.addAll(preDefinedNames); namesPredef.remove(defaultName); // add defaultName as first in set names.add(defaultName); names.addAll(namesUser); names.addAll(namesPredef); } else { names.addAll(userDefinedNames); names.addAll(preDefinedNames); } return names; } /** * Provides {@link Optional} with the name of an external tool if specified * in git configuration for a path. * * The formed git configuration results from global rules as well as merged * rules from info and worktree attributes. * * Triggers {@link TreeWalk} until specified path found in the tree. * * @param repository * target repository to traverse into * @param path * path to the node in repository to parse git attributes for * @param toolKey * config key name for the tool * @return attribute value for the given tool key if set * @throws ToolException * if the tool failed */ public static Optional getExternalToolFromAttributes( final Repository repository, final String path, final String toolKey) throws ToolException { try { WorkingTreeIterator treeIterator = new FileTreeIterator(repository); try (TreeWalk walk = new TreeWalk(repository)) { walk.addTree(treeIterator); walk.setFilter(new NotIgnoredFilter(0)); while (walk.next()) { String treePath = walk.getPathString(); if (treePath.equals(path)) { Attributes attrs = walk.getAttributes(); if (attrs.containsKey(toolKey)) { return Optional.of(attrs.getValue(toolKey)); } } if (walk.isSubtree()) { walk.enterSubtree(); } } // no external tool specified return Optional.empty(); } } catch (RevisionSyntaxException | IOException e) { throw new ToolException(e); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 6880 Content-Disposition: inline; filename="FileElement.java" Last-Modified: Tue, 06 May 2025 14:13:29 GMT Expires: Tue, 06 May 2025 14:18:29 GMT ETag: "37eb2d9f7d40b9ea037496f3947d64cb7f528e46" /* * Copyright (C) 2018-2021, Andre Bossert * * 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.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Map; import org.eclipse.jgit.diff.DiffEntry; /** * The element used as left or right file for compare. * */ 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 Type type; private final File workDir; private InputStream stream; private File tempFile; /** * Creates file element for path. * * @param path * the file path * @param type * the element type */ public FileElement(String path, Type type) { this(path, type, null); } /** * Creates file element for path. * * @param path * the file path * @param type * the element type * @param workDir * the working directory of the path (can be null, then current * working dir is used) */ public FileElement(String path, Type type, File workDir) { this(path, type, workDir, null); } /** * Create file element * * @param path * the file path * @param type * the element type * @param workDir * the working directory of the path (can be null, then current * working dir is used) * @param stream * the object stream to load and write on demand, @see getFile(), * to tempFile once (can be null) */ public FileElement(String path, Type type, File workDir, InputStream stream) { this.path = path; this.type = type; this.workDir = workDir; this.stream = stream; } /** * Get path * * @return the file path */ public String getPath() { return path; } /** * Get type * * @return the element type */ public Type getType() { return type; } /** * Get file *

* Return *

    *
  • a temporary file if already created and stream is not valid
  • *
  • OR a real file from work tree: if no temp file was created (@see * createTempFile()) and if no stream was set
  • *
  • OR an empty temporary file if path is "/dev/null"
  • *
  • OR a temporary file with stream content if stream is valid (not * null); stream is closed and invalidated (set to null) after write to temp * file, so stream is used only once during first call!
  • *
* * @return the object stream * @throws IOException * if an IO error occurred */ public File getFile() throws IOException { // if we have already temp file and no stream // then just return this temp file (it was filled from outside) if ((tempFile != null) && (stream == null)) { return tempFile; } File file = new File(workDir, path); // if we have a stream or file is missing (path is "/dev/null") // then optionally create temporary file and fill it with stream content if ((stream != null) || isNullPath()) { if (tempFile == null) { tempFile = getTempFile(file, type.name(), null); } if (stream != null) { copyFromStream(tempFile, stream); } // invalidate the stream, because it is used once stream = null; return tempFile; } return file; } /** * 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 * if an IO error occurred */ public File createTempFile(File directory) throws IOException { if (tempFile == null) { tempFile = getTempFile(new File(path), type.name(), directory); } return tempFile; } /** * Delete and invalidate temporary file if necessary. */ public void cleanTemporaries() { if (tempFile != null && tempFile.exists()) { tempFile.delete(); } tempFile = null; } /** * Replace variable in input. * * @param input * the input string * @return the replaced input string * @throws IOException * if an IO error occurred */ 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 * if an IO error occurred */ public void addToEnv(Map env) throws IOException { env.put(type.name(), getFile().getPath()); } private static File getTempFile(final File file, final String midName, final File workingDir) throws IOException { String[] fileNameAndExtension = splitBaseFileNameAndExtension(file); // TODO: avoid long random file name (number generated by // createTempFile) return File.createTempFile( fileNameAndExtension[0] + "_" + midName + "_", //$NON-NLS-1$ //$NON-NLS-2$ fileNameAndExtension[1], workingDir); } private static void copyFromStream(final File file, final InputStream stream) throws IOException, FileNotFoundException { try (OutputStream outStream = new FileOutputStream(file)) { int read = 0; byte[] bytes = new byte[8 * 1024]; while ((read = stream.read(bytes)) != -1) { outStream.write(bytes, 0, read); } } finally { // stream can only be consumed once --> close it and invalidate stream.close(); } } private static String[] splitBaseFileNameAndExtension(File file) { String[] result = new String[2]; result[0] = file.getName(); result[1] = ""; //$NON-NLS-1$ int idx = result[0].lastIndexOf("."); //$NON-NLS-1$ // if "." was found (>-1) and last-index is not first char (>0), then // split (same behavior like cgit) if (idx > 0) { result[1] = result[0].substring(idx, result[0].length()); result[0] = result[0].substring(0, idx); } return result; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 811 Content-Disposition: inline; filename="InformNoToolHandler.java" Last-Modified: Tue, 06 May 2025 14:13:29 GMT Expires: Tue, 06 May 2025 14:18:29 GMT ETag: "36b290d37d49502ab5d76a05abe18ee824e37ceb" /* * Copyright (C) 2018-2019, Tim Neumann * * 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.List; /** * A handler for when the diff/merge tool manager wants to inform the user that * no tool has been configured and one of the default tools will be used. */ public interface InformNoToolHandler { /** * Inform the user, that no tool is configured and that one of the given * tools is used. * * @param toolNames * The tools which are tried */ void inform(List toolNames); } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 4501 Content-Disposition: inline; filename="MergeToolConfig.java" Last-Modified: Tue, 06 May 2025 14:13:29 GMT Expires: Tue, 06 May 2025 14:18:29 GMT ETag: "40cb82060227d9fc42e51a0b98175b7fdfa8e524" /* * Copyright (C) 2018-2022, Andre Bossert * * 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_KEEP_BACKUP; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_KEEP_TEMPORARIES; 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_KEY_WRITE_TO_TEMP; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGETOOL_SECTION; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGE_SECTION; import java.util.HashMap; import java.util.Map; import java.util.Set; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config.SectionParser; import org.eclipse.jgit.lib.internal.BooleanTriState; /** * Keeps track of merge tool related configuration options. */ public class MergeToolConfig { /** Key for {@link Config#get(SectionParser)}. */ public static final Config.SectionParser KEY = MergeToolConfig::new; private final String toolName; private final String guiToolName; private final boolean prompt; private final boolean keepBackup; private final boolean keepTemporaries; private final boolean writeToTemp; private final Map tools; private MergeToolConfig(Config rc) { toolName = rc.getString(CONFIG_MERGE_SECTION, null, CONFIG_KEY_TOOL); guiToolName = rc.getString(CONFIG_MERGE_SECTION, null, CONFIG_KEY_GUITOOL); prompt = rc.getBoolean(CONFIG_MERGETOOL_SECTION, toolName, CONFIG_KEY_PROMPT, true); keepBackup = rc.getBoolean(CONFIG_MERGETOOL_SECTION, CONFIG_KEY_KEEP_BACKUP, true); keepTemporaries = rc.getBoolean(CONFIG_MERGETOOL_SECTION, CONFIG_KEY_KEEP_TEMPORARIES, false); writeToTemp = rc.getBoolean(CONFIG_MERGETOOL_SECTION, CONFIG_KEY_WRITE_TO_TEMP, false); tools = new HashMap<>(); Set subsections = rc.getSubsections(CONFIG_MERGETOOL_SECTION); for (String name : subsections) { String cmd = rc.getString(CONFIG_MERGETOOL_SECTION, name, CONFIG_KEY_CMD); String path = rc.getString(CONFIG_MERGETOOL_SECTION, name, CONFIG_KEY_PATH); BooleanTriState trustExitCode = BooleanTriState.FALSE; String trustStr = rc.getString(CONFIG_MERGETOOL_SECTION, name, CONFIG_KEY_TRUST_EXIT_CODE); if (trustStr != null) { trustExitCode = Boolean.valueOf(trustStr).booleanValue() ? BooleanTriState.TRUE : BooleanTriState.FALSE; } else { trustExitCode = BooleanTriState.UNSET; } if ((cmd != null) || (path != null)) { tools.put(name, new UserDefinedMergeTool(name, path, cmd, trustExitCode)); } } } /** * Get default tool name * * @return the default merge tool name (merge.tool) */ public String getDefaultToolName() { return toolName; } /** * Get default GUI tool name * * @return the default GUI merge tool name (merge.guitool) */ public String getDefaultGuiToolName() { return guiToolName; } /** * Get mergetool.prompt option * * @return the merge tool "prompt" option (mergetool.prompt) */ public boolean isPrompt() { return prompt; } /** * Get tool "keep backup" option * * @return the tool "keep backup" option */ public boolean isKeepBackup() { return keepBackup; } /** * Get tool "keep temporaries" option * * @return the tool "keepTemporaries" option */ public boolean isKeepTemporaries() { return keepTemporaries; } /** * Get the tool "write to temp" option * * @return the tool "write to temp" option */ public boolean isWriteToTemp() { return writeToTemp; } /** * Get the tools map * * @return the tools map */ public Map getTools() { return tools; } /** * Get tool names * * @return the tool names */ public Set getToolNames() { return tools.keySet(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 13893 Content-Disposition: inline; filename="MergeTools.java" Last-Modified: Tue, 06 May 2025 14:13:29 GMT Expires: Tue, 06 May 2025 14:18:29 GMT ETag: "213ce6871e4aedf095e37e80af9fded57300c178" /* * Copyright (C) 2018-2022, Andre Bossert * Copyright (C) 2019, Tim Neumann * * 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.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.TreeMap; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.lib.internal.BooleanTriState; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FS.ExecutionResult; import org.eclipse.jgit.util.StringUtils; /** * Manages merge tools. */ public class MergeTools { private final FS fs; private final File gitDir; private final File workTree; private final MergeToolConfig config; private final Repository repo; private final Map predefinedTools; private final Map userDefinedTools; /** * Creates the external merge-tools manager for given repository. * * @param repo * the repository */ public MergeTools(Repository repo) { this(repo, repo.getConfig()); } /** * Creates the external diff-tools manager for given configuration. * * @param config * the git configuration */ public MergeTools(StoredConfig config) { this(null, config); } private MergeTools(Repository repo, StoredConfig config) { this.repo = repo; this.config = config.get(MergeToolConfig.KEY); this.gitDir = repo == null ? null : repo.getDirectory(); this.fs = repo == null ? FS.DETECTED : repo.getFS(); this.workTree = repo == null ? null : repo.getWorkTree(); predefinedTools = setupPredefinedTools(); userDefinedTools = setupUserDefinedTools(predefinedTools); } /** * Merge two versions of a file with optional base file. * * @param localFile * The local/left version of the file. * @param remoteFile * The remote/right version of the file. * @param mergedFile * The file for the result. * @param baseFile * The base version of the file. May be null. * @param tempDir * The tmepDir used for the files. May be null. * @param toolName * Optionally the name of the tool to use. If not given the * default tool will be used. * @param prompt * Optionally a flag whether to prompt the user before compare. * If not given the default will be used. * @param gui * A flag whether to prefer a gui tool. * @param promptHandler * The handler to use when needing to prompt the user if he wants * to continue. * @param noToolHandler * The handler to use when needing to inform the user, that no * tool is configured. * @return the optional result of executing the tool if it was executed * @throws ToolException * when the tool fails */ public Optional merge(FileElement localFile, FileElement remoteFile, FileElement mergedFile, FileElement baseFile, File tempDir, Optional toolName, BooleanTriState prompt, boolean gui, PromptContinueHandler promptHandler, InformNoToolHandler noToolHandler) throws ToolException { String toolNameToUse; if (toolName == null) { throw new ToolException(JGitText.get().diffToolNullError); } if (toolName.isPresent()) { toolNameToUse = toolName.get(); } else { toolNameToUse = getDefaultToolName(gui); if (StringUtils.isEmptyOrNull(toolNameToUse)) { noToolHandler.inform(new ArrayList<>(predefinedTools.keySet())); toolNameToUse = getFirstAvailableTool(); } } if (StringUtils.isEmptyOrNull(toolNameToUse)) { throw new ToolException(JGitText.get().diffToolNotGivenError); } boolean doPrompt; if (prompt != BooleanTriState.UNSET) { doPrompt = prompt == BooleanTriState.TRUE; } else { doPrompt = isInteractive(); } if (doPrompt) { if (!promptHandler.prompt(toolNameToUse)) { return Optional.empty(); } } ExternalMergeTool tool = getTool(toolNameToUse); if (tool == null) { throw new ToolException( "External merge tool is not defined: " + toolNameToUse); //$NON-NLS-1$ } return Optional.of(merge(localFile, remoteFile, mergedFile, baseFile, tempDir, tool)); } /** * Merge two versions of a file with optional base file. * * @param localFile * the local file element * @param remoteFile * the remote file element * @param mergedFile * the merged file element * @param baseFile * the base file element (can be null) * @param tempDir * the temporary directory (needed for backup and auto-remove, * can be null) * @param tool * the selected tool * @return the execution result from tool * @throws ToolException * if the tool failed */ public ExecutionResult merge(FileElement localFile, FileElement remoteFile, FileElement mergedFile, FileElement baseFile, File tempDir, ExternalMergeTool tool) throws ToolException { FileElement backup = null; ExecutionResult result = null; try { // create additional backup file (copy worktree file) backup = createBackupFile(mergedFile, tempDir != null ? tempDir : workTree); // prepare the command (replace the file paths) String command = ExternalToolUtils.prepareCommand( tool.getCommand(baseFile != null), localFile, remoteFile, mergedFile, baseFile); // prepare the environment Map env = ExternalToolUtils.prepareEnvironment( gitDir, localFile, remoteFile, mergedFile, baseFile); boolean trust = tool.getTrustExitCode() == BooleanTriState.TRUE; // execute the tool CommandExecutor cmdExec = new CommandExecutor(fs, trust); result = cmdExec.run(command, workTree, env); // keep backup as .orig file if (backup != null) { keepBackupFile(mergedFile.getPath(), backup); } return result; } catch (IOException | InterruptedException e) { throw new ToolException(e); } finally { // always delete backup file (ignore that it was may be already // moved to keep-backup file) if (backup != null) { backup.cleanTemporaries(); } // if the tool returns an error and keepTemporaries is set to true, // then these temporary files will be preserved if (!((result == null) && config.isKeepTemporaries())) { // delete the files localFile.cleanTemporaries(); remoteFile.cleanTemporaries(); if (baseFile != null) { baseFile.cleanTemporaries(); } // delete temporary directory if needed if (config.isWriteToTemp() && (tempDir != null) && tempDir.exists()) { tempDir.delete(); } } } } private FileElement createBackupFile(FileElement from, File toParentDir) throws IOException { FileElement backup = null; Path path = Paths.get(from.getPath()); if (Files.exists(path)) { backup = new FileElement(from.getPath(), FileElement.Type.BACKUP); Files.copy(path, backup.createTempFile(toParentDir).toPath(), StandardCopyOption.REPLACE_EXISTING); } return backup; } /** * Create temporary directory. * * @return the created temporary directory if (mergetol.writeToTemp == true) * or null if not configured or false. * @throws IOException * if an IO error occurred */ public File createTempDirectory() throws IOException { return config.isWriteToTemp() ? Files.createTempDirectory("jgit-mergetool-").toFile() //$NON-NLS-1$ : null; } /** * Get user defined tool names. * * @return the user defined tool names */ public Set getUserDefinedToolNames() { return userDefinedTools.keySet(); } /** * Get predefined tool names * * @return the predefined tool names */ public Set getPredefinedToolNames() { return predefinedTools.keySet(); } /** * Get all tool names. * * @return the all tool names (default or available tool name is the first * in the set) */ public Set getAllToolNames() { String defaultName = getDefaultToolName(false); if (defaultName == null) { defaultName = getFirstAvailableTool(); } return ExternalToolUtils.createSortedToolSet(defaultName, getUserDefinedToolNames(), getPredefinedToolNames()); } /** * Provides {@link Optional} with the name of an external merge tool if * specified in git configuration for a path. * * The formed git configuration results from global rules as well as merged * rules from info and worktree attributes. * * Triggers {@link TreeWalk} until specified path found in the tree. * * @param path * path to the node in repository to parse git attributes for * @return name of the difftool if set * @throws ToolException * if the tool failed */ public Optional getExternalToolFromAttributes(final String path) throws ToolException { return ExternalToolUtils.getExternalToolFromAttributes(repo, path, ExternalToolUtils.KEY_MERGE_TOOL); } /** * Checks the availability of the predefined tools in the system. * * @return set of predefined available tools */ public Set getPredefinedAvailableTools() { Map defTools = getPredefinedTools(true); Set availableTools = new LinkedHashSet<>(); for (Entry elem : defTools.entrySet()) { if (elem.getValue().isAvailable()) { availableTools.add(elem.getKey()); } } return availableTools; } /** * Get user defined tools * * @return the user defined tools */ public Map getUserDefinedTools() { return Collections.unmodifiableMap(userDefinedTools); } /** * Get predefined tools map. * * @param checkAvailability * true: for checking if tools can be executed; ATTENTION: this * check took some time, do not execute often (store the map for * other actions); false: availability is NOT checked: * isAvailable() returns default false is this case! * @return the predefined tools with optionally checked availability (long * running operation) */ public Map getPredefinedTools( boolean checkAvailability) { if (checkAvailability) { for (ExternalMergeTool tool : predefinedTools.values()) { PreDefinedMergeTool predefTool = (PreDefinedMergeTool) tool; predefTool.setAvailable(ExternalToolUtils.isToolAvailable(fs, gitDir, workTree, predefTool.getPath())); } } return Collections.unmodifiableMap(predefinedTools); } /** * Get first available tool name. * * @return the name of first available predefined tool or null */ public String getFirstAvailableTool() { String name = null; for (ExternalMergeTool tool : predefinedTools.values()) { if (ExternalToolUtils.isToolAvailable(fs, gitDir, workTree, tool.getPath())) { name = tool.getName(); break; } } return name; } /** * Is interactive merge (prompt enabled) ? * * @return is interactive (config prompt enabled) ? */ public boolean isInteractive() { return config.isPrompt(); } /** * Get the default (gui-)tool name. * * @param gui * use the diff.guitool setting ? * @return the default tool name */ public String getDefaultToolName(boolean gui) { return gui ? config.getDefaultGuiToolName() : config.getDefaultToolName(); } private ExternalMergeTool getTool(final String name) { ExternalMergeTool tool = userDefinedTools.get(name); if (tool == null) { tool = predefinedTools.get(name); } return tool; } private void keepBackupFile(String mergedFilePath, FileElement backup) throws IOException { if (config.isKeepBackup()) { Path backupPath = backup.getFile().toPath(); Files.move(backupPath, backupPath.resolveSibling( Paths.get(mergedFilePath).getFileName() + ".orig"), //$NON-NLS-1$ StandardCopyOption.REPLACE_EXISTING); } } private Map setupPredefinedTools() { Map tools = new TreeMap<>(); for (CommandLineMergeTool tool : CommandLineMergeTool.values()) { tools.put(tool.name(), new PreDefinedMergeTool(tool)); } return tools; } private Map setupUserDefinedTools( Map predefTools) { Map tools = new TreeMap<>(); Map userTools = config.getTools(); for (String name : userTools.keySet()) { ExternalMergeTool userTool = userTools.get(name); // if mergetool..cmd is defined we have user defined tool if (userTool.getCommand() != null) { tools.put(name, userTool); } else if (userTool.getPath() != null) { // if mergetool..path is defined we just overload the path // of predefined tool PreDefinedMergeTool predefTool = (PreDefinedMergeTool) predefTools .get(name); if (predefTool != null) { predefTool.setPath(userTool.getPath()); if (userTool.getTrustExitCode() != BooleanTriState.UNSET) { predefTool .setTrustExitCode(userTool.getTrustExitCode()); } } } } return tools; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1452 Content-Disposition: inline; filename="PreDefinedDiffTool.java" Last-Modified: Tue, 06 May 2025 14:13:29 GMT Expires: Tue, 06 May 2025 14:18:29 GMT ETag: "c1d69b4f119e7b147940573de9bd114f8551aaa8" /* * Copyright (C) 2018-2021, Andre Bossert * * 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; /** * The pre-defined diff tool. */ public class PreDefinedDiffTool extends UserDefinedDiffTool { /** * Create a pre-defined diff tool * * @param name * the name * @param path * the path * @param parameters * the tool parameters as one string that is used together with * path as command */ public PreDefinedDiffTool(String name, String path, String parameters) { super(name, path, parameters); } /** * Creates the pre-defined diff tool * * @param tool * the command line diff tool * */ public PreDefinedDiffTool(CommandLineDiffTool tool) { this(tool.name(), tool.getPath(), tool.getParameters()); } /** * @param path * path string */ @Override public void setPath(String path) { super.setPath(path); } /** * {@inheritDoc} * * @return the concatenated path and command of the pre-defined diff tool */ @Override public String getCommand() { return ExternalToolUtils.quotePath(getPath()) + " " + super.getCommand(); //$NON-NLS-1$ } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2445 Content-Disposition: inline; filename="PreDefinedMergeTool.java" Last-Modified: Tue, 06 May 2025 14:13:29 GMT Expires: Tue, 06 May 2025 14:18:29 GMT ETag: "7b28d328208eab839ee32836cc133b24c4eae8aa" /* * Copyright (C) 2018-2022, Andre Bossert * * 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 org.eclipse.jgit.lib.internal.BooleanTriState; /** * The pre-defined merge tool. */ public class PreDefinedMergeTool extends UserDefinedMergeTool { /** * the tool parameters without base */ private final String parametersWithoutBase; /** * Creates the pre-defined merge tool * * @param name * the name * @param path * the path * @param parametersWithBase * the tool parameters that are used together with path as * command and "base is present" ($BASE) * @param parametersWithoutBase * the tool parameters that are used together with path as * command and "base is present" ($BASE) * @param trustExitCode * the "trust exit code" option */ public PreDefinedMergeTool(String name, String path, String parametersWithBase, String parametersWithoutBase, BooleanTriState trustExitCode) { super(name, path, parametersWithBase, trustExitCode); this.parametersWithoutBase = parametersWithoutBase; } /** * Creates the pre-defined merge tool * * @param tool * the command line merge tool * */ public PreDefinedMergeTool(CommandLineMergeTool tool) { this(tool.name(), tool.getPath(), tool.getParameters(true), tool.getParameters(false), tool.isExitCodeTrustable() ? BooleanTriState.TRUE : BooleanTriState.FALSE); } /** * @param trustExitCode * the "trust exit code" option */ @Override public void setTrustExitCode(BooleanTriState trustExitCode) { super.setTrustExitCode(trustExitCode); } /** * @return the tool command (with base present) */ @Override public String getCommand() { return getCommand(true); } /** * @param withBase * get command with base present (true) or without base present * (false) * @return the tool command */ @Override public String getCommand(boolean withBase) { return ExternalToolUtils.quotePath(getPath()) + " " //$NON-NLS-1$ + (withBase ? super.getCommand() : parametersWithoutBase); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 773 Content-Disposition: inline; filename="PromptContinueHandler.java" Last-Modified: Tue, 06 May 2025 14:13:29 GMT Expires: Tue, 06 May 2025 14:18:29 GMT ETag: "6ad33df2a01924576b559456936f846dd7421c21" /* * Copyright (C) 2018-2019, Tim Neumann * * 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; /** * A handler for when the diff/merge tool manager wants to prompt the user * whether to continue */ public interface PromptContinueHandler { /** * Prompt the user whether to continue with the next file by opening a given * tool. * * @param toolName * The name of the tool to open * @return Whether the user wants to continue */ boolean prompt(String toolName); } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3399 Content-Disposition: inline; filename="ToolException.java" Last-Modified: Tue, 06 May 2025 14:13:29 GMT Expires: Tue, 06 May 2025 14:18:29 GMT ETag: "cd11325433b30703011d7b703622f8b1ceb698cc" /* * Copyright (C) 2018-2021, Andre Bossert * * 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 org.eclipse.jgit.util.FS.ExecutionResult; import org.eclipse.jgit.util.SystemReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tool exception for differentiation. * */ public class ToolException extends Exception { private final static Logger LOG = LoggerFactory .getLogger(ToolException.class); private final ExecutionResult result; private final boolean commandExecutionError; /** * the serial version UID */ private static final long serialVersionUID = 1L; /** * Create tool exception */ public ToolException() { this(null, null, false); } /** * Create tool exception * * @param message * the exception message */ public ToolException(String message) { this(message, null, false); } /** * Create tool exception * * @param message * the exception message * @param result * the execution result * @param commandExecutionError * is command execution error happened ? */ public ToolException(String message, ExecutionResult result, boolean commandExecutionError) { super(message); this.result = result; this.commandExecutionError = commandExecutionError; } /** * Create tool exception * * @param message * the exception message * @param cause * the cause for throw */ public ToolException(String message, Throwable cause) { super(message, cause); result = null; commandExecutionError = false; } /** * Create tool exception * * @param cause * the cause for throw */ public ToolException(Throwable cause) { super(cause); result = null; commandExecutionError = false; } /** * Whether result is valid * * @return true if result is valid, false else */ public boolean isResult() { return result != null; } /** * Get execution result * * @return the execution result */ public ExecutionResult getResult() { return result; } /** * Whether execution failed with an error * * @return true if command execution error appears, false otherwise */ public boolean isCommandExecutionError() { return commandExecutionError; } /** * Get buffered stderr as a String * * @return the result Stderr */ public String getResultStderr() { if (result == null) { return ""; //$NON-NLS-1$ } try { return new String(result.getStderr().toByteArray(), SystemReader.getInstance().getDefaultCharset()); } catch (Exception e) { LOG.warn("Failed to retrieve standard error output", e); //$NON-NLS-1$ } return ""; //$NON-NLS-1$ } /** * Get buffered stdout as a String * * @return the result Stdout */ public String getResultStdout() { if (result == null) { return ""; //$NON-NLS-1$ } try { return new String(result.getStdout().toByteArray(), SystemReader.getInstance().getDefaultCharset()); } catch (Exception e) { LOG.warn("Failed to retrieve standard output", e); //$NON-NLS-1$ } return ""; //$NON-NLS-1$ } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3001 Content-Disposition: inline; filename="UserDefinedDiffTool.java" Last-Modified: Tue, 06 May 2025 14:13:29 GMT Expires: Tue, 06 May 2025 14:18:29 GMT ETag: "62bde28feb20f3442260887f89e359207e68ebec" /* * Copyright (C) 2018-2021, Andre Bossert * * 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; /** * The user-defined diff tool. */ public class UserDefinedDiffTool implements ExternalDiffTool { private boolean available; /** * the diff tool name */ private final String name; /** * the diff tool path */ private String path; /** * the diff tool command */ private final String cmd; /** * Creates the diff tool * * @param name * the name * @param path * the path * @param cmd * the command */ public UserDefinedDiffTool(String name, String path, String cmd) { this.name = name; this.path = path; this.cmd = cmd; } /** * @return the diff tool name */ @Override public String getName() { return name; } /** * The path of the diff tool. * *

* The path to a pre-defined external diff tool can be overridden by * specifying {@code difftool..path} in a configuration file. *

*

* For a user defined diff tool (that does not override a pre-defined diff * tool), the path is ignored when invoking the tool. *

* * @return the diff tool path * * @see https://git-scm.com/docs/git-difftool */ @Override public String getPath() { return path; } /** * The command of the diff tool. * *

* A pre-defined external diff tool can be overridden using the tools name * in a configuration file. The overwritten tool is then a user defined tool * and the command of the diff tool is specified with * {@code difftool..cmd}. This command must work without prepending * the value of {@link #getPath()} and can sometimes include tool * parameters. *

* * @return the diff tool command * * @see https://git-scm.com/docs/git-difftool */ @Override public String getCommand() { return cmd; } /** * @return availability of the tool: true if tool can be executed and false * if not */ @Override public boolean isAvailable() { return available; } /** * Set whether tool is available * * @param available * true if tool can be found and false if not */ public void setAvailable(boolean available) { this.available = available; } /** * Overrides the path for the given tool. Equivalent to setting * {@code difftool..path}. * * @param path * the new diff tool path * * @see https://git-scm.com/docs/git-difftool */ public void setPath(String path) { this.path = path; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1743 Content-Disposition: inline; filename="UserDefinedMergeTool.java" Last-Modified: Tue, 06 May 2025 14:13:29 GMT Expires: Tue, 06 May 2025 14:18:29 GMT ETag: "b1a5a22ef5bc25daa39ca35752e1513f9d34aa74" /* * Copyright (C) 2018-2022, Andre Bossert * * 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 org.eclipse.jgit.lib.internal.BooleanTriState; /** * The user-defined merge tool. */ public class UserDefinedMergeTool extends UserDefinedDiffTool implements ExternalMergeTool { /** * the merge tool "trust exit code" option */ private BooleanTriState trustExitCode; /** * Creates the merge tool * * @param name * the name * @param path * the path * @param cmd * the command * @param trustExitCode * the "trust exit code" option */ public UserDefinedMergeTool(String name, String path, String cmd, BooleanTriState trustExitCode) { super(name, path, cmd); this.trustExitCode = trustExitCode; } /** * @return the "trust exit code" flag */ @Override public BooleanTriState getTrustExitCode() { return trustExitCode; } /** * Set "trust exit code" flag * * @param trustExitCode * the new "trust exit code" flag */ protected void setTrustExitCode(BooleanTriState trustExitCode) { this.trustExitCode = trustExitCode; } /** * Get command * * @param withBase * not used, because user-defined merge tool can only define one * cmd -> it must handle with and without base present (empty) * @return the tool command */ @Override public String getCommand(boolean withBase) { return getCommand(); } }