支持文件名的Git bash补全?

egmofgnx  于 2023-02-07  发布在  Git
关注(0)|答案(4)|浏览(113)

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

hg diff test/test_<tab>

并且它会显示/完成所有修改过的测试文件。它适用于大多数子命令,iIndiee.hg add <tab><tab>只会列出未跟踪的文件。它真的很方便。
git contrib中的bash脚本似乎不支持这个功能,有没有其他的替代方法,或者如何在命令行上使用git?

编辑2015年

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

wa7juj8i

wa7juj8i1#

那么,让我们看看Mercurial bash完成脚本是如何完成的。
这是最重要的部分:

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

它在这里被称为:

_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-

(for git diff --cached)和

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

(for git 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>

我把这个作为补丁提交到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的答案)。

我可能会采取另一个去,但不能保证这在不久的将来。如果别人想这样做,随时采取我的代码,改变它,并再次提交。

ztigrdn8

ztigrdn82#

这样就解决了git diff <tab>问题,将以下内容放入**.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 <tab>而不是git diff <tab>。它可能会简化,但作为快速修复似乎效果很好。

gc0ot86w

gc0ot86w3#

这不是你想要的答案,但我想让你知道,很快fish(友好的交互式环境)就会给予你提供开箱即用的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,如果它是唯一匹配的,它会为你插入它。

e0bqpujr

e0bqpujr4#

自2011年起,如OP注解所示,Git从~1.8.2起支持完整的文件名补全。
但是在Git 2.18(Q2 2018)中,给出路径列表的shell补全(在contrib/中)得到了一些优化。
参见Clemens Buchacher ( drizzd )(2018年4月4日)。
(由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

使用剪切命令替换循环可显著提高性能:

$ COMP_WORDS=(git status -- ar) COMP_CWORD=3; time _git

real    0m1.372s
user    0m0.263s
sys     0m0.167s

测量是使用Windows版Git使用的Msys2 bash完成的。
当过滤ls-files输出时,我们注意不要触及绝对路径。这是多余的,因为ls-files永远不会输出绝对路径。删除不必要的操作。
该问题最初报告为Git for Windows issue 1533
目录遍历代码有冗余的递归调用,这使得它的性能特征与树的深度成指数关系,这在Git 2.27(Q2 2020)中得到了纠正。
请参见第一版第五版、第一版第六版、第一版第七版、第一版第八版、第一版第九版、第一版第十版、第一版第十一版、第一版第十二版、第一版第十三版、第一版第十四版、第一版第十五版(2020年4月1日)。
参见Derrick Stolee ( derrickstolee )commit 0bbd0e8(2020年4月1日)。
(由Junio C Hamano -- gitster --合并至commit 6eacc39,2020年4月29日)

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

签署人:伊莱贾·纽伦
正如git邮件列表中所报告的,自git-2. 25以来,

git add untracked-dir/

已按Tab键完成到

git add untracked-dir/./

其原因是commit b9670c1f5e("dir:修复对公共前缀目录的检查",2019年12月19日,Git v2.25.0-rc0--merge),

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

(or等价物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文件的行为。

  • 旧版的git只会根据命令git ls-files -o --directory $SUBDIR中是否有尾部斜杠来报告不同的结果。
  • 旧的git违反了文档中记录的行为,即指定--directory时不递归到与路径规范匹配的目录。
  • 毕竟,commit b9670c1f5edir:修复了对通用前缀目录的检查,2019年12月19日,Git v2.25.0-rc0)未忽略此问题;它明确指出,该命令的行为正在改变,以使其与文档保持一致。

(Also,如果有帮助的话,尽管那个提交在2.25系列中被合并,这个bug在2.25周期中没有被报告,甚至在2.26周期的大部分时间里也没有--它在2.26发布的前一天被报告。
因此,这一变化的影响至少在某种程度上是小的。)
与其依赖ls-files的bug来报告错误的内容,不如改变git-completion使用的ls文件的调用,使其获取更深一层的路径。
为此,请将"$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(Q4 2020)修复了在2.27周期中引入的回归。
参见Martin Ågren ( none )commit cada730(2020年7月20日)。
(由Junio C Hamano -- gitster --合并至commit 82fafc7,2020年7月30日)

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

报告人:安德烈亚斯·施瓦布
审核人:伊莱贾·纽伦
签署人:马丁·奥格伦
95c11ecc73("修复容易出错的fill_directory() API;make it only return matches ",2020 - 04 - 01,Git v2.27.0-rc0--merge listed in batch #5),我们教fill_directory(),或者更具体地说treat_path(),检查任何路径规范,以便简化调用程序。
但是在这样做的时候,我们为"excluded"的情况添加了一个稍早的返回,我们最终没有检查路径规范,这意味着我们在应该返回path_none的时候返回了path_excluded,结果git status --ignored -- pathspec(man)可能显示的路径实际上并不匹配"pathspec"。
将"excluded"复选框下移到检查完所有路径规范之后。

Git 2.38(Q3 2022)修复了在2.27周期中引入的 * 另一个 * 回归,这可能会影响git-bash的完成。
在一个非裸仓库中,当core.worktree配置变量指向一个子目录为仓库的目录时,Git的行为在Git 2.27中出现了退化。
参见commit d6c9a71commit 2712899(2022年6月16日),作者为Goss Geppert ( ggossdev )
(由Junio C Hamano -- gitster --合并至commit dc6315e,2022年7月14日)

dir:遍历到存储库

签署人:戈斯·格佩特
审核人:伊莱贾·纽伦
由于8d92fb2("dir:replace exponential algorithm with a linear one ",2020 - 04 - 01,Git v2.27.0-rc0--merge listed in batch #5)当遍历开始于仓库的标准位置之外时,遍历仓库的目录树失败,因为遇到的仓库被标识为嵌套的外部仓库。
在此提交之前,在以下任一条件下(可能还有其他条件),从用户的Angular 可以观察到未能遍历到存储库的默认工作树位置:
1.将core.worktree位置设置为默认工作树的父目录;或
1.当工作目录不在存储库的默认工作树位置时,请使用--git_dir选项
在这两种情况下,遍历存储库的默认工作树位置失败的症状包括无法将文件添加到索引或通过ls-files获取未跟踪文件的列表。
这个提交添加了一个检查,以确定在递归路径时遇到的嵌套存储库是否实际上是the_repository
如果是这样,我们就简单地将目录视为不包含嵌套存储库。

相关问题