summaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit.pgm
diff options
context:
space:
mode:
authorAndre Bossert <andre.bossert@siemens.com>2019-02-23 15:25:10 +0100
committerSimeon Andreev <simeon.danailov.andreev@gmail.com>2021-11-25 18:17:16 +0100
commit48f4d97a226a2c9be4577b916cc0b99ce870939a (patch)
treefcb823d8e8ae96d8890561990f9f2d3eab2c8534 /org.eclipse.jgit.pgm
parent4453a6e04292e149bd69fe45a6383590cc1a60ab (diff)
downloadjgit-48f4d97a226a2c9be4577b916cc0b99ce870939a.tar.gz
jgit-48f4d97a226a2c9be4577b916cc0b99ce870939a.zip
Add command line support for "git difftool"
see: http://git-scm.com/docs/git-difftool * add command line support for "jgit difftool" * show supported commands with "jgit difftool --help" * added "git difftool --tool-help" to show the tools (empty now) * prepare for all other commands Bug: 356832 Change-Id: Ice0c13ef7953a20feaf25e7746d62b94ff4e89e5 Signed-off-by: Andre Bossert <andre.bossert@siemens.com> Signed-off-by: Simeon Andreev <simeon.danailov.andreev@gmail.com>
Diffstat (limited to 'org.eclipse.jgit.pgm')
-rw-r--r--org.eclipse.jgit.pgm/META-INF/MANIFEST.MF2
-rw-r--r--org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin1
-rw-r--r--org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties13
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTool.java255
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java3
5 files changed, 274 insertions, 0 deletions
diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
index 1ebd3a3e4c..fa0f452c59 100644
--- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
@@ -24,6 +24,7 @@ Import-Package: javax.servlet;version="[3.1.0,5.0.0)",
org.eclipse.jgit.errors;version="[6.1.0,6.2.0)",
org.eclipse.jgit.gitrepo;version="[6.1.0,6.2.0)",
org.eclipse.jgit.internal.storage.file;version="[6.1.0,6.2.0)",
+ org.eclipse.jgit.internal.diffmergetool;version="[6.1.0,6.2.0)",
org.eclipse.jgit.internal.storage.io;version="[6.1.0,6.2.0)",
org.eclipse.jgit.internal.storage.pack;version="[6.1.0,6.2.0)",
org.eclipse.jgit.internal.storage.reftable;version="[6.1.0,6.2.0)",
@@ -33,6 +34,7 @@ Import-Package: javax.servlet;version="[3.1.0,5.0.0)",
org.eclipse.jgit.lfs.server.s3;version="[6.1.0,6.2.0)",
org.eclipse.jgit.lib;version="[6.1.0,6.2.0)",
org.eclipse.jgit.merge;version="[6.1.0,6.2.0)",
+ org.eclipse.jgit.lib.internal;version="[6.1.0,6.2.0)",
org.eclipse.jgit.nls;version="[6.1.0,6.2.0)",
org.eclipse.jgit.notes;version="[6.1.0,6.2.0)",
org.eclipse.jgit.revplot;version="[6.1.0,6.2.0)",
diff --git a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
index e645255e96..8c44764c63 100644
--- a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
+++ b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
@@ -12,6 +12,7 @@ org.eclipse.jgit.pgm.ConvertRefStorage
org.eclipse.jgit.pgm.Daemon
org.eclipse.jgit.pgm.Describe
org.eclipse.jgit.pgm.Diff
+org.eclipse.jgit.pgm.DiffTool
org.eclipse.jgit.pgm.DiffTree
org.eclipse.jgit.pgm.Fetch
org.eclipse.jgit.pgm.Gc
diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
index 97450033ee..d51daafde3 100644
--- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
+++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
@@ -58,6 +58,9 @@ couldNotCreateBranch=Could not create branch {0}: {1}
dateInfo=Date: {0}
deletedBranch=Deleted branch {0}
deletedRemoteBranch=Deleted remote branch {0}
+diffToolHelpSetToFollowing='git difftool --tool=<tool>' may be set to one of the following:\n{0}\n\tuser-defined:\n{1}\nThe following tools are valid, but not currently available:\n{2}\nSome of the tools listed above only work in a windowed\nenvironment. If run in a terminal-only session, they will fail.
+diffToolLaunch=Viewing ({0}/{1}): '{2}'\nLaunch '{3}' [Y/n]?
+diffToolDied=external diff died, stopping at {0}
doesNotExist={0} does not exist
dontOverwriteLocalChanges=error: Your local changes to the following file would be overwritten by merge:
everythingUpToDate=Everything up-to-date
@@ -145,6 +148,7 @@ metaVar_s3StorageClass=STORAGE-CLASS
metaVar_seconds=SECONDS
metaVar_service=SERVICE
metaVar_tagLocalUser=<GPG key ID>
+metaVar_tool=TOOL
metaVar_treeish=tree-ish
metaVar_uriish=uri-ish
metaVar_url=URL
@@ -249,6 +253,8 @@ usage_DiffAlgorithms=Test performance of jgit's diff algorithms
usage_DisplayTheVersionOfJgit=Display the version of jgit
usage_Gc=Cleanup unnecessary files and optimize the local repository
usage_Glog=View commit history as a graph
+usage_DiffGuiTool=When git-difftool is invoked with the -g or --gui option the default diff tool will be read from the configured diff.guitool variable instead of diff.tool.
+usage_noGui=The --no-gui option can be used to override -g or --gui setting.
usage_IndexPack=Build pack index file for an existing packed archive
usage_LFSDirectory=Directory to store large objects
usage_LFSPort=Server http port
@@ -295,6 +301,7 @@ usage_ShowRef=List references in a local repository
usage_Status=Show the working tree status
usage_StopTrackingAFile=Stop tracking a file
usage_TextHashFunctions=Scan repository to compute maximum number of collisions for hash functions
+usage_ToolForDiff=Use the diff tool specified by <tool>. Run git difftool --tool-help for the list of valid <tool> settings.\nIf a diff tool is not specified, git difftool will use the configuration variable diff.tool.
usage_UpdateRemoteRepositoryFromLocalRefs=Update remote repository from local refs
usage_UseAll=Use all refs found in refs/
usage_UseTags=Use any tag including lightweight tags
@@ -341,6 +348,7 @@ usage_deleteFullyMergedBranch=delete fully merged branch
usage_date=date format, one of default, rfc, local, iso, short, raw (as defined by git-log(1) ), locale or localelocal (jgit extensions)
usage_detectRenames=detect renamed files
usage_diffAlgorithm=the diff algorithm to use. Currently supported are: 'myers', 'histogram'
+usage_DiffTool=git difftool is a Git command that allows you to compare and edit files between revisions using common diff tools.\ngit difftool is a frontend to git diff and accepts the same options and arguments.
usage_directoriesToExport=directories to export
usage_disableTheServiceInAllRepositories=disable the service in all repositories
usage_displayAListOfAllRegisteredJgitCommands=Display a list of all registered jgit commands
@@ -395,6 +403,8 @@ usage_pathToXml=path to the repo manifest XML file
usage_performFsckStyleChecksOnReceive=perform fsck style checks on receive
usage_portNumberToListenOn=port number to listen on
usage_printOnlyBranchesThatContainTheCommit=print only branches that contain the commit
+usage_prompt=Prompt before each invocation of the diff tool. This is the default behaviour; the option is provided to override any configuration settings.
+usage_noPrompt=Do not prompt before launching a diff tool.
usage_pruneStaleTrackingRefs=prune stale tracking refs
usage_pushUrls=push URLs are manipulated
usage_quiet=don't show progress messages
@@ -422,6 +432,8 @@ usage_srcPrefix=show the source prefix instead of "a/"
usage_sshDriver=Selects the built-in ssh library to use, JSch or Apache MINA sshd.
usage_symbolicVersionForTheProject=Symbolic version for the project
usage_tags=fetch all tags
+usage_trustExitCode=git-difftool invokes a diff tool individually on each file. Errors reported by the diff tool are ignored by default. Use --trust-exit-code to make git-difftool exit when an invoked diff tool returns a non-zero exit code.\ngit-difftool will forward the exit code of the invoked tool when --trust-exit-code is used.
+usage_noTrustExitCode=This option can be used to override --trust-exit-code setting.
usage_notags=do not fetch tags
usage_tagAnnotated=create an annotated tag, unsigned unless -s or -u are given, or config tag.gpgSign is true
usage_tagDelete=delete tag
@@ -430,6 +442,7 @@ usage_tagMessage=create an annotated tag with the given message, unsigned unless
usage_tagSign=create a signed annotated tag
usage_tagNoSign=suppress signing the tag
usage_tagVerify=Verify the GPG signature
+usage_toolHelp=Print a list of diff tools that may be used with --tool.
usage_untrackedFilesMode=show untracked files
usage_updateRef=reference to update
usage_updateRemoteRefsFromAnotherRepository=Update remote refs from another repository
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTool.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTool.java
new file mode 100644
index 0000000000..9fc26c9356
--- /dev/null
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTool.java
@@ -0,0 +1,255 @@
+/*
+ * 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.pgm;
+
+import static org.eclipse.jgit.lib.Constants.HEAD;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.text.MessageFormat;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jgit.diff.DiffEntry;
+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.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.TextProgressMonitor;
+import org.eclipse.jgit.lib.internal.BooleanTriState;
+import org.eclipse.jgit.pgm.internal.CLIText;
+import org.eclipse.jgit.pgm.opt.PathTreeFilterHandler;
+import org.eclipse.jgit.treewalk.AbstractTreeIterator;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
+import org.eclipse.jgit.util.StringUtils;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+
+@Command(name = "difftool", common = true, usage = "usage_DiffTool")
+class DiffTool extends TextBuiltin {
+ private DiffFormatter diffFmt;
+
+ private DiffTools diffTools;
+
+ @Argument(index = 0, metaVar = "metaVar_treeish")
+ private AbstractTreeIterator oldTree;
+
+ @Argument(index = 1, metaVar = "metaVar_treeish")
+ private AbstractTreeIterator newTree;
+
+ @Option(name = "--tool", aliases = {
+ "-t" }, metaVar = "metaVar_tool", usage = "usage_ToolForDiff")
+ private String toolName;
+
+ @Option(name = "--cached", aliases = { "--staged" }, usage = "usage_cached")
+ private boolean cached;
+
+ private BooleanTriState prompt = BooleanTriState.UNSET;
+
+ @Option(name = "--prompt", usage = "usage_prompt")
+ void setPrompt(@SuppressWarnings("unused") boolean on) {
+ prompt = BooleanTriState.TRUE;
+ }
+
+ @Option(name = "--no-prompt", aliases = { "-y" }, usage = "usage_noPrompt")
+ void noPrompt(@SuppressWarnings("unused") boolean on) {
+ prompt = BooleanTriState.FALSE;
+ }
+
+ @Option(name = "--tool-help", usage = "usage_toolHelp")
+ private boolean toolHelp;
+
+ private BooleanTriState gui = BooleanTriState.UNSET;
+
+ @Option(name = "--gui", aliases = { "-g" }, usage = "usage_DiffGuiTool")
+ void setGui(@SuppressWarnings("unused") boolean on) {
+ gui = BooleanTriState.TRUE;
+ }
+
+ @Option(name = "--no-gui", usage = "usage_noGui")
+ void noGui(@SuppressWarnings("unused") boolean on) {
+ gui = BooleanTriState.FALSE;
+ }
+
+ private BooleanTriState trustExitCode = BooleanTriState.UNSET;
+
+ @Option(name = "--trust-exit-code", usage = "usage_trustExitCode")
+ void setTrustExitCode(@SuppressWarnings("unused") boolean on) {
+ trustExitCode = BooleanTriState.TRUE;
+ }
+
+ @Option(name = "--no-trust-exit-code", usage = "usage_noTrustExitCode")
+ void noTrustExitCode(@SuppressWarnings("unused") boolean on) {
+ trustExitCode = BooleanTriState.FALSE;
+ }
+
+ @Option(name = "--", metaVar = "metaVar_paths", handler = PathTreeFilterHandler.class)
+ private TreeFilter pathFilter = TreeFilter.ALL;
+
+ @Override
+ protected void init(Repository repository, String gitDir) {
+ super.init(repository, gitDir);
+ diffFmt = new DiffFormatter(new BufferedOutputStream(outs));
+ diffTools = new DiffTools(repository);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ if (toolHelp) {
+ showToolHelp();
+ } else {
+ boolean showPrompt = diffTools.isInteractive();
+ if (prompt != BooleanTriState.UNSET) {
+ showPrompt = prompt == BooleanTriState.TRUE;
+ }
+ String toolNamePrompt = toolName;
+ if (showPrompt) {
+ if (StringUtils.isEmptyOrNull(toolNamePrompt)) {
+ toolNamePrompt = diffTools.getDefaultToolName(gui);
+ }
+ }
+ // get the changed files
+ List<DiffEntry> files = getFiles();
+ if (files.size() > 0) {
+ compare(files, showPrompt, toolNamePrompt);
+ }
+ }
+ outw.flush();
+ } catch (RevisionSyntaxException | IOException e) {
+ throw die(e.getMessage(), e);
+ } finally {
+ diffFmt.close();
+ }
+ }
+
+ private void compare(List<DiffEntry> files, boolean showPrompt,
+ String toolNamePrompt) throws IOException {
+ for (int fileIndex = 0; fileIndex < files.size(); fileIndex++) {
+ DiffEntry ent = files.get(fileIndex);
+ String mergedFilePath = ent.getNewPath();
+ if (mergedFilePath.equals(DiffEntry.DEV_NULL)) {
+ mergedFilePath = ent.getOldPath();
+ }
+ // check if user wants to launch compare
+ boolean launchCompare = true;
+ if (showPrompt) {
+ launchCompare = isLaunchCompare(fileIndex + 1, files.size(),
+ mergedFilePath, toolNamePrompt);
+ }
+ if (launchCompare) {
+ switch (ent.getChangeType()) {
+ case MODIFY:
+ outw.println("M\t" + ent.getNewPath() //$NON-NLS-1$
+ + " (" + ent.getNewId().name() + ")" //$NON-NLS-1$ //$NON-NLS-2$
+ + "\t" + ent.getOldPath() //$NON-NLS-1$
+ + " (" + ent.getOldId().name() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ int ret = diffTools.compare(ent.getNewPath(),
+ ent.getOldPath(), ent.getNewId().name(),
+ ent.getOldId().name(), toolName, prompt, gui,
+ trustExitCode);
+ if (ret != 0) {
+ throw die(MessageFormat.format(
+ CLIText.get().diffToolDied, mergedFilePath));
+ }
+ break;
+ default:
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ }
+
+ @SuppressWarnings("boxing")
+ private boolean isLaunchCompare(int fileIndex, int fileCount,
+ String fileName, String toolNamePrompt) throws IOException {
+ boolean launchCompare = true;
+ outw.println(MessageFormat.format(CLIText.get().diffToolLaunch,
+ fileIndex, fileCount, fileName, toolNamePrompt));
+ outw.flush();
+ BufferedReader br = new BufferedReader(new InputStreamReader(ins));
+ String line = null;
+ if ((line = br.readLine()) != null) {
+ if (!line.equalsIgnoreCase("Y")) { //$NON-NLS-1$
+ launchCompare = false;
+ }
+ }
+ return launchCompare;
+ }
+
+ private void showToolHelp() throws IOException {
+ String availableToolNames = new String();
+ for (String name : diffTools.getAvailableTools().keySet()) {
+ availableToolNames += String.format("\t\t{0}\n", name); //$NON-NLS-1$
+ }
+ String notAvailableToolNames = new String();
+ for (String name : diffTools.getNotAvailableTools().keySet()) {
+ notAvailableToolNames += String.format("\t\t{0}\n", name); //$NON-NLS-1$
+ }
+ String userToolNames = new String();
+ Map<String, ExternalDiffTool> userTools = diffTools
+ .getUserDefinedTools();
+ for (String name : userTools.keySet()) {
+ availableToolNames += String.format("\t\t{0}.cmd {1}\n", //$NON-NLS-1$
+ name, userTools.get(name).getCommand());
+ }
+ outw.println(MessageFormat.format(
+ CLIText.get().diffToolHelpSetToFollowing, availableToolNames,
+ userToolNames, notAvailableToolNames));
+ }
+
+ private List<DiffEntry> getFiles()
+ throws RevisionSyntaxException, AmbiguousObjectException,
+ IncorrectObjectTypeException, IOException {
+ diffFmt.setRepository(db);
+ if (cached) {
+ if (oldTree == null) {
+ ObjectId head = db.resolve(HEAD + "^{tree}"); //$NON-NLS-1$
+ if (head == null) {
+ die(MessageFormat.format(CLIText.get().notATree, HEAD));
+ }
+ CanonicalTreeParser p = new CanonicalTreeParser();
+ try (ObjectReader reader = db.newObjectReader()) {
+ p.reset(reader, head);
+ }
+ oldTree = p;
+ }
+ newTree = new DirCacheIterator(db.readDirCache());
+ } else if (oldTree == null) {
+ oldTree = new DirCacheIterator(db.readDirCache());
+ newTree = new FileTreeIterator(db);
+ } else if (newTree == null) {
+ newTree = new FileTreeIterator(db);
+ }
+
+ TextProgressMonitor pm = new TextProgressMonitor(errw);
+ pm.setDelayStart(2, TimeUnit.SECONDS);
+ diffFmt.setProgressMonitor(pm);
+ diffFmt.setPathFilter(pathFilter);
+
+ List<DiffEntry> files = diffFmt.scan(oldTree, newTree);
+ return files;
+ }
+
+}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
index 8e49a76a33..7fe5b0fa45 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
@@ -136,6 +136,9 @@ public class CLIText extends TranslationBundle {
/***/ public String dateInfo;
/***/ public String deletedBranch;
/***/ public String deletedRemoteBranch;
+ /***/ public String diffToolHelpSetToFollowing;
+ /***/ public String diffToolLaunch;
+ /***/ public String diffToolDied;
/***/ public String doesNotExist;
/***/ public String dontOverwriteLocalChanges;
/***/ public String everythingUpToDate;