【问题标题】:Why does git difftool return "cannot run git-difftool--helper" error?为什么 git difftool 返回“无法运行 git-difftool--helper”错误?
【发布时间】:2020-07-29 07:33:27
【问题描述】:

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 以外的其他内容时会出现同样的问题。
  • 它发生在该机器上的任何 repo 上,因此它不是特定于 repo 的问题。
  • yum reinstall git222-core.x86_64 尝试重新安装 git。重新安装成功,但问题仍然存在。
  • git difftool 过去曾在同一台机器上工作,所以我猜是机器的配置发生了变化并导致了这种情况。
  • 其他 git 命令(状态、差异、提交、推送等)正常工作。问题似乎仅限于 git difftool。

git版本为2.22.3,运行于CentOS Linux release 7.7.1908 (Core)

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

【问题讨论】:

  • locate -b \\git-difftool--helper ?
  • @phd - 确实不见了!我希望它在/usr/libexec/git-core/ 上,但它不存在(其他文件是)。卸载并重新安装 git 并没有恢复。
  • 对我来说它在/usr/lib/git-core/git-difftool--helper 怎么样得到它:stackoverflow.com/a/58287568/7976758 发现在stackoverflow.com/…

标签: git git-difftool


【解决方案1】:

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

yum install git222

【讨论】:

    【解决方案2】:

    感谢@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
    

    【讨论】:

    • 有趣。可能值得向为 CentOS 维护此软件包存档的人报告。
    • @torek 那将是ius repo 而不是 CentOS repo,因为我需要更新的 git 版本。 CentOS 分发 git 1.8.5。
    • @AmirGonnen 感谢您为此打开 GitHub 问题,我不经常查看此网站。我已经在此处的答案和 GitHub 问题上做出了回应。
    【解决方案3】:

    我解决了这个问题

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

    C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Git\mingw32\libexec\git-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%s\n" "$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 "%s\n" "$preamble"
                        preamble=
                    fi
                    shown_any=yes
                    printf "%s%s\n" "$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%s\n" "$extra_content"
            fi
    
            if test -n "$preamble" && test -n "$not_found_msg"
            then
                printf "%s\n" "$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"
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多