【问题标题】:How to pass a programmatically generated list of files to `git filter-branch`?如何将以编程方式生成的文件列表传递给`git filter-branch`?
【发布时间】:2013-10-30 16:45:46
【问题描述】:

我正在拆分 git repo 的一部分以创建新的 repo,并尝试使用 git filter-branch 来维护正在移动到新项目的文件的历史记录。我知道--subdirectory-filter,但这不是一个好的解决方案,因为我提取的文件并没有完全映射到一个子目录。到目前为止我发现的最佳选择是--index-filter,使用如下:

git filter-branch -f --index-filter 'git read-tree --empty && git reset -q "${GIT_COMMIT}" -- <list of files>' --prune-empty -f

这似乎可行,但我希望能够以编程方式生成要保留的文件列表,以便我可以迭代地优化此列表。我目前正在尝试获取要保留在另一个文件中的文件列表,并将其附加到代表每次提交要执行的命令的字符串中,如下所示:

tmp=$(cat ~/to_keep.txt) && git filter-branch -f --index-filter 'git read-tree --empty && git reset -q "${GIT_COMMIT}" -- '$tmp --prune-empty -f

不幸的是,这会导致

fatal: bad flag '--prune-empty' used after filename

即使只是回显文件似乎也会造成麻烦:

tmp=$(echo a.txt b.txt) && git filter-branch -f --index-filter 'git read-tree --empty && git reset -q "${GIT_COMMIT}" -- '$tmp --prune-empty -f
fatal: ambiguous argument 'b.txt': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

我之前也尝试过连接字符串:

tmp1=$(echo a.txt b.txt) && tmp2='git read-tree --empty && git reset -q "${GIT_COMMIT}" -- ' && tmp3=${tmp2}${tmp1} && git filter-branch -f --index-filter $tmp3 --prune-empty -f
fatal: ambiguous argument 'read-tree': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

我认为这只是连接并没有像我期望的那样在 shell 中发生。有谁知道我怎样才能使这项工作?如果您也能解释这些错误的含义,那就太好了。谢谢。

【问题讨论】:

    标签: git bash


    【解决方案1】:

    各种...-filters 的每个参数都需要是一个字符串。该字符串被保存为 shell 变量:

        --index-filter)
                filter_index="$OPTARG"
                ;;
    

    在适当的时候,filter-branch 脚本(位于git-core 子目录中,例如/usr/libexec/git-core/usr/local/libexec/git-core)执行以下操作:

        eval "$filter_index" < /dev/null ||
                die "index filter failed: $filter_index"
    

    (除了使用/bin/sh -c "$filter_commit" ... 运行的提交过滤器)。

    因此,您的假设是正确的,您需要使文件列表成为单个以空格分隔的字符串的一部分。

    执行此操作的最简单方法是从您的原始命令开始:

    git filter-branch -f --index-filter \
        'git read-tree --empty && git reset -q "${GIT_COMMIT}" -- <list of files>' \
        --prune-empty -f
    

    (当您有静态列表时有效)并修改它以从~/to_keep.txt 中提取动态列表。我将原件分成三行部分是为了显示,但也因为我们现在可以只关注中间行。

    [编辑以修复评论中指出的换行问题。让我们创建一个别名或 shell 函数 xc,将换行符转换为空格]

    xc() {
        tr '\n' ' '
    }
    
    "git read-tree --empty && git reset -q \"\${GIT_COMMIT}\" -- $(xc < ~/to_keep.txt)" \
    

    或:

    'git read-tree --empty && git reset -q "${GIT_COMMIT}" -- '"$(xc < ~/to_keep.txt)" \
    

    或者,正如您所尝试的那样(但有一处更改):

    'git read-tree --empty && git reset -q "${GIT_COMMIT}" -- '"$tmp" \
    

    (已设置tmp=$(xc &lt; ~/to_keep.txt))。

    请注意,如果任何文件名包含空格,则这些都不正确。例如,假设一个文件名为a file(嵌入空白)。 eval 将在空格处断开参数,git reset 命令将名称 afile 作为两个单独的参数。

    只要你没有任何这样的文件名,你就不必担心解决这个问题。

    另一个潜在的问题是如果这个文件列表变得很长。您可能会遇到内核对可以发送到一个文件的参数数量的限制。您应该能够使用xargs 来解决这个问题(并且就此而言,通过一些工作和使用-0 来处理文件名中的空格)。

    【讨论】:

    • 谢谢,为我工作。需要注意的是"git read-tree --empty &amp;&amp; git reset -q \"\${GIT_COMMIT}\" -- $(cat ~/to_keep.txt)" 解决方案不会将多行压缩为一行,因此如果您使用该方法,则需要通过tr '\n' ' ' 或类似的方式进行管道传输。
    • 啊,对,好点。如果不是eval 部分,那将不是问题! :-)
    猜你喜欢
    • 2020-02-13
    • 2013-02-07
    • 2019-12-28
    • 2015-10-24
    • 2010-12-24
    • 2012-04-29
    • 2012-08-16
    • 1970-01-01
    • 2014-09-21
    相关资源
    最近更新 更多