支持文件名的Git bash自动补全?

10

有没有支持文件名补全的bash-completion脚本?我主要使用Mercurial,我可以输入:

hg diff test/test_<tab>

它会展示/补全所有修改过的测试文件。它适用于大多数子命令,例如hg add <tab><tab>只会列出未追踪的文件。这真的非常方便。

从git contrib中获取的bash脚本似乎不支持此功能。有没有其他选择,或者你是如何在命令行上使用git的?

编辑2015

git-completion.bash~1.8.2开始支持完整文件名补全。


我通常先使用 git status 命令,然后知道该输入什么来执行 git diff 命令。(但大多数情况下,我使用 gitk 来查看差异。) - Paŭlo Ebermann
注意:使用Git 2.18(2018年第二季度),文件名自动补全将更快。请参见我的答案 - VonC
4个回答

13

那么,让我们看看Mercurial bash自动补全脚本是如何实现这一点的。

这是重要部分

_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)似乎输出正确的内容(如果没有重命名的话)。

但是,如果与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
#      https://dev59.com/qW025IYBdhLWcg3wYFCH
#  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的答案)。

我可能会再试一次,但不能保证近期会这样做。如果其他人想做,请随意使用我的代码,进行更改并重新提交。


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>。这样虽然简单,但似乎作为快速修复很有效。


0

虽然这不是你想要的回答,但我想让你知道,不久的将来,fish(友好交互式Shell)将会默认为你提供git文件名补全支持。它目前在主分支上,并且即将发布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 

enter image description here

如果README是唯一匹配项,您也可以只键入README并按Tab键,它会自动插入。非常方便!


请查看我在帖子中的2015年更新:“自1.8.2版本以来,git-completion.bash支持完整的文件名补全”。 - olt

0

自2011年起,正如OP评论所述,Git支持完整的文件名完成,自~1.8.2版本开始。

但是在Git 2.18(2018年第二季度)中,提供路径列表的shell完成(在contrib/中)已经得到了一定的优化。

请参见提交78a2d21(2018年4月4日),作者为Clemens Buchacher(drizzd
(由Junio C Hamano -- gitster --提交3a940e9中合并,2018年4月25日)

完成:提高ls-files过滤器性能

From the output of ls-files, we remove all but the leftmost path component and then we eliminate duplicates. We do this in a while loop, which is a performance bottleneck when the number of iterations is large (e.g. for 60000 files in linux.git).

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

real    0m11.876s
user    0m4.685s
sys     0m6.808s

Replacing the loop with the cut command improves performance significantly:

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

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

The measurements were done with Msys2 bash, which is used by Git for Windows.

When filtering the ls-files output we take care not to touch absolute paths. This is redundant, because ls-files will never output absolute paths. Remove the unnecessary operations.

The issue was originally reported Git for Windows issue 1533.


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

请查看 commit c0af173, commit 95c11ec, commit 7f45ab2, commit 1684644, commit 8d92fb2, commit 2df179d, commit 0126d14, commit cd129ee, commit 446f46d, commit 7260c7b, commit ce5c61a (2020年4月1日) 由 Elijah Newren (newren) 提交。
请查看 commit 0bbd0e8 (2020年4月1日) 由 Derrick Stolee (derrickstolee) 提交。
(由Junio C Hamano -- gitster --合并于commit 6eacc39, 2020年4月29日)

完成:修复在未跟踪目录下的路径上 'git add' 的问题

签名作者:Elijah Newren

据git邮件列表报道,自从git-2.25以来,
git add untracked-dir/
已经被tab键补全为
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是否有尾随斜杠而报告不同的结果。
旧版本的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调用的方式,而不是依赖于ls-files报告错误内容的bug,使其抓取深度更深的路径。
通过将“$DIR/*”(匹配“$DIR/”加上0个或多个字符)更改为“$DIR/?*”(匹配“$DIR/”加上1个或多个字符)来实现这一点。
请注意,在尝试完成文件名时不应添加“?”字符(例如,“git ls-files -o --directory merge.c?*”将无法正确返回“[merge.c](https://github.com/git/git/blob/c0af173a136785b3cfad4bd414b2fb10a130760a/merge.c)”当存在这样的文件时),因此我们必须确保仅在到目前为止指定的路径是目录的情况下添加“?”字符。

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

请参见提交 cada730(2020年7月20日),作者为Martin Ågren(none
(由Junio C Hamano -- gitster --提交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_none时,我们返回path_excluded。因此,git status --ignored -- pathspecman可能会显示实际上不匹配“pathspec”的路径。
将“排除”检查移到我们检查任何路径规范之后。

Git 2.38 (Q3 2022) 修复了在 2.27 版本周期中引入的另一个回归问题,该问题可能会影响 git-bash 完成。

在非裸仓库中,当 core.worktree 配置变量指向其子目录作为仓库的目录时,Git 的行为在 Git 2.27 版本中出现了回归问题。

请查看提交d6c9a71提交2712899(2022年6月16日),作者为Goss Geppert (ggossdev)
(由Junio C Hamano -- gitster --提交dc6315e中合并,2022年7月14日)

dir:进入代码库

签名者:Goss Geppert
审核者:Elijah Newren

自从8d92fb2(“dir: replace exponential algorithm with a linear one”,2020-04-01,Git v2.27.0-rc0 - merge列在batch #5中)开始遍历存储库的目录树时,当遍历开始于存储库的标准位置之外时,遍历存储库的目录树失败,因为遇到的存储库被识别为嵌套的外部存储库。
在此提交之前,在以下任一条件下,用户可以从用户的角度观察到无法遍历存储库的默认工作树位置(可能还有其他条件):
1. 将core.worktree位置设置为默认工作树的父目录;或 2. 在工作目录位于存储库的默认工作树位置之外时使用--git_dir选项
在这些条件下,无法将文件添加到索引或通过ls-files获取未跟踪文件列表的失败遍历存储库的默认工作树位置的症状。
此提交添加了一个检查,以确定在递归路径时遇到的嵌套存储库是否实际上是the_repository。如果是,则我们简单地将该目录视为不包含嵌套存储库。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接