git - etiquetas - remove tag local and remote
¿Cómo gitro apropiadamente el stash/pop en ganchos de pre-confirmación para obtener un árbol de trabajo limpio para las pruebas? (3)
Estoy tratando de hacer un gancho de pre-confirmación con una simple prueba de pruebas unitarias y quiero asegurarme de que mi directorio de trabajo esté limpio. La compilación lleva mucho tiempo, así que quiero aprovechar la reutilización de binarios compilados siempre que sea posible. Mi guión sigue ejemplos que he visto en línea:
# Stash changes
git stash -q --keep-index
# Run tests
...
# Restore changes
git stash pop -q
Esto causa problemas sin embargo. Aquí está el repro:
- Añadir
// Step 1
aa.java
-
git add .
- Añadir
// Step 2
aa.java
-
git commit
-
git stash -q --keep-index
# Stash cambios - Ejecutar pruebas
-
git stash pop -q
# Restaurar cambios
-
En este punto me topé con el problema. El git stash pop -q
aparentemente tiene un conflicto y en a.java
tengo
// Step 1
<<<<<<< Updated upstream
=======
// Step 2
>>>>>>> Stashed changes
¿Hay una manera de hacer que esto salte limpiamente?
Gracias a la respuesta de @torek, pude armar un script que también trata con archivos sin seguimiento. (Nota: no quiero usar git stash -u
debido a un comportamiento no deseado de git stash -u )
El error del git stash
mencionado no ha cambiado y todavía no estoy seguro de si este método puede tener problemas cuando un .gitignore se encuentra entre los archivos modificados. (lo mismo se aplica a la respuesta de @ torek)
#! /bin/sh
# script to run tests on what is to be committed
# Based on http://.com/a/20480591/1606867
# Remember old stash
old_stash=$(git rev-parse -q --verify refs/stash)
# First, stash index and work dir, keeping only the
# to-be-committed changes in the working directory.
git stash save -q --keep-index
changes_stash=$(git rev-parse -q --verify refs/stash)
if [ "$old_stash" = "$changes_stash" ]
then
echo "pre-commit script: no changes to test"
sleep 1 # XXX hack, editor may erase message
exit 0
fi
#now let''s stash the staged changes
git stash save -q
staged_stash=$(git rev-parse -q --verify refs/stash)
if [ "$changes_stash" = "$staged_stash" ]
then
echo "pre-commit script: no staged changes to test"
# re-apply changes_stash
git reset --hard -q && git stash pop --index -q
sleep 1 # XXX hack, editor may erase message
exit 0
fi
# Add all untracked files and stash those as well
# We don''t want to use -u due to
# http://blog.icefusion.co.uk/git-stash-can-delete-ignored-files-git-stash-u/
git add .
git stash save -q
untracked_stash=$(git rev-parse -q --verify refs/stash)
#Re-apply the staged changes
if [ "$staged_stash" = "$untracked_stash" ]
then
git reset --hard -q && git stash apply --index -q stash@{0}
else
git reset --hard -q && git stash apply --index -q stash@{1}
fi
# Run tests
status=...
# Restore changes
# Restore untracked if any
if [ "$staged_stash" != "$untracked_stash" ]
then
git reset --hard -q && git stash pop --index -q
git reset HEAD -- . -q
fi
# Restore staged changes
git reset --hard -q && git stash pop --index -q
# Restore unstaged changes
git reset --hard -q && git stash pop --index -q
# Exit with status from test-run: nonzero prevents commit
exit $status
Hay, pero vamos a llegar de una manera ligeramente indirecta. (Además, vea la advertencia a continuación: hay un error en el código de alijo que pensé que era muy raro, pero aparentemente hay más personas que se están encontrando).
git stash save
(la acción predeterminada para git stash
) realiza un compromiso que tiene al menos dos padres (consulte esta respuesta a una pregunta más básica sobre los alijos). La confirmación de stash
es el estado del árbol de trabajo, y la segunda ocultación de confirmación primaria stash^2
es el estado de índice en el momento de la ocultación.
Una vez que se hace el alijo (y suponiendo que no hay opción -p
), el script git reset --hard
git stash
es un script de git reset --hard
usa git reset --hard
para limpiar los cambios.
Cuando usa --keep-index
, la secuencia de comandos no cambia el alijo guardado de ninguna manera. En su lugar, después de la operación git reset --hard
, la secuencia de comandos usa un git read-tree --reset -u
para borrar los cambios en el directorio de trabajo, reemplazándolos con la parte "index" del alijo.
En otras palabras, es casi como hacer:
git reset --hard stash^2
excepto que git reset
también movería la rama, no del todo lo que quieres, por lo tanto, el método del read-tree
lugar.
Aquí es donde regresa su código. Ahora # Run tests
en el contenido del índice de confirmación.
Suponiendo que todo va bien, supongo que desea que el índice vuelva al estado que tenía cuando hizo el git stash
, y que el árbol de trabajo vuelva a su estado también.
Con git stash apply
o git stash pop
, la forma de hacerlo es usar --index
(no --keep-index
, que es solo para el tiempo de creación de stash, para indicar al script de stash "whack en el directorio de trabajo").
--index
embargo, el uso de --index
seguirá fallando, porque --keep-index
volvió a aplicar los cambios de índice al directorio de trabajo. Por lo tanto, primero debe deshacerse de todos esos cambios ... y para hacer eso, simplemente necesita (re) ejecutar git reset --hard
, tal como lo hizo el script de git reset --hard
. (Probablemente tú también quieras -q
.)
Por lo tanto, esto da como último paso # Restore changes
:
# Restore changes
git reset --hard -q
git stash pop --index -q
(Los separaría como:
git stash apply --index -q && git stash drop -q
Yo mismo, solo por claridad, pero el pop
hará lo mismo).
Como se señala en un comentario a continuación, el git stash pop --index -q
final git stash pop --index -q
queja un poco (o, peor aún, restaura un stash antiguo ) si el paso inicial de git stash save
no encuentra cambios para guardar. Por lo tanto, debe proteger el paso de "restauración" con una prueba para ver si el paso de "guardar" realmente ocultó algo.
El git stash --keep-index -q
inicial git stash --keep-index -q
simplemente sale silenciosamente (con estado 0) cuando no hace nada, por lo que necesitamos manejar dos casos: no existe ningún stash antes o después de guardar; y, existía algún alijo antes de guardar, y el guardar no hizo nada, por lo que el viejo alijo existente sigue siendo la parte superior de la pila de alijo.
Creo que el método más simple es usar git rev-parse
para averiguar qué refs/stash
nombres, en todo caso. Así que deberíamos hacer que el script lea algo más como esto:
#! /bin/sh
# script to run tests on what is to be committed
# First, stash index and work dir, keeping only the
# to-be-committed changes in the working directory.
old_stash=$(git rev-parse -q --verify refs/stash)
git stash save -q --keep-index
new_stash=$(git rev-parse -q --verify refs/stash)
# If there were no changes (e.g., `--amend` or `--allow-empty`)
# then nothing was stashed, and we should skip everything,
# including the tests themselves. (Presumably the tests passed
# on the previous commit, so there is no need to re-run them.)
if [ "$old_stash" = "$new_stash" ]; then
echo "pre-commit script: no changes to test"
sleep 1 # XXX hack, editor may erase message
exit 0
fi
# Run tests
status=...
# Restore changes
git reset --hard -q && git stash apply --index -q && git stash drop -q
# Exit with status from test-run: nonzero prevents commit
exit $status
advertencia: pequeño error en git stash
Hay un error menor en la forma en que git stash
escribe su "bolsa de escondite" . El alijo de estado de índice es correcto, pero supongamos que haces algo como esto:
cp foo.txt /tmp/save # save original version
sed -i '''' -e ''1s/^/inserted/'' foo.txt # insert a change
git add foo.txt # record it in the index
cp /tmp/save foo.txt # then undo the change
Cuando ejecuta git stash save
después de esto, el índice-commit ( refs/stash^2
) tiene el texto insertado en foo.txt
. La confirmación del árbol de trabajo ( refs/stash
) debe tener la versión de foo.txt
sin el material adicional insertado. Sin embargo, si lo mira, verá que tiene la versión incorrecta (modificada por el índice).
La secuencia de comandos anterior utiliza --keep-index
para configurar el árbol de trabajo como estaba el índice, que está perfectamente bien y hace lo correcto para ejecutar las pruebas. Después de ejecutar las pruebas, usa git reset --hard
para volver al estado de confirmación HEAD
(que sigue siendo perfectamente git stash apply --index
) ... y luego usa git stash apply --index
para restaurar el índice (que funciona) y el Directorio de trabajo.
Aquí es donde va mal. El índice se restaura (correctamente) desde la confirmación del índice de ocultación, pero el directorio de trabajo se restaura desde la confirmación del directorio de ocultación de trabajo. Esta confirmación del directorio de trabajo tiene la versión de foo.txt
que está en el índice. En otras palabras, el último paso, cp /tmp/save foo.txt
, que deshizo el cambio, se ha cp /tmp/save foo.txt
.
(El error en la secuencia de comandos de stash
produce porque la secuencia de comandos compara el estado del árbol de trabajo con la confirmación HEAD
para calcular el conjunto de archivos que se deben registrar en el índice temporal especial antes de hacer que la asignación de confirmación especial de work-dir del stash-bag . Dado que foo.txt
ha cambiado con respecto a HEAD
, no puede git add
al índice temporal especial. La confirmación especial del árbol de trabajo se realiza con la versión de foo.txt
del foo.txt
de foo.txt
. La solución es muy simple pero nadie lo ha puesto en git oficial [¿ya?].
No es que quiera animar a la gente a modificar sus versiones de git, pero aquí está la solución .)
basado en la respuesta de torek, se me ocurrió un método para garantizar el comportamiento correcto de los cambios ocultos sin usar git rev-parse , en lugar de eso, utilicé git stash create y git stash store (aunque usar git stash store no es estrictamente necesario) Nota debido a El entorno en el que estoy trabajando en mi script está escrito en PHP en lugar de bash
#!/php/php
<?php
$files = array();
$stash = array();
exec(''git stash create -q'', $stash);
$do_stash = !(empty($stash) || empty($stash[0]));
if($do_stash) {
exec(''git stash store ''.$stash[0]); //store the stash (does not tree state like git stash save does)
exec(''git stash show -p | git apply --reverse''); //remove working tree changes
exec(''git diff --cached | git apply''); //re-add indexed (ready to commit) changes to working tree
}
//exec(''git stash save -q --keep-index'', $stash);
exec(''git diff-index --cached --name-only HEAD'', $files );
// dont redirect stderr to stdin, we will get the errors twice, redirect it to dev/null
if ( PHP_OS == ''WINNT'' )
$redirect = '' 2> NUL'';
else
$redirect = '' 2> /dev/null'';
$exitcode = 0;
foreach( $files as $file ) {
if ( !preg_match(''//.php$/i'', $file ) )
continue;
exec(''php -l '' . escapeshellarg( $file ) . $redirect, $output, $return );
if ( !$return ) // php -l gives a 0 error code if everything went well
continue;
$exitcode = 1; // abort the commit
array_shift( $output ); // first line is always blank
array_pop( $output ); // the last line is always "Errors parsing httpdocs/test.php"
echo implode("/n", $output ), "/n"; // an extra newline to make it look good
}
if($do_stash) {
exec(''git reset --hard -q'');
exec(''git stash apply --index -q'');
exec(''git stash drop -q'');
}
exit( $exitcode );
?>
php script adaptado desde aquí http://blog.dotsamazing.com/2010/04/ask-git-to-check-if-your-codes-are-error-free/