为什么 git difftool 返回"cannot run git-difftool--helper"错误?



git difftool停止工作,我不知道为什么。

# git difftool --tool=vimdiff
error: cannot run git-difftool--helper: No such file or directory
fatal: external diff died, stopping at  ...

vimdiff安装在/bin/vimdiff上,工作正常。

# vimdiff --version
VIM - Vi IMproved 7.4 (2013 Aug 10, compiled Aug  9 2019 03:17:15)
  • --tool替换为vimdiff以外的内容时也会出现同样的问题
  • 这发生在该机器上的任何回购上,因此这不是回购的特定问题
  • 尝试通过yum reinstall git222-core.x86_64重新安装git。重新安装成功,但问题仍然存在
  • git difftool过去曾在同一台机器上工作,所以我猜测是机器的配置发生了变化,导致了这种情况
  • 其他git命令(status、diff、commit、push等(工作正常。这个问题似乎仅限于gitdifftool

git版本为2.22.3,运行在CentosLinux 7.7.1908版(核心(上

知道可能出了什么问题以及如何进一步调试吗?

IUS git222包是从Fedora git包派生而来的。它遵循相同的布局,在git222核心包中有一组最小的功能,其余功能(及其所有依赖项(在主git222包中。这在git222的生命周期中没有改变,所以最有可能的情况是有人认为他们只需要git222核心,因此卸载了git222。要恢复该功能,请再次安装git222。

yum install git222

感谢@phd的评论,我发现文件/usr/lib/git-core/git-difftool--helper不见了
由于重新安装git并不能解决此问题,因此git包本身可能丢失。

所以我从git repo下载了它(与我的git版本相同的标签(:

wget https://raw.githubusercontent.com/git/git/v2.22.4/git-difftool--helper.sh

移动(并重命名(到/usr/lib/git-core/git-difftool--helper,chmod a+x,现在它可以工作了。


更新1

在ius git222 上打开了一个问题


更新2

根据在ius上维护git222的@carlwgeorge的说法,git-difftool--helper是git222的一部分,而不是git222-core

这可以这样验证:

# repoquery -q --whatprovides /usr/libexec/git-core/git-difftool--helper
git-0:1.8.3.1-23.el7_8.x86_64
git-0:1.8.3.1-21.el7_7.x86_64
git-0:1.8.3.1-22.el7_8.x86_64
git222-0:2.22.2-1.el7.ius.x86_64
git224-0:2.24.3-1.el7.ius.x86_64
git222-0:2.22.3-1.el7.ius.x86_64
git224-0:2.24.2-1.el7.ius.x86_64
git222-0:2.22.4-1.el7.ius.x86_64

运行yum install git222后,git-difftool--helper被恢复:

# rpm -q --whatprovides /usr/libexec/git-core/git-difftool--helper
git222-2.22.4-1.el7.ius.x86_64

我修复了这个问题

转到Visual Studio创建并安装Git:的位置

C:Program Files (x86)Microsoft Visual Studio2019CommunityCommon7IDECommonExtensionsMicrosoftTeamFoundationTeam ExplorerGitmingw32libexecgit-core

该文件夹需要更新2个文件:

  • git-difftool--helper
  • git-mergetool--lib

所以我们将更新它们。

  1. 创建一个新文件并将其命名为git-difftool--helper,内容如下:
#!/bin/sh
# git-difftool--helper is a GIT_EXTERNAL_DIFF-compatible diff tool launcher.
# This script is typically launched by using the 'git difftool'
# convenience command.
#
# Copyright (c) 2009, 2010 David Aguilar
TOOL_MODE=diff
. git-mergetool--lib
# difftool.prompt controls the default prompt/no-prompt behavior
# and is overridden with $GIT_DIFFTOOL*_PROMPT.
should_prompt () {
prompt_merge=$(git config --bool mergetool.prompt || echo true)
prompt=$(git config --bool difftool.prompt || echo $prompt_merge)
if test "$prompt" = true
then
test -z "$GIT_DIFFTOOL_NO_PROMPT"
else
test -n "$GIT_DIFFTOOL_PROMPT"
fi
}
# Indicates that --extcmd=... was specified
use_ext_cmd () {
test -n "$GIT_DIFFTOOL_EXTCMD"
}
launch_merge_tool () {
# Merged is the filename as it appears in the work tree
# Local is the contents of a/filename
# Remote is the contents of b/filename
# Custom merge tool commands might use $BASE so we provide it
MERGED="$1"
LOCAL="$2"
REMOTE="$3"
BASE="$1"
# $LOCAL and $REMOTE are temporary files so prompt
# the user with the real $MERGED name before launching $merge_tool.
if should_prompt
then
printf "nViewing (%s/%s): '%s'n" "$GIT_DIFF_PATH_COUNTER" 
"$GIT_DIFF_PATH_TOTAL" "$MERGED"
if use_ext_cmd
then
printf "Launch '%s' [Y/n]? " 
"$GIT_DIFFTOOL_EXTCMD"
else
printf "Launch '%s' [Y/n]? " "$merge_tool"
fi
read ans || return
if test "$ans" = n
then
return
fi
fi
if use_ext_cmd
then
export BASE
eval $GIT_DIFFTOOL_EXTCMD '"$LOCAL"' '"$REMOTE"'
else
run_merge_tool "$merge_tool"
fi
}
if ! use_ext_cmd
then
if test -n "$GIT_DIFF_TOOL"
then
merge_tool="$GIT_DIFF_TOOL"
else
merge_tool="$(get_merge_tool)"
fi
fi
if test -n "$GIT_DIFFTOOL_DIRDIFF"
then
LOCAL="$1"
REMOTE="$2"
run_merge_tool "$merge_tool" false
else
# Launch the merge tool on each path provided by 'git diff'
while test $# -gt 6
do
launch_merge_tool "$1" "$2" "$5"
status=$?
if test $status -ge 126
then
# Command not found (127), not executable (126) or
# exited via a signal (>= 128).
exit $status
fi
if test "$status" != 0 &&
test "$GIT_DIFFTOOL_TRUST_EXIT_CODE" = true
then
exit $status
fi
shift 7
done
fi
exit 0
  1. 编辑现有文件,使其名称为git-mergetool--lib,以具有以下内容:
# git-mergetool--lib is a shell library for common merge tool functions
: ${MERGE_TOOLS_DIR=$(git --exec-path)/mergetools}
IFS='
'
mode_ok () {
if diff_mode
then
can_diff
elif merge_mode
then
can_merge
else
false
fi
}
is_available () {
merge_tool_path=$(translate_merge_tool_path "$1") &&
type "$merge_tool_path" >/dev/null 2>&1
}
list_config_tools () {
section=$1
line_prefix=${2:-}
git config --get-regexp $section'..*.cmd' |
while read -r key value
do
toolname=${key#$section.}
toolname=${toolname%.cmd}
printf "%s%sn" "$line_prefix" "$toolname"
done
}
show_tool_names () {
condition=${1:-true} per_line_prefix=${2:-} preamble=${3:-}
not_found_msg=${4:-}
extra_content=${5:-}
shown_any=
( cd "$MERGE_TOOLS_DIR" && ls ) | {
while read scriptname
do
setup_tool "$scriptname" 2>/dev/null
variants="$variants$(list_tool_variants)n"
done
variants="$(echo "$variants" | sort | uniq)"
for toolname in $variants
do
if setup_tool "$toolname" 2>/dev/null &&
(eval "$condition" "$toolname")
then
if test -n "$preamble"
then
printf "%sn" "$preamble"
preamble=
fi
shown_any=yes
printf "%s%sn" "$per_line_prefix" "$toolname"
fi
done
if test -n "$extra_content"
then
if test -n "$preamble"
then
# Note: no 'n' here since we don't want a
# blank line if there is no initial content.
printf "%s" "$preamble"
preamble=
fi
shown_any=yes
printf "n%sn" "$extra_content"
fi
if test -n "$preamble" && test -n "$not_found_msg"
then
printf "%sn" "$not_found_msg"
fi
test -n "$shown_any"
}
}
diff_mode () {
test "$TOOL_MODE" = diff
}
merge_mode () {
test "$TOOL_MODE" = merge
}
gui_mode () {
test "$GIT_MERGETOOL_GUI" = true
}
translate_merge_tool_path () {
echo "$1"
}
check_unchanged () {
if test "$MERGED" -nt "$BACKUP"
then
return 0
else
while true
do
echo "$MERGED seems unchanged."
printf "Was the merge successful [y/n]? "
read answer || return 1
case "$answer" in
y*|Y*) return 0 ;;
n*|N*) return 1 ;;
esac
done
fi
}
valid_tool () {
setup_tool "$1" && return 0
cmd=$(get_merge_tool_cmd "$1")
test -n "$cmd"
}
setup_user_tool () {
merge_tool_cmd=$(get_merge_tool_cmd "$tool")
test -n "$merge_tool_cmd" || return 1
diff_cmd () {
( eval $merge_tool_cmd )
}
merge_cmd () {
( eval $merge_tool_cmd )
}
list_tool_variants () {
echo "$tool"
}
}
setup_tool () {
tool="$1"
# Fallback definitions, to be overridden by tools.
can_merge () {
return 0
}
can_diff () {
return 0
}
diff_cmd () {
return 1
}
merge_cmd () {
return 1
}
translate_merge_tool_path () {
echo "$1"
}
list_tool_variants () {
echo "$tool"
}
# Most tools' exit codes cannot be trusted, so By default we ignore
# their exit code and check the merged file's modification time in
# check_unchanged() to determine whether or not the merge was
# successful.  The return value from run_merge_cmd, by default, is
# determined by check_unchanged().
#
# When a tool's exit code can be trusted then the return value from
# run_merge_cmd is simply the tool's exit code, and check_unchanged()
# is not called.
#
# The return value of exit_code_trustable() tells us whether or not we
# can trust the tool's exit code.
#
# User-defined and built-in tools default to false.
# Built-in tools advertise that their exit code is trustable by
# redefining exit_code_trustable() to true.
exit_code_trustable () {
false
}
if test -f "$MERGE_TOOLS_DIR/$tool"
then
. "$MERGE_TOOLS_DIR/$tool"
elif test -f "$MERGE_TOOLS_DIR/${tool%[0-9]}"
then
. "$MERGE_TOOLS_DIR/${tool%[0-9]}"
else
setup_user_tool
return $?
fi
# Now let the user override the default command for the tool.  If
# they have not done so then this will return 1 which we ignore.
setup_user_tool
if ! list_tool_variants | grep -q "^$tool$"
then
return 1
fi
if merge_mode && ! can_merge
then
echo "error: '$tool' can not be used to resolve merges" >&2
return 1
elif diff_mode && ! can_diff
then
echo "error: '$tool' can only be used to resolve merges" >&2
return 1
fi
return 0
}
get_merge_tool_cmd () {
merge_tool="$1"
if diff_mode
then
git config "difftool.$merge_tool.cmd" ||
git config "mergetool.$merge_tool.cmd"
else
git config "mergetool.$merge_tool.cmd"
fi
}
trust_exit_code () {
if git config --bool "mergetool.$1.trustExitCode"
then
:; # OK
elif exit_code_trustable
then
echo true
else
echo false
fi
}

# Entry point for running tools
run_merge_tool () {
# If GIT_PREFIX is empty then we cannot use it in tools
# that expect to be able to chdir() to its value.
GIT_PREFIX=${GIT_PREFIX:-.}
export GIT_PREFIX
merge_tool_path=$(get_merge_tool_path "$1") || exit
base_present="$2"
# Bring tool-specific functions into scope
setup_tool "$1" || return 1
if merge_mode
then
run_merge_cmd "$1"
else
run_diff_cmd "$1"
fi
}
# Run a either a configured or built-in diff tool
run_diff_cmd () {
diff_cmd "$1"
}
# Run a either a configured or built-in merge tool
run_merge_cmd () {
mergetool_trust_exit_code=$(trust_exit_code "$1")
if test "$mergetool_trust_exit_code" = "true"
then
merge_cmd "$1"
else
touch "$BACKUP"
merge_cmd "$1"
check_unchanged
fi
}
list_merge_tool_candidates () {
if merge_mode
then
tools="tortoisemerge"
else
tools="kompare"
fi
if test -n "$DISPLAY"
then
if test -n "$GNOME_DESKTOP_SESSION_ID"
then
tools="meld opendiff kdiff3 tkdiff xxdiff $tools"
else
tools="opendiff kdiff3 tkdiff xxdiff meld $tools"
fi
tools="$tools gvimdiff diffuse diffmerge ecmerge"
tools="$tools p4merge araxis bc codecompare"
tools="$tools smerge"
fi
case "${VISUAL:-$EDITOR}" in
*nvim*)
tools="$tools nvimdiff vimdiff emerge"
;;
*vim*)
tools="$tools vimdiff nvimdiff emerge"
;;
*)
tools="$tools emerge vimdiff nvimdiff"
;;
esac
}
show_tool_help () {
tool_opt="'git ${TOOL_MODE}tool --tool=<tool>'"
tab='   '
LF='
'
any_shown=no
cmd_name=${TOOL_MODE}tool
config_tools=$({
diff_mode && list_config_tools difftool "$tab$tab"
list_config_tools mergetool "$tab$tab"
} | sort)
extra_content=
if test -n "$config_tools"
then
extra_content="${tab}user-defined:${LF}$config_tools"
fi
show_tool_names 'mode_ok && is_available' "$tab$tab" 
"$tool_opt may be set to one of the following:" 
"No suitable tool for 'git $cmd_name --tool=<tool>' found." 
"$extra_content" &&
any_shown=yes
show_tool_names 'mode_ok && ! is_available' "$tab$tab" 
"${LF}The following tools are valid, but not currently available:" &&
any_shown=yes
if test "$any_shown" = yes
then
echo
echo "Some of the tools listed above only work in a windowed"
echo "environment. If run in a terminal-only session, they will fail."
fi
exit 0
}
guess_merge_tool () {
list_merge_tool_candidates
cat >&2 <<-EOF
This message is displayed because '$TOOL_MODE.tool' is not configured.
See 'git ${TOOL_MODE}tool --tool-help' or 'git help config' for more details.
'git ${TOOL_MODE}tool' will now attempt to use one of the following tools:
$tools
EOF
# Loop over each candidate and stop when a valid merge tool is found.
IFS=' '
for tool in $tools
do
is_available "$tool" && echo "$tool" && return 0
done
echo >&2 "No known ${TOOL_MODE} tool is available."
return 1
}
get_configured_merge_tool () {
keys=
if diff_mode
then
if gui_mode
then
keys="diff.guitool merge.guitool diff.tool merge.tool"
else
keys="diff.tool merge.tool"
fi
else
if gui_mode
then
keys="merge.guitool merge.tool"
else
keys="merge.tool"
fi
fi
merge_tool=$(
IFS=' '
for key in $keys
do
selected=$(git config $key)
if test -n "$selected"
then
echo "$selected"
return
fi
done)
if test -n "$merge_tool" && ! valid_tool "$merge_tool"
then
echo >&2 "git config option $TOOL_MODE.${gui_prefix}tool set to unknown tool: $merge_tool"
echo >&2 "Resetting to default..."
return 1
fi
echo "$merge_tool"
}
get_merge_tool_path () {
# A merge tool has been set, so verify that it's valid.
merge_tool="$1"
if ! valid_tool "$merge_tool"
then
echo >&2 "Unknown merge tool $merge_tool"
exit 1
fi
if diff_mode
then
merge_tool_path=$(git config difftool."$merge_tool".path ||
git config mergetool."$merge_tool".path)
else
merge_tool_path=$(git config mergetool."$merge_tool".path)
fi
if test -z "$merge_tool_path"
then
merge_tool_path=$(translate_merge_tool_path "$merge_tool")
fi
if test -z "$(get_merge_tool_cmd "$merge_tool")" &&
! type "$merge_tool_path" >/dev/null 2>&1
then
echo >&2 "The $TOOL_MODE tool $merge_tool is not available as"
"'$merge_tool_path'"
exit 1
fi
echo "$merge_tool_path"
}
get_merge_tool () {
is_guessed=false
# Check if a merge tool has been configured
merge_tool=$(get_configured_merge_tool)
# Try to guess an appropriate merge tool if no tool has been set.
if test -z "$merge_tool"
then
merge_tool=$(guess_merge_tool) || exit
is_guessed=true
fi
echo "$merge_tool"
test "$is_guessed" = false
}
mergetool_find_win32_cmd () {
executable=$1
sub_directory=$2
# Use $executable if it exists in $PATH
if type -p "$executable" >/dev/null 2>&1
then
printf '%s' "$executable"
return
fi
# Look for executable in the typical locations
for directory in $(env | grep -Ei '^PROGRAM(FILES((X86))?|W6432)=' |
cut -d '=' -f 2- | sort -u)
do
if test -n "$directory" && test -x "$directory/$sub_directory/$executable"
then
printf '%s' "$directory/$sub_directory/$executable"
return
fi
done
printf '%s' "$executable"
}

最新更新