如何 git 仅添加与模式匹配的行?
How do I git add only lines matching a pattern?
我正在使用 git 一些配置文件进行跟踪。我通常会进行交互式 git add -p
,但我正在寻找一种方法来自动添加与模式匹配的所有 new/modified/deleted 行。否则,我将花费很长时间来完成所有交互式拆分和添加。 git add
有文件名模式匹配,但我找不到有关内容的任何信息。
您可以从 git ls-files
开始获取给定路径的感兴趣文件列表。然后您可以将该列表通过管道传输到 grep
并根据正则表达式匹配进行限制。最后,这个简化的文件列表可以通过 xargs git add
:
传送到 git add
git ls-files [path] | grep '^some regex goes here$' | xargs git add -p
此方法允许您应用智能正则表达式,有望减少交互式 git add
会话的文件数量。根据定义,执行 git add -p
需要人工交互,因此如果在应用该模式后您仍然有太多文件,那么您应该寻找另一种方法。
如果您想添加整个文件,而不是行,请尝试 this answer。
我不认为这是可能的;因为 git add -p
总是向您显示大量更改;但是那个 hunk 可能包含一些你想添加的行(并且匹配你的模式)和一个包含你不想添加的更改的行。
有时我在做两个更改并想分别提交时遇到类似的问题:
- 重命名变量
- 添加一些功能
我使用了一个解决方法:
- 搁置我的更改(使用
git stash
或仅复制文件)
- 重命名变量(所以我重做了我工作中最简单的部分;因为重命名变量通常由 IDE)
- 提交这些更改
- 重新应用我的更改(使用
git stash pop
或将文件复制回来)
- 提交我的其余更改
这当然很疯狂。但是你知道,我偶尔会问起我工作中一些疯狂的工作流程,而这些疯狂通常有一些充分的理由。
好的,这个模式是逐行模式还是"if the chunk contains it"模式?如果它是逐行的,也许你可以做这样的事情。这不会完全起作用,但这是一个开始
git diff <file> | egrep '^[^+]|<pattern' > file.patch
git stash
git apply file.patch
如果您必须应用任何具有您正在寻找的模式的块,那么您将需要一个更长、更有状态的脚本来解析您的差异。事实上,无论如何这可能是必要的。浏览 diff 以查找指示 diff 部分开头的“@@”字符。缓冲该部分,直到结束。如果你 运行 进入有问题的模式,输出该部分,如果没有,则将其丢弃。然后将该新差异作为补丁应用。
git diff <file> | parse_diff_script.sh > file.patch
git stash
git apply file.patch
我在 TXR:
中推出了这个实验性且测试不佳的程序
样本运行:首先我们在回购中的位置:
$ git diff
diff --git a/lorem.txt b/lorem.txt
index d5d20a4..58609a7 100644
--- a/lorem.txt
+++ b/lorem.txt
@@ -2,10 +2,14 @@ Lorem ipsum dolor sit amet,
consectetur adipiscing elit,
sed do eiusmod tempor
incididunt ut labore et dolore
-magna aliqua. Ut enim ad minim
+minim
+minim
veniam, quis nostrud
exercitation ullamco laboris
+maxim
+maxim
nisi ut aliquip ex ea commodo
+minim
consequat. Duis aute irure
dolor in reprehenderit in
voluptate velit esse cillum
并且:
$ git diff --cached # nothing staged in the index
目标是仅提交包含 min
:
匹配项的行
$ txr addmatch.txr min lorem.txt
patching file .merge_file_BilTfQ
现在是什么状态?
$ git diff
diff --git a/lorem.txt b/lorem.txt
index 7e1b4cb..58609a7 100644
--- a/lorem.txt
+++ b/lorem.txt
@@ -6,6 +6,8 @@ minim
minim
veniam, quis nostrud
exercitation ullamco laboris
+maxim
+maxim
nisi ut aliquip ex ea commodo
minim
consequat. Duis aute irure
并且:
$ git diff --cached
diff --git a/lorem.txt b/lorem.txt
index d5d20a4..7e1b4cb 100644
--- a/lorem.txt
+++ b/lorem.txt
@@ -2,10 +2,12 @@ Lorem ipsum dolor sit amet,
consectetur adipiscing elit,
sed do eiusmod tempor
incididunt ut labore et dolore
-magna aliqua. Ut enim ad minim
+minim
+minim
veniam, quis nostrud
exercitation ullamco laboris
nisi ut aliquip ex ea commodo
+minim
consequat. Duis aute irure
dolor in reprehenderit in
voluptate velit esse cillum
匹配的内容在索引中,不匹配的 +maxim
行仍未暂存。
addmatch.txr
中的代码:
@(next :args)
@(assert)
@pattern
@file
@(bind regex @(regex-compile pattern))
@(next (open-command `git diff @file`))
diff @diffjunk
index @indexjunk
--- a/@file
+++ b/@file
@(collect)
@@@@ -@bfline,@bflen +@afline,@aflen @@@@@(skip)
@ (bind (nminus nplus) (0 0))
@ (collect)
@ (cases)
@line
@ (bind zerocol " ")
@ (or)
+@line
@ (bind zerocol "+")
@ (require (search-regex line regex))
@ (do (inc nplus))
@ (or)
-@line
@ (bind zerocol "-")
@ (require (search-regex line regex))
@ (do (inc nminus))
@ (or)
-@line
@;; unmatched - line becomes context line
@ (bind zerocol " ")
@ (end)
@ (until)
@/[^+\- ]/@(skip)
@ (end)
@ (set (bfline bflen afline aflen)
@[mapcar int-str (list bfline bflen afline aflen)])
@ (set aflen @(+ bflen nplus (- nminus)))
@(end)
@(output :into stripped-diff)
diff @diffjunk
index @indexjunk
--- a/@file
+++ b/@file
@ (repeat)
@@@@ -@bfline,@bflen +@afline,@aflen @@@@
@ (repeat)
@zerocol@line
@ (end)
@ (end)
@(end)
@(next (open-command `git checkout-index --temp @file`))
@tempname@\t@file
@(try)
@ (do
(with-stream (patch-stream (open-command `patch -p1 @tempname` "w"))
(put-lines stripped-diff patch-stream)))
@ (next (open-command `git hash-object -w @tempname`))
@newsha
@ (do (sh `git update-index --cacheinfo 100644 @newsha @file`))
@(catch)
@ (fail)
@(finally)
@ (do
(ignerr [mapdo remove-path #`@tempname @tempname.orig @tempname.rej`]))
@(end)
基本上策略是:
对 git diff
输出进行一些模式匹配,以将大块过滤到匹配行。我们必须 re-compute 大块 header 中的 "after" 行计数,并保留上下文行。
将过滤后的差异输出到变量中。
使用 git checkout-index --temp
从索引中获取文件的原始副本。此命令输出它生成的临时名称,我们捕获它。
现在将 filtered/reduced diff 发送到 patch -p1
,目标是保存索引原始副本的临时文件。好的,我们现在已经将我们想要的更改应用于原始文件。
接下来,使用 git hash-object -w
从补丁文件中创建一个 Git object。捕获此命令输出的哈希值。
最后,使用git update-index --cacheinfo ...
将这个新的object输入到原始文件名下的索引中,有效地对文件进行更改。
如果这搞砸了,我们可以 git reset
擦除索引,修复损坏的脚本并重试。
通过 +
和 -
行盲目匹配有明显的问题。它应该适用于模式匹配配置文件中的变量名称而不是内容的情况。例如
替换:
-CONFIG_VAR=foo
+CONFIG_VAR=bar
在这里,如果我们在 CONFIG_VAR
上进行匹配,那么这两行都包括在内。如果我们在右侧的 foo
上进行匹配,我们会破坏一些东西:我们最终会得到一个仅减去 CONFIG_VAR=foo
行的补丁!
显然,考虑到配置文件的语法和语义,这可以变得更聪明。
我如何解决这个 "for real" 将是编写一个健壮的配置文件解析器和 re-generator(它保留注释、空格和所有内容)。然后将新的和原始的 pristine 文件解析到 config objects,将匹配的更改从一个 object 迁移到另一个,并生成一个更新文件以转到索引。不要乱用补丁。
这里有一个方法:
使用git diff > patch
对当前差异进行补丁。
使用 gawk
仅对匹配模式的 +/-
行制作第二个补丁:从不匹配模式的已删除行中删除 -
,删除 +
行与模式不匹配,修改 hunk header 行号,输出每个修改的 hunk,但不输出任何修改后的 hunk 不再有任何变化。
使用 git stash save
、apply patch
、add -u
和 stash pop
应用和暂存修改后的补丁,并保留其余更改未暂存.
这适用于多个测试用例,它一次适用于整个差异(所有文件),而且速度很快。
#!/bin/sh
diff=`mktemp`
git diff > $diff
[ -s $diff ] || exit
patch=`mktemp`
gawk -v pat="" '
function hh(){
if(keep && n > 0){
for(i=0;i<n;i++){
if(i==hrn){
printf "@@ -%d,%d +%d,%d @@\n", har[1],har[2],har[3],har[4];
}
print out[i];
}
}
}
{
if(/^diff --git a\/.* b\/.*/){
hh();
keep=0;
dr=NR;
n=0;
out[n++]=[=10=]
}
else if(NR == dr+1 && /^index [0-9a-f]+\.\.[0-9a-f]+ [0-9]+$/){
ir=NR;
out[n++]=[=10=]
}
else if(NR == ir+1 && /^\-\-\- a\//){
mr=NR;
out[n++]=[=10=]
}
else if(NR == mr+1 && /^\+\+\+ b\//){
pr=NR;
out[n++]=[=10=]
}
else if(NR == pr+1 && match([=10=], /^@@ \-([0-9]+),?([0-9]+)? \+([0-9]+),?([0-9]+)? @@/, har)){
hr=NR;
hrn=n
}
else if(NR > hr){
if(/^\-/ && [=10=] !~ pat){
har[4]++;
sub(/^\-/, " ", [=10=]);
out[n++] = [=10=]
}
else if(/^\+/ && [=10=] !~ pat){
har[4]--;
}
else{
if(/^[+-]/){
keep=1
}
out[n++] = [=10=]
}
}
}
END{
hh()
}' $diff > $patch
git stash save &&
git apply $patch &&
git add -u &&
git stash pop
rm $diff
rm $patch
参考资料:
我正在使用 git 一些配置文件进行跟踪。我通常会进行交互式 git add -p
,但我正在寻找一种方法来自动添加与模式匹配的所有 new/modified/deleted 行。否则,我将花费很长时间来完成所有交互式拆分和添加。 git add
有文件名模式匹配,但我找不到有关内容的任何信息。
您可以从 git ls-files
开始获取给定路径的感兴趣文件列表。然后您可以将该列表通过管道传输到 grep
并根据正则表达式匹配进行限制。最后,这个简化的文件列表可以通过 xargs git add
:
git add
git ls-files [path] | grep '^some regex goes here$' | xargs git add -p
此方法允许您应用智能正则表达式,有望减少交互式 git add
会话的文件数量。根据定义,执行 git add -p
需要人工交互,因此如果在应用该模式后您仍然有太多文件,那么您应该寻找另一种方法。
如果您想添加整个文件,而不是行,请尝试 this answer。
我不认为这是可能的;因为 git add -p
总是向您显示大量更改;但是那个 hunk 可能包含一些你想添加的行(并且匹配你的模式)和一个包含你不想添加的更改的行。
有时我在做两个更改并想分别提交时遇到类似的问题:
- 重命名变量
- 添加一些功能
我使用了一个解决方法:
- 搁置我的更改(使用
git stash
或仅复制文件) - 重命名变量(所以我重做了我工作中最简单的部分;因为重命名变量通常由 IDE)
- 提交这些更改
- 重新应用我的更改(使用
git stash pop
或将文件复制回来) - 提交我的其余更改
这当然很疯狂。但是你知道,我偶尔会问起我工作中一些疯狂的工作流程,而这些疯狂通常有一些充分的理由。
好的,这个模式是逐行模式还是"if the chunk contains it"模式?如果它是逐行的,也许你可以做这样的事情。这不会完全起作用,但这是一个开始
git diff <file> | egrep '^[^+]|<pattern' > file.patch
git stash
git apply file.patch
如果您必须应用任何具有您正在寻找的模式的块,那么您将需要一个更长、更有状态的脚本来解析您的差异。事实上,无论如何这可能是必要的。浏览 diff 以查找指示 diff 部分开头的“@@”字符。缓冲该部分,直到结束。如果你 运行 进入有问题的模式,输出该部分,如果没有,则将其丢弃。然后将该新差异作为补丁应用。
git diff <file> | parse_diff_script.sh > file.patch
git stash
git apply file.patch
我在 TXR:
中推出了这个实验性且测试不佳的程序样本运行:首先我们在回购中的位置:
$ git diff
diff --git a/lorem.txt b/lorem.txt
index d5d20a4..58609a7 100644
--- a/lorem.txt
+++ b/lorem.txt
@@ -2,10 +2,14 @@ Lorem ipsum dolor sit amet,
consectetur adipiscing elit,
sed do eiusmod tempor
incididunt ut labore et dolore
-magna aliqua. Ut enim ad minim
+minim
+minim
veniam, quis nostrud
exercitation ullamco laboris
+maxim
+maxim
nisi ut aliquip ex ea commodo
+minim
consequat. Duis aute irure
dolor in reprehenderit in
voluptate velit esse cillum
并且:
$ git diff --cached # nothing staged in the index
目标是仅提交包含 min
:
$ txr addmatch.txr min lorem.txt
patching file .merge_file_BilTfQ
现在是什么状态?
$ git diff
diff --git a/lorem.txt b/lorem.txt
index 7e1b4cb..58609a7 100644
--- a/lorem.txt
+++ b/lorem.txt
@@ -6,6 +6,8 @@ minim
minim
veniam, quis nostrud
exercitation ullamco laboris
+maxim
+maxim
nisi ut aliquip ex ea commodo
minim
consequat. Duis aute irure
并且:
$ git diff --cached
diff --git a/lorem.txt b/lorem.txt
index d5d20a4..7e1b4cb 100644
--- a/lorem.txt
+++ b/lorem.txt
@@ -2,10 +2,12 @@ Lorem ipsum dolor sit amet,
consectetur adipiscing elit,
sed do eiusmod tempor
incididunt ut labore et dolore
-magna aliqua. Ut enim ad minim
+minim
+minim
veniam, quis nostrud
exercitation ullamco laboris
nisi ut aliquip ex ea commodo
+minim
consequat. Duis aute irure
dolor in reprehenderit in
voluptate velit esse cillum
匹配的内容在索引中,不匹配的 +maxim
行仍未暂存。
addmatch.txr
中的代码:
@(next :args)
@(assert)
@pattern
@file
@(bind regex @(regex-compile pattern))
@(next (open-command `git diff @file`))
diff @diffjunk
index @indexjunk
--- a/@file
+++ b/@file
@(collect)
@@@@ -@bfline,@bflen +@afline,@aflen @@@@@(skip)
@ (bind (nminus nplus) (0 0))
@ (collect)
@ (cases)
@line
@ (bind zerocol " ")
@ (or)
+@line
@ (bind zerocol "+")
@ (require (search-regex line regex))
@ (do (inc nplus))
@ (or)
-@line
@ (bind zerocol "-")
@ (require (search-regex line regex))
@ (do (inc nminus))
@ (or)
-@line
@;; unmatched - line becomes context line
@ (bind zerocol " ")
@ (end)
@ (until)
@/[^+\- ]/@(skip)
@ (end)
@ (set (bfline bflen afline aflen)
@[mapcar int-str (list bfline bflen afline aflen)])
@ (set aflen @(+ bflen nplus (- nminus)))
@(end)
@(output :into stripped-diff)
diff @diffjunk
index @indexjunk
--- a/@file
+++ b/@file
@ (repeat)
@@@@ -@bfline,@bflen +@afline,@aflen @@@@
@ (repeat)
@zerocol@line
@ (end)
@ (end)
@(end)
@(next (open-command `git checkout-index --temp @file`))
@tempname@\t@file
@(try)
@ (do
(with-stream (patch-stream (open-command `patch -p1 @tempname` "w"))
(put-lines stripped-diff patch-stream)))
@ (next (open-command `git hash-object -w @tempname`))
@newsha
@ (do (sh `git update-index --cacheinfo 100644 @newsha @file`))
@(catch)
@ (fail)
@(finally)
@ (do
(ignerr [mapdo remove-path #`@tempname @tempname.orig @tempname.rej`]))
@(end)
基本上策略是:
对
git diff
输出进行一些模式匹配,以将大块过滤到匹配行。我们必须 re-compute 大块 header 中的 "after" 行计数,并保留上下文行。将过滤后的差异输出到变量中。
使用
git checkout-index --temp
从索引中获取文件的原始副本。此命令输出它生成的临时名称,我们捕获它。现在将 filtered/reduced diff 发送到
patch -p1
,目标是保存索引原始副本的临时文件。好的,我们现在已经将我们想要的更改应用于原始文件。接下来,使用
git hash-object -w
从补丁文件中创建一个 Git object。捕获此命令输出的哈希值。最后,使用
git update-index --cacheinfo ...
将这个新的object输入到原始文件名下的索引中,有效地对文件进行更改。
如果这搞砸了,我们可以 git reset
擦除索引,修复损坏的脚本并重试。
通过 +
和 -
行盲目匹配有明显的问题。它应该适用于模式匹配配置文件中的变量名称而不是内容的情况。例如
替换:
-CONFIG_VAR=foo
+CONFIG_VAR=bar
在这里,如果我们在 CONFIG_VAR
上进行匹配,那么这两行都包括在内。如果我们在右侧的 foo
上进行匹配,我们会破坏一些东西:我们最终会得到一个仅减去 CONFIG_VAR=foo
行的补丁!
显然,考虑到配置文件的语法和语义,这可以变得更聪明。
我如何解决这个 "for real" 将是编写一个健壮的配置文件解析器和 re-generator(它保留注释、空格和所有内容)。然后将新的和原始的 pristine 文件解析到 config objects,将匹配的更改从一个 object 迁移到另一个,并生成一个更新文件以转到索引。不要乱用补丁。
这里有一个方法:
使用
git diff > patch
对当前差异进行补丁。使用
gawk
仅对匹配模式的+/-
行制作第二个补丁:从不匹配模式的已删除行中删除-
,删除+
行与模式不匹配,修改 hunk header 行号,输出每个修改的 hunk,但不输出任何修改后的 hunk 不再有任何变化。使用
git stash save
、apply patch
、add -u
和stash pop
应用和暂存修改后的补丁,并保留其余更改未暂存.
这适用于多个测试用例,它一次适用于整个差异(所有文件),而且速度很快。
#!/bin/sh
diff=`mktemp`
git diff > $diff
[ -s $diff ] || exit
patch=`mktemp`
gawk -v pat="" '
function hh(){
if(keep && n > 0){
for(i=0;i<n;i++){
if(i==hrn){
printf "@@ -%d,%d +%d,%d @@\n", har[1],har[2],har[3],har[4];
}
print out[i];
}
}
}
{
if(/^diff --git a\/.* b\/.*/){
hh();
keep=0;
dr=NR;
n=0;
out[n++]=[=10=]
}
else if(NR == dr+1 && /^index [0-9a-f]+\.\.[0-9a-f]+ [0-9]+$/){
ir=NR;
out[n++]=[=10=]
}
else if(NR == ir+1 && /^\-\-\- a\//){
mr=NR;
out[n++]=[=10=]
}
else if(NR == mr+1 && /^\+\+\+ b\//){
pr=NR;
out[n++]=[=10=]
}
else if(NR == pr+1 && match([=10=], /^@@ \-([0-9]+),?([0-9]+)? \+([0-9]+),?([0-9]+)? @@/, har)){
hr=NR;
hrn=n
}
else if(NR > hr){
if(/^\-/ && [=10=] !~ pat){
har[4]++;
sub(/^\-/, " ", [=10=]);
out[n++] = [=10=]
}
else if(/^\+/ && [=10=] !~ pat){
har[4]--;
}
else{
if(/^[+-]/){
keep=1
}
out[n++] = [=10=]
}
}
}
END{
hh()
}' $diff > $patch
git stash save &&
git apply $patch &&
git add -u &&
git stash pop
rm $diff
rm $patch
参考资料: