10

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

hg diff test/test_<tab>

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

来自 git contrib 的 bash 脚本接缝不支持这一点。有没有其他选择,或者你如何在命令行上使用 git?

编辑 2015

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

4

4 回答 4

13

那么,让我们看看 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 完成脚本中并不会太难——我们必须在__git_diff这里修改,不要做一个普通的文件名 + 分支完成,而是调用git status


命令

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

(对于git diff --cached)和

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

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

但是,它们在区分 HEAD 以外的任何东西时都没有用。

更通用的方法是使用

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

wherecommit1commit2(也许--cached) 来自已经给出的 diff 命令行。


我在 bash 中实现了上面概述的想法,并修补到git-completion.bash. 如果您不想更改您git-completion.bashgit-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
}

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

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

于 2011-05-17T18:56:30.987 回答
3

这解决了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>. 它可能会被简化,但似乎可以很好地作为快速修复。

于 2013-11-18T16:01:40.973 回答
0

自 2011 年以来,正如 OP 评论的那样,Git 从 ~1.8.2 开始支持完整的文件名完成。

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

请参阅Clemens Buchacher ( ) 的提交 78a2d21(2018 年 4 月 4 日(由Junio C Hamano 合并——提交 3a940e9中,2018 年 4 月 25 日)drizzd
gitster

completion: 提高ls-files过滤性能

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

$ 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

测量是使用 Git for Windows 使用的 Msys2 bash 完成的。

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

该问题最初报告为 Git for Windows 问题 1533


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

请参阅提交 c0af173提交 95c11ec提交 7f45ab2提交 1684644提交 8d92fb2提交 2df179d提交 0126d14提交 cd129ee提交 446f46d提交 7260c7b提交 ce5c61a(2020 年 4 月 1 日),作者Elijahnewren
请参阅Derrick Stolee ( ) 的提交 0bbd0e8(2020 年 4 月 1 日(由Junio C Hamano 合并 -- --提交 6eacc39中,2020 年 4 月 29 日)derrickstolee
gitster

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

签字人:以利亚·纽伦

正如 git 邮件列表中所报告的,从 git-2.25 开始,

git add untracked-dir/

已完成制表符

git add untracked-dir/./

造成这种情况的原因是提交 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 的行为。
  • $SUBDIR较旧的 git 将仅根据command 中是否存在尾部斜杠来报告不同的结果git ls-files -o --directory $SUBDIR
  • 旧 git 违反了记录在案的行为,即在指定时不递归到与 pathspec 匹配的目录中--directory
  • 毕竟,commit b9670c1f5e ( dir: fix checks on common prefix directory, 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 ( ) 的commit cada730(2020 年 7 月 20 日(由Junio C Hamano 合并 -- --82fafc7 提交中,2020 年 7 月 30 日)none
gitster

dir: 在返回之前检查 pathspecspath_excluded

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

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

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

在我们检查了任何路径规范之后,将“排除”检查向下移动。

于 2018-05-01T21:45:01.687 回答
0

不是您真正想要的答案,但我想让您知道,很快 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/鱼壳/提交/c5c59d4acb00674bc37198468b5978f69484c628

如果你有这样的状态:

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

在此处输入图像描述

您也可以只键入README并点击选项卡,如果它是唯一的匹配项,它将为您插入。好样的!

于 2016-03-21T23:01:41.333 回答