git - tag - ¿Cómo puedo agregar solo líneas que coincidan con un patrón?
git tag best practices (5)
Estoy siguiendo con git algunos archivos de configuración. Por lo general hago un git add -p
interactivo, pero estoy buscando una manera de agregar automáticamente todas las líneas nuevas / modificadas / eliminadas que coincidan con un patrón. De lo contrario, me llevará años envejecer y agregar toda la división interactiva. git add
tiene un patrón que coincide con los nombres de archivo, pero no puedo encontrar nada sobre el contenido.
Aquí hay una manera:
use
git diff > patch
para hacer un patch de la diferencia actual.use
gawk
para hacer un segundo parche solo de+/-
líneas que coincidan con el patrón: eliminar-
de las líneas eliminadas que no coincidan con el patrón, eliminar+
líneas que no coincidan con el patrón, modificar los números de línea del encabezado del fragmento, generar cada fragmento modificado, pero no t genera cualquier fragmento modificado que ya no tenga ningún cambio en ellos.use
git stash save
,apply patch
,add -u
ystash pop
para aplicar y escalone el parche modificado y deje el resto de los cambios sin programar.
esto funcionó para varios casos de prueba, funciona en todo el diferencial a la vez (todos los archivos), y es rápido.
#!/bin/sh
diff=`mktemp`
git diff > $diff
[ -s $diff ] || exit
patch=`mktemp`
gawk -v pat="$1" ''
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++]=$0
}
else if(NR == dr+1 && /^index [0-9a-f]+/./.[0-9a-f]+ [0-9]+$/){
ir=NR;
out[n++]=$0
}
else if(NR == ir+1 && /^/-/-/- a///){
mr=NR;
out[n++]=$0
}
else if(NR == mr+1 && /^/+/+/+ b///){
pr=NR;
out[n++]=$0
}
else if(NR == pr+1 && match($0, /^@@ /-([0-9]+),?([0-9]+)? /+([0-9]+),?([0-9]+)? @@/, har)){
hr=NR;
hrn=n
}
else if(NR > hr){
if(/^/-/ && $0 !~ pat){
har[4]++;
sub(/^/-/, " ", $0);
out[n++] = $0
}
else if(/^/+/ && $0 !~ pat){
har[4]--;
}
else{
if(/^[+-]/){
keep=1
}
out[n++] = $0
}
}
}
END{
hh()
}'' $diff > $patch
git stash save &&
git apply $patch &&
git add -u &&
git stash pop
rm $diff
rm $patch
refs:
git diff
apply
git add -u
Esto es, por supuesto, una locura. Pero, sabes, tenemos algunos flujos de trabajo locos en mi trabajo que ocasionalmente pregunto y generalmente hay una buena razón para la locura.
Ok, ¿es el patrón un patrón línea por línea o un patrón "si el fragmento lo contiene"? Si es línea por línea, quizás podrías hacer algo como esto. Esto no funcionará exactamente, pero es un comienzo.
git diff <file> | egrep ''^[^+]|<pattern'' > file.patch
git stash
git apply file.patch
Si tiene que aplicar cualquier fragmento que tenga el patrón que está buscando, entonces necesitará un script más largo y con más estado para analizar su diff. De hecho, eso es probablemente necesario de todos modos. Rastrear a través del diff buscando caracteres ''@@'' que indiquen el comienzo de una sección de diff. Amontona esa sección hasta que llegues al final. Si se ha topado con el patrón en cuestión, imprima la sección, si no, entonces deséchela. A continuación, aplique esa nueva diferencia como un parche.
git diff <file> | parse_diff_script.sh > file.patch
git stash
git apply file.patch
No creo que sea posible; ya que git add -p
siempre te muestra trozos de cambios; pero ese fragmento podría contener alguna línea que quisieras agregar (y que coincida con tu patrón) y una línea que contenga cambios que no quieras agregar.
A veces me enfrento a un problema similar cuando hice dos cambios y quiero cometerlos por separado:
- renombrar una variable
- añadir alguna funcionalidad
Hay una solución que utilizo:
- Poner mis cambios a un lado (usando
git stash
o simplemente copiando los archivos) - renombrar la variable (así que rehago la parte fácil de mi trabajo; ya que el IDE generalmente se encarga de renombrar una variable)
- cometer estos cambios
- volver a aplicar mis cambios (usando
git stash pop
o copiando los archivos de nuevo) - cometer el resto de mis cambios
Puede comenzar con git ls-files
para obtener una lista de archivos de interés para una ruta determinada. Luego puede canalizar esa lista en grep
y restringir en función de una coincidencia de expresiones regulares. Finalmente, esta lista reducida de archivos se puede canalizar en git add
través de xargs git add
:
git ls-files [path] | grep ''^some regex goes here$'' | xargs git add -p
Este enfoque le permitirá aplicar una expresión regular inteligente que, con suerte, puede reducir la cantidad de archivos para su sesión interactiva de git add
. Por definición, hacer git add -p
requiere interacción humana, por lo que si después de aplicar el patrón todavía tiene demasiados archivos, debería encontrar otro enfoque.
Por cierto, a veces es útil buscar Desbordamiento de pila antes de publicar, donde puede encontrar publicaciones muy útiles como esta .
Saqué este programa experimental y mal probado en TXR :
Ejecución de la muestra: primero dónde estamos en el repositorio:
$ 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
Y:
$ git diff --cached # nothing staged in the index
El objetivo es simplemente cometer las líneas que contienen una coincidencia para min
:
$ txr addmatch.txr min lorem.txt
patching file .merge_file_BilTfQ
Ahora que es el estado?
$ 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
Y:
$ 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
El material coincidente está en el índice, y las líneas de +maxim
coincidencia aún no se han organizado.
Código en 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)
Básicamente la estrategia es:
haga un poco de coincidencia de patrones en la salida de
git diff
para filtrar los trozos hasta las líneas correspondientes. Debemos volver a calcular el recuento de líneas "después" en el encabezado del hunk y preservar las líneas de contexto.la salida filtrada dif en una variable.
obtenga una copia prístina del archivo del índice utilizando
git checkout-index --temp
. Este comando genera el nombre temporal que ha generado y lo capturamos.Ahora envíe la diferencia filtrada / reducida al
patch -p1
,patch -p1
este archivo temporal que contiene la copia original del índice. Bien, ahora tenemos los cambios que queríamos, aplicados al archivo original.A continuación, cree un objeto Git fuera del archivo parchado, utilizando
git hash-object -w
. Captura el hash que genera este comando.Por último, use
git update-index --cacheinfo ...
para ingresar este nuevo objeto en el índice con el nombre del archivo original,git update-index --cacheinfo ...
efectiva un cambio para el archivo.
Si esto se equivoca, solo podemos hacer un git reset
de Git para borrar el índice, corregir nuestra secuencia de comandos rota e intentarlo de nuevo.
Solo hacer coincidencias ciegas a través de las líneas +
y -
tiene problemas obvios. Debería funcionar en el caso en que los patrones coincidan con los nombres de las variables en los archivos de configuración, en lugar del contenido. P.ej
Reemplazo:
-CONFIG_VAR=foo
+CONFIG_VAR=bar
Aquí, si coincidimos en CONFIG_VAR
, entonces se incluyen ambas líneas. Si coincidimos en foo
en el lado derecho, rompemos cosas: terminamos con un parche que simplemente resta la línea CONFIG_VAR=foo
!
Obviamente, esto podría hacerse inteligente, teniendo en cuenta la sintaxis y la semántica del archivo de configuración.
Cómo resolvería esto "de verdad" sería escribir un analizador y re-generador de archivos de configuración (que conserva los comentarios, espacios en blanco y todo). Luego analice el nuevo y original archivo original para configurar objetos, migre los cambios correspondientes de un objeto a otro y genere un archivo actualizado para ir al índice. No te metas con los parches.