tipos tag remove remote etiquetas eliminar delete create crear and git githooks git-stash

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:

  1. Añadir // Step 1 a a.java
  2. git add .
  3. Añadir // Step 2 a a.java
  4. git commit
    1. git stash -q --keep-index # Stash cambios
    2. Ejecutar pruebas
    3. 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/