通过grep/regex将行添加到Git索引

30
我有一个巨大的补丁,想将其分成多个逻辑git提交。许多更改只是更改变量名称或函数调用,因此它们可以很容易地通过grep定位。如果我可以添加与正则表达式匹配的任何更改到索引中,然后在git gui中清理,这将节省我大量手动工作。是否有一种好的方法可以使用git中的正则表达式或来自grep的某些输出(例如行号)逐行更新索引?
我找到了一个类似的问题,但我不确定如何从正则表达式类型搜索构建临时文件。

1
一些示例可能会在澄清您试图实现的内容方面起到很大作用。 - rvalvik
1
Git的原子工作单位是整个文件,也就是说,在你的项目中,你要么暂存(或不暂存)整个文件,而不是单独的行。 - Tim Biegeleisen
你能否编写一个脚本,它可以获取最后一次提交中文件的版本,将其与当前磁盘上文件的内容进行比较,并生成您想要提交的版本? - LeGEC
@LeGEC问题在于同一个文件中可能有一些我不想暂存的更改。 - Samantha
2
@TimBiegeleisen 可以使用 -p-i 进行添加,这样您就可以选择要添加到索引的文件的特定部分(非常酷)。 - eftshift0
显示剩余8条评论
7个回答

35

patchutils 提供了一个命令grepdiff,可用于实现此功能。

# check that the regex search correctly matches the changes you want.
git diff -U0 | grepdiff 'regex search' --output-matching=hunk  

# then apply the changes to the index
git diff -U0 | grepdiff 'regex search' --output-matching=hunk | git apply --cached --unidiff-zero 

我在 diff 命令中使用了 -U0 参数,以避免出现无关的更改。你可能需要根据自己的情况调整此值。


3
对我来说这很有效,但需要注意的是 hunk 如果你修改了不匹配正则表达式的相邻行,就不能提供足够的细化程度。 - arcyqwerty
我发现一个好方法就是运行第二行代码,然后检查结果。使用 git status -v 查看已暂存的内容,使用 git diff 查看所有未暂存的内容。如果一切看起来正确,那么就提交吧。 - Gerry
这对于使用 git apply --reverse 撤销工作目录中的某些更改也非常方便。 - Joshua Goldberg

5
更简单的方法是使用git add -p命令,并利用/选项搜索差异以添加补丁。虽然不是完全自动化,但比其他我找到的替代方案更容易。

2

git add -p <file> 命令的作用,大致上是这样的:

tmpfile=$(mktemp)
tf2=$(mktemp)
tf3=$(mktemp)
git diff <file> > $tmpfile
while [ -s $tmpfile ]; do
    extract first diff hunk from $tmpfile to $tf2 and rest to $tf3
    show you $tf2, ask if you want to include this hunk
        (with options to edit the hunk, etc); repeat until ready
    if you say to *add* the hunk, run git apply --cached $tf2
    cat < $tf3 > $tf2
done
rm -f $tmpfile $tf2 $tf3

也就是说,git add -p 使用的是 git apply --cached(一种特殊的子变体,它忽略了文件的 工作树 副本的 git apply --index)。你需要从上面得出的关键信息是:有三个版本的文件!

  • 第一个版本(在这里完全被忽略)已经永久冻结在 HEAD 提交中。
  • 第二个版本在 Git 的 索引 或者说 暂存区 中。这是由 git diff 用作“旧版本”的。
  • 第三个版本在你的工作树中。这是由 git diff 用作“新版本”的。

Git 允许你接受或跳过的补丁只是比较“旧”(索引)和“新”(工作树)版本的结果。如果你接受了某个补丁,Git 将通过应用该补丁来更新索引中的副本。

因此,如果工作树版本中有一些行(比如说从第100行到110行)是你想用来替换索引版本中的另一组行(比如说从第90行到92行),那么构建的方法是:
  • 提取索引版本;
  • 从索引版本中刮去1-89行;将工作树版本的100-110行连接起来;将索引版本的93-结尾连接到一个临时文件中;
  • 用临时文件替换索引副本。
要阅读索引版本,请使用git showgit cat-file -p与文件的索引版本名称。如果文件名为path/to/file,则索引版本的名称为:path/to/file(简写为:0:path/to/file:我们希望在槽零中有副本;不能在槽1、2或3中有副本,以便在槽0中有副本;您可以尝试从槽零读取它,如果失败,则假定该文件不在索引中或存在冲突)。
阅读工作树文件(某些选择的行子集)留作练习,连接部分和任何您希望包括的错误检查也是如此。
假设最终生成的文件位于名为$tf的临时文件中(作为shell变量),要更新索引副本,您必须首先确保存在适当的blob哈希ID:
hash=$(git hash-object -w -t blob --path="$path" -- "$tf")

例如(假设您想运行通常的.gitattribute过滤器(如果有),并且知道路径为$path)。然后,如果一切顺利,请使用该哈希ID与git update-index:
git update-index --cacheinfo "$mode,$hash,$path"

其中$mode为文件的相应模式,可以是100644100755。如果您不想更改该模式,则可以使用git ls-files --cached或类似命令读取以前的模式。否则,如果core.fileModetrue,则从文件的工作树副本中读取模式,以与git add的行为匹配:将“具有任何可执行位”转换为100755,“无可执行位”转换为100644。当core.fileModefalse时,请使用git config --get --type bool core.filemode命令进行阅读。对于这种add-patch情况下,git add使用现有的模式)。


0

你可以先运行:

git status | \grep "your_pattern"

如果输出符合预期,那么将文件添加到索引中:
git add $(git status | \grep "your_pattern")

2
这不是问题的答案。OP问如何添加特定行,但你的回答是如何添加特定文件。 - DeveloperKid
git status 显示了被修改的文件,但并没有显示哪些代码行发生了变化。我不明白这如何回答问题。 - Alex
git status 显示已更改的文件,而不是更改的代码行。我不明白这如何回答问题。 - Alex

0

我找到了一个答案。

以下是一些步骤:

  1. git status --porcelain 以易于解析的格式为脚本(如 grep)提供 git 状态。

  2. sed s/^...// 从第3个字符切片到行尾。

  3. xargs 使您能够逐行运行脚本。

在我的情况下,使用需要忽略迁移的django,我的脚本是 git status --porcelain | sed s/^...// | grep -v migrations | xargs git add

您可以定制grep选项以适合自己的需求。


文档

xargs

git-status

sed


0
我现在正在Windows上使用Git-Bash,遇到了类似的问题:我不需要添加一些“未提交文件列表”中的文件。
 $ git status
 On branch Bug_#292400_buggy
 Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)


    modified:   the/path/to/the/file333.NO   
    modified:   the/path/to/the/file334.NO 
    modified:   the/path/to/the/file1.ok
    modified:   the/path/to/the/file2.ok
    modified:   the/path/to/the/file3.ok
    modified:   the/path/to/the/file4.ok
    ....................................
    modified:   the/path/to/the/file666.ok

首先,我检查了文件选择是否符合我的要求:

$ git status | grep ok
            modified:   the/path/to/the/file1.ok
            modified:   the/path/to/the/file2.ok
            modified:   the/path/to/the/file3.ok
            modified:   the/path/to/the/file4.ok
            ....................................
            modified:   the/path/to/the/file666.ok

我尝试了一个在这个论坛中描述的想法,以便将相同的文件列表添加到git中,如下:

$ git add $(git status | \grep "your_pattern")

但对我不起作用(记住:在Windows10上使用Git-Bash

至少,我尝试了一种直接的方式,它很好用:

$ git add *ok
$ git status
On branch Bug_#292400_buggy
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

            modified:   the/path/to/the/file1.ok
            modified:   the/path/to/the/file2.ok
            modified:   the/path/to/the/file3.ok
            modified:   the/path/to/the/file4.ok
            ....................................
            modified:   the/path/to/the/file666.ok

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   the/path/to/the/file333.NO   
        modified:   the/path/to/the/file334.NO

准备好提交了,所以。


这不是问题的答案。OP问如何添加特定行,但你的回答是如何添加特定文件。 - DeveloperKid

-1

xargs 是你要找的。尝试这个:

grep -irl 'regex_term_to_find' * | xargs -I FILE git add FILE

在管道符号|之前是用于搜索所有文件*的标准grep命令。选项包括:

  • i - 不区分大小写
  • r - 递归搜索子目录
  • l - 仅列出文件名

在语句的xargs部分,FILE是用于每个参数/匹配项的变量名称,由grep命令传递。然后使用变量输入所需的命令。


10
谢谢,但我只想添加文件的一部分,而不是整个文件。 - FazJaxton
@FazJaxton,你需要添加一个补丁。在git add之后使用-p参数,但我不确定它在xargs中如何工作,因为补丁过程是由你完成的(即选择应该添加什么和不应该添加什么)。 - RDL

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