【问题标题】:Git bash-completion with filename support?带有文件名支持的 Git bash 完成?
【发布时间】:2011-05-17 17:06:28
【问题描述】:

是否有支持文件名完成的 bash 完成脚本?我主要使用 mercurial,我可以在那里输入:

hg diff test/test_<tab>

它会显示/完成所有修改过的测试文件。它适用于大多数子命令,即hg add &lt;tab&gt;&lt;tab&gt; 只会列出未跟踪的文件。真的很方便。

来自 git contrib 的 bash 脚本接缝不支持这一点。是否有任何替代方案,或者您如何在命令行上使用 git?

2015 年编辑

git-completion.bash~1.8.2 以来支持完整的文件名完成

【问题讨论】:

  • 我通常先使用git status,然后才知道为git diff 输入什么。 (但我经常使用gitk 来查看差异。)
  • 注意:使用 Git 2.18(2018 年第二季度),文件名完成会更快。见my answer below

标签: git bash bash-completion


【解决方案1】:

那么,让我们看看 Mercurial bash 完成脚本是如何做到这一点的。

这是important part

_hg_status()
{
    local files="$(_hg_cmd status -n$1 .)"
    local IFS=$'\n'
    COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$files' -- "$cur"))
}

它被称为here:

_hg_command_specific()
{
    case "$cmd" in 
    [...]
    diff)
        _hg_status "mar"
    ;;
    [...]
    esac
    return 0
}

因此,它只是对hg status -nmar 的调用,并将输出用作完成的文件列表。

我认为将类似的东西修补到git completion script 中不会太难 - 我们必须在此处修改__git_diff 以不执行普通文件名+分支完成,而是调用git status


命令

git status --porcelain | grep '^.[^ ?]' | cut -b 4-

(对于git diff --cached)和

git status --porcelain | grep '^[^ ?]' | cut -b 4-

(对于git diff)似乎输出了正确的东西(如果没有重命名)。

不过,当 diff 到 HEAD 以外的任何东西时,它们都没有用。

更通用的方法是使用

git diff --relative --name-only [--cached] [commit1] [commit2]]

其中commit1commit2(可能还有--cached)来自已经给出的diff 命令行。


我在 bash 中实现了上面概述的想法,并修补到 git-completion.bash。如果您不想更改您的git-completion.bash,请将这两个函数添加到某个 bash 文件中,并在原始git-completion.bash 之后获取它。它现在应该可以使用像

这样的命令了
git diff -- <tab>
git diff --cached -- <tab>
git diff HEAD^^ -- <tab>
git diff origin/master master -- <tab>

submitted this 作为 git 邮件列表的补丁,让我们看看由此产生的结果。 (我会在收到反馈时更新这个答案。)

# Completion for the file argument for git diff.
# It completes only files actually changed. This might be useful
# as completion for other commands as well.
#
# The idea comes from the bash completion for Mercurial (hg),
# which does something similar (but more simple, only difference of
# working directory to HEAD and/or index, if I understand right).
# It (the idea) was brought to us by the question
#      http://stackoverflow.com/q/6034472/600500
#  from "olt".
__git_complete_changed_files()
{
  #
  # We use "git diff --name-only --relative" to generate the list,
  # but this needs the same --cached and <commit> arguments as the
  # command line being constructed.
  #


    # first grab arguments like --cached and any commit arguments.

    local -a args=()
    local finish=false

    for (( i=1 ; i < cword ; i++)) do
    local current_arg=${words[$i]}
    #  echo checking $current_arg >&2
       case $current_arg in
           --cached)
               args+=( $current_arg )
               ;;
           --)
               # finish parsing arguments, the rest are file names
               break
               ;;
           -*)
               # other options are ignored
               ;;
           *)
               if git cat-file -e $current_arg 2> /dev/null
               then
                   case $( git cat-file -t $current_arg ) in
                       commit|tag)
                       # commits and tags are added to the command line.
                           args+=( $current_arg )
                           # echo adding $current_arg >&2
                           ;;
                       *)
                   esac
               fi
               ;;
       esac
    done

    # now we can call `git diff`

    COMPREPLY=( $( compgen \
        -W "$( git diff --name-only --relative "${args[@]}" -- )" -- $cur ) )
}

_git_diff ()
{
    if __git_has_doubledash
    then
        # complete for the file part: only changed files
        __git_complete_changed_files
    else
    case "$cur" in
    --*)
        __gitcomp "--cached --staged --pickaxe-all --pickaxe-regex
            --base --ours --theirs --no-index
            $__git_diff_common_options
            "
        return
        ;;
    esac
    __git_complete_revlist_file
    fi
}

更新: 看起来这种形式不需要此补丁,因为当前完成文件的方法对于想要检查某些子目录中是否有更改的人更有用(例如,完成时diff 输出可能为空)。如果链接到某个配置变量(默认为当前行为),它可能会被接受。此外,缩进应该适应标准(参见Junio C Hamano的答案)。

我可能会再做一次,但不能保证在不久的将来会这样做。如果其他人想做,请随时拿走我的代码,更改并再次提交。

【讨论】:

    【解决方案2】:

    这为我解决了git diff &lt;tab&gt; 的问题,将以下内容放入.bashrc

    alias gid='git diff'
    __gdiff () {
        local cur prev opts
        COMPREPLY=()
        cur="${COMP_WORDS[COMP_CWORD]}"
        prev="${COMP_WORDS[COMP_CWORD-1]}"
        opts=$(git status --porcelain | grep '^.[^ ?]' | cut -b 4-)
    
        case "${prev}" in
            gid)
                COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
                ;;
        esac
    }
    complete -F __gdiff gid
    

    然后使用gid &lt;tab&gt; 而不是git diff &lt;tab&gt;。它可能会被简化,但似乎作为一种快速修复效果很好。

    【讨论】:

      【解决方案3】:

      并不是你想要的答案,但我想让你知道,fish(友好的交互式 shell)很快就会为你提供开箱即用的 git 文件名完成支持。它目前处于 master 状态,即将发布 2.3.0 版本。

      https://github.com/fish-shell/fish-shell/issues/901
      https://github.com/fish-shell/fish-shell/pull/2364
      https://github.com/fish-shell/fish-shell/commit/c5c59d4acb00674bc37198468b5978f69484c628

      如果你有这样的状态:

      $ git status
      modified: ../README.md
      $ git add <tab>
      :/README.md 
      

      您也可以只输入README 并点击tab,如果它是唯一的匹配项,它将为您插入。该死的好!

      【讨论】:

      • 在帖子中查看我 2015 年的更新:“git-completion.bash 支持自 ~1.8.2 以来的完整文件名完成”
      【解决方案4】:

      自 2011 年以来,作为 OP cmets,Git 从 ~1.8.2 开始支持完整的文件名完成。

      但在 Git 2.18(2018 年第二季度)中,提供路径列表的 shell 补全(在 contrib/ 中)已经有所优化。

      commit 78a2d21(2018 年 4 月 4 日)Clemens Buchacher (drizzd)
      (由 Junio C Hamano -- gitster -- 合并到 commit 3a940e9,2018 年 4 月 25 日)

      completion:提高ls-files过滤器性能

      ls-files 的输出中,我们删除了除了最左边的路径之外的所有路径 组件,然后我们消除重复。我们在while 循环中执行此操作, 当迭代次数很大时,这是一个性能瓶颈 (例如linux.git 中的 60000 个文件)。

      $ COMP_WORDS=(git status -- ar) COMP_CWORD=3; time _git
      
      real    0m11.876s
      user    0m4.685s
      sys     0m6.808s
      

      用 cut 命令替换循环可以提高性能 显着:

      $ COMP_WORDS=(git status -- ar) COMP_CWORD=3; time _git
      
      real    0m1.372s
      user    0m0.263s
      sys     0m0.167s
      

      测量是使用 Msys2 bash 完成的,Git 使用它 窗户。

      过滤ls-files 输出时,我们注意不要触摸绝对值 路径。这是多余的,因为ls-files 永远不会输出绝对值 路径。去掉不必要的操作。

      该问题最初是在Git for Windows issue 1533 报告的。


      目录遍历代码具有冗余递归调用,这使其性能特征相对于树的深度呈指数级增长,这已在 Git 2.27(2020 年第二季度)中得到纠正。

      commit c0af173commit 95c11eccommit 7f45ab2commit 1684644commit 8d92fb2commit 2df179dcommit 0126d14commit cd129eecommit cd129eecommit 446f46dcommit 7260c7b,@097654335@,@097654335@ 2020)Elijah Newren (newren).
      请参阅 Derrick Stolee (derrickstolee)commit 0bbd0e8(2020 年 4 月 1 日)。
      (由 Junio C Hamano -- gitster -- 合并于 commit 6eacc39,2020 年 4 月 29 日)

      completion:在未跟踪目录下的路径上修复“git add

      签字人:Elijah Newren

      根据 git 邮件列表中的报告,从 git-2.25 开始,

      git add untracked-dir/
      

      标签已完成到

      git add untracked-dir/./
      

      原因是commit b9670c1f5e ("dir: fix checks on common prefix directory", 2019-12-19, Git v2.25.0-rc0 -- merge),

      git ls-files -o --directory untracked-dir/
      

      (或等效的git -C untracked-dir ls-files -o --directory)开始报告

      untracked-dir/
      

      而不是列出该目录下的路径。

      值得注意的是,真正的命令是

      git -C untracked-dir ls-files -o --directory '*'
      

      相当于:

      git ls-files -o --directory 'untracked-dir/*'
      

      在此问题中表现相同(“*”可以匹配空字符串),但与建议的修复相关。

      起初,根据报告,我决定尝试将此视为一种回归,并尝试找到一种方法来恢复旧行为而不破坏其他东西,或者至少尽可能少地破坏。
      但是,最后,我想不出一种方法来做到这一点,它不仅会导致比解决的问题更多的问题。

      旧的行为是一个错误:

      • 虽然较旧的 git 会避免使用 git clean -f .git 清理任何内容,但它会使用 git clean -f .git/ 清除该目录下的所有内容。
        尽管使用的命令有所不同,但这是相关的,因为与修复 clean 完全相同的更改改变了 ls-files 的行为。
      • 较旧的 git 将仅根据命令 git ls-files -o --directory $SUBDIR$SUBDIR 的尾部斜杠是否存在来报告不同的结果。
      • 当指定--directory 时,旧 git 违反了不递归到与路径规范匹配的目录的记录行为。
      • 毕竟,commit b9670c1f5edir:修复公共前缀目录检查,2019-12-19,Git v2.25.0-rc0)没有忽略这个问题;它明确表示正在更改命令的行为以使其与文档内联。

      (另外,如果有帮助的话,尽管在 2.25 系列期间合并了该提交,但在 2.25 周期中甚至在 2.26 周期的大部分时间里都没有报告这个错误——它是在 2.26 发布的前一天报告的。

      所以变化的影响至少有点小。)

      不要依赖ls-files 的错误来报告错误的内容,而是更改 git-completion 使用的 ls-files 的调用,使其更深地抓取路径。

      通过将“$DIR/*”(匹配 $DIR/ 加上 0 个或多个字符)更改为“$DIR/?*”(匹配 $DIR/ 加上 1 个或多个字符)来执行此操作。

      请注意,在尝试完成文件名时不应添加“?”字符(例如,“git ls-files -o --directory merge.c?*"' would not correctly return "[merge.c](https://github.com/git/git/blob/c0af173a136785b3cfad4bd414b2fb10a130760a/merge.c)" when such a file exists), so we have to make sure to add the '?`”字符仅适用于目前指定的路径是目录的情况.


      警告:Git 2.29(2020 年第四季度)修复了在 2.27 周期中引入的回归。

      参见Martin Ågren (none)commit cada730(2020 年 7 月 20 日)。
      (由 Junio C Hamano -- gitster -- 合并于 commit 82fafc7,2020 年 7 月 30 日)

      dir:在返回 path_excluded 之前检查路径规范

      报告人:Andreas Schwab
      审核人:Elijah Newren
      签字人:Martin Ågren

      95c11ecc73(“修复容易出错的fill_directory() API;使其只返回匹配项”,2020-04-01,Git v2.27.0-rc0 -- merge 列在batch #5)中,我们教导 fill_directory(),或更具体地讲 treat_path(),检查任何路径规范,以便我们可以简化调用者。

      但在这样做的过程中,我们为“排除”的情况增加了一个稍微过早的回报。我们最终没有检查路径规范,这意味着我们返回path_excluded,而也许我们应该返回path_none。因此,git status --ignored -- pathspec(man) 可能会显示与“pathspec”实际不匹配的路径。

      在我们检查完所有路径规范之后,将“排除”检查移至下方。

      【讨论】:

        猜你喜欢
        • 2012-06-15
        • 1970-01-01
        • 1970-01-01
        • 2012-04-09
        • 1970-01-01
        • 1970-01-01
        • 2020-10-19
        • 2021-12-27
        • 2013-12-05
        相关资源
        最近更新 更多