Buscar un índice sucio o archivos sin seguimiento con Git

243

¿Cómo puedo verificar si tengo cambios no confirmados en mi repositorio git?

  1. Cambios agregados al índice pero no confirmados
  2. Archivos sin seguimiento

de un guión?

git-status parece que siempre devuelve cero con git versión 1.6.4.2.

Robert Munteanu
fuente
3
El estado de git devolverá 1 si hay archivos modificados sin etapas. Pero en general, encuentro que las herramientas de git no son particularmente completas con el estado de devolución. Por ejemplo, git diff devuelve 0 si hay o no diferencias.
intuido
11
@intuited: si necesita diffindicar la presencia o ausencia de diferencias en lugar de la ejecución exitosa del comando, entonces debe usar --exit-codeo --quiet. Los comandos git son generalmente muy consistentes con la devolución de un código de salida cero o distinto de cero para indicar el éxito del comando.
CB Bailey
1
@ Charles Bailey: Hola genial, me perdí esa opción. ¡Gracias! Supongo que nunca lo necesité para hacer eso, o probablemente lo habría buscado en la página del manual. Me alegro de que me haya corregido :)
intuido
1
Robert: este es un tema muy confuso para la comunidad (no ayuda que la 'porcelana' se use de manera diferente en diferentes contextos). La respuesta que ha seleccionado ignora una respuesta votada 2.5 veces más alta que es más robusta y sigue el diseño git. ¿Has visto la respuesta de @ChrisJ?
Mike
1
@Robert - Espero que consideres cambiar tu respuesta aceptada. El que seleccionó se basa en comandos git 'porcelain' más frágiles; la respuesta correcta real (también con 2x los votos a favor) se basa en los comandos correctos de 'plomería' de git. Esa respuesta correcta fue originalmente de Chris Johnsen. Menciono esto, ya que hoy es la tercera vez que tengo que referir a alguien a esta página, pero no puedo señalar la respuesta, he explicado por qué la respuesta aceptada es subóptima / límite incorrecta. ¡Gracias!
Mike

Respuestas:

173

Gran momento! Escribí una publicación de blog sobre esto exactamente hace unos días, cuando descubrí cómo agregar información de estado de git a mi mensaje.

Esto es lo que hago:

  1. Para estado sucio:

    # Returns "*" if the current git branch is dirty.
    function evil_git_dirty {
      [[ $(git diff --shortstat 2> /dev/null | tail -n1) != "" ]] && echo "*"
    }
  2. Para archivos no rastreados (Observe la --porcelainbandera a la git statusque le da una buena salida analizable):

    # Returns the number of untracked files
    
    function evil_git_num_untracked_files {
      expr `git status --porcelain 2>/dev/null| grep "^??" | wc -l` 
    }

Aunque git diff --shortstates más conveniente, también puede usar git status --porcelainpara ensuciar archivos sucios:

# Get number of files added to the index (but uncommitted)
expr $(git status --porcelain 2>/dev/null| grep "^M" | wc -l)

# Get number of files that are uncommitted and not added
expr $(git status --porcelain 2>/dev/null| grep "^ M" | wc -l)

# Get number of total uncommited files
expr $(git status --porcelain 2>/dev/null| egrep "^(M| M)" | wc -l)

Nota: 2>/dev/nullFiltra los mensajes de error para que pueda usar estos comandos en directorios que no sean git. (Simplemente regresarán 0para el recuento de archivos).

Editar :

Aquí están las publicaciones:

Agregar información de estado de Git a su indicador de terminal

Indicador de Shell mejorado habilitado para Git

0xfe
fuente
8
Vale la pena señalar que la finalización de git bash viene con una función de shell para hacer más o menos lo que estás haciendo con tu prompt __git_ps1. Muestra los nombres de las sucursales, incluido el tratamiento especial si está en el proceso de un rebase, am-apply, merge o bisect. Y puede configurar la variable de entorno GIT_PS1_SHOWDIRTYSTATEpara obtener un asterisco para los cambios no organizados y más para los cambios organizados. (Creo que también puede obtenerlo para indicar archivos no rastreados y darle algo de git-describesalida)
Cascabel
8
Una advertencia: git diff --shortstatdará un falso negativo si los cambios ya están en el índice.
Marko Topolnik
44
git status --porcelaines preferible, porque git diff --shortstatno capturará los archivos vacíos recién creados. Puede probarlo en cualquier árbol de trabajo limpio:touch foo && git diff --shortstat
Campadrenalin
77
NO: la porcelana significa que la salida es para humanos y se rompe fácilmente. vea la respuesta de @ChrisJohnsen, que usa correctamente las opciones estables y fáciles de usar.
Mike
77
@mike de la página de manual de estado de git sobre la opción de porcelana: "Proporcione la salida en un formato fácil de analizar para los scripts. Esto es similar a la salida corta, pero se mantendrá estable en las versiones de Git e independientemente de la configuración del usuario. "
itsadok
399

La clave para "guionar" Git de manera confiable es usar los comandos de "plomería".

Los desarrolladores tienen cuidado al cambiar los comandos de plomería para asegurarse de que proporcionen interfaces muy estables (es decir, una combinación dada de estado del repositorio, stdin, opciones de línea de comandos, argumentos, etc.) producirá la misma salida en todas las versiones de Git donde el comando / opción existe). Se pueden introducir nuevas variaciones de salida en los comandos de plomería a través de nuevas opciones, pero eso no puede presentar ningún problema para los programas que ya se han escrito en versiones anteriores (no estarían usando las nuevas opciones, ya que no existían (o al menos eran no utilizado) en el momento en que se escribió el guión).

Desafortunadamente, los comandos de Git 'cotidianos' son los comandos de 'porcelana', por lo que la mayoría de los usuarios de Git pueden no estar familiarizados con los comandos de plomería. La distinción entre comando de porcelana y plomería se realiza en la página principal de git (vea las subsecciones tituladas Comandos de alto nivel (porcelana) y comandos de bajo nivel (plomería) .


Para conocer los cambios no comprometidos, es probable que necesite git diff-index(compare el índice (y tal vez los bits del árbol de trabajo rastreados) con algún otro árbol (por ejemplo HEAD)), tal vez git diff-files(compare el árbol de trabajo con el índice) y posiblemente git ls-files(enumere los archivos; por ejemplo, lista sin seguimiento , archivos no ignorados).

(Tenga en cuenta que en los comandos a continuación, HEAD --se usa en lugar de HEADporque, de lo contrario, el comando falla si hay un archivo llamado HEAD).

Para verificar si un repositorio ha efectuado cambios (aún no confirmados) use esto:

git diff-index --quiet --cached HEAD --
  • Si sale con, 0entonces no hubo diferencias ( 1significa que hubo diferencias).

Para verificar si un árbol de trabajo tiene cambios que podrían organizarse:

git diff-files --quiet
  • El código de salida es el mismo que para git diff-index( 0== sin diferencias; 1== diferencias).

Para verificar si la combinación del índice y los archivos rastreados en el árbol de trabajo tienen cambios con respecto a HEAD:

git diff-index --quiet HEAD --
  • Esto es como una combinación de los dos anteriores. Una diferencia importante es que aún informará "sin diferencias" si tiene un cambio por etapas que ha "deshecho" en el árbol de trabajo (regresado a los contenidos que están en HEAD). En esta misma situación, los dos comandos separados devolverían informes de "diferencias presentes".

También mencionaste archivos sin seguimiento. Puede significar "sin seguimiento y sin ignorar", o puede significar simplemente "sin seguimiento" (incluidos los archivos ignorados). De cualquier manera, git ls-fileses la herramienta para el trabajo:

Para "sin seguimiento" (incluirá archivos ignorados, si están presentes):

git ls-files --others

Para "sin seguimiento y sin ignorar":

git ls-files --exclude-standard --others

Mi primer pensamiento es simplemente verificar si estos comandos tienen salida:

test -z "$(git ls-files --others)"
  • Si sale con, 0entonces no hay archivos sin seguimiento. Si sale con, 1entonces hay archivos sin seguimiento.

Existe una pequeña posibilidad de que esto traduzca las salidas anormales de los git ls-filesinformes "sin archivos no rastreados" (ambos dan como resultado salidas distintas de cero del comando anterior). Una versión un poco más robusta podría verse así:

u="$(git ls-files --others)" && test -z "$u"
  • La idea es la misma que la del comando anterior, pero permite git ls-filesque se propaguen errores inesperados . En este caso, una salida distinta de cero podría significar "hay archivos sin seguimiento" o podría significar que se produjo un error. Si desea que los resultados de "error" se combinen con el resultado de "sin archivos sin seguimiento", use test -n "$u"(donde salir de 0significa "algunos archivos sin seguimiento" y no cero significa error o "sin archivos sin seguimiento").

Otra idea es usar --error-unmatchpara provocar una salida distinta de cero cuando no hay archivos sin seguimiento. Esto también corre el riesgo de combinar "no archivos no rastreados" (salir 1) con "se produjo un error" (salir no es cero, pero probablemente 128). Pero verificar los códigos de salida 0vs. 1vs. no cero es probablemente bastante robusto:

git ls-files --others --error-unmatch . >/dev/null 2>&1; ec=$?
if test "$ec" = 0; then
    echo some untracked files
elif test "$ec" = 1; then
    echo no untracked files
else
    echo error from ls-files
fi

git ls-filesPuede tomar cualquiera de los ejemplos anteriores --exclude-standardsi desea considerar solo los archivos no rastreados y no ignorados.

Chris Johnsen
fuente
55
Me gustaría señalar que git ls-files --othersproporciona archivos locales sin seguimiento, mientras que la git status --porcelainrespuesta aceptada proporciona todos los archivos sin seguimiento que se encuentran en el repositorio de git. No sé cuál de estos quería el póster original, pero la diferencia entre ambos es interesante.
Eric O Lebigot
1
@phunehehe: debe proporcionar una especificación de ruta --error-unmatch. Pruebe (por ejemplo) git ls-files --other --error-unmatch --exclude-standard .(tenga en cuenta el período final, se refiere al cwd; ejecute esto desde el directorio de nivel superior del árbol de trabajo).
Chris Johnsen
8
@phs: es posible que tenga que hacer git update-index -q --refreshantes diff-indexde evitar algunos "falsos positivos" causados ​​por la falta de coincidencia de la información estadística (2).
Chris Johnsen
1
¡Me mordió la git update-indexnecesidad! Esto es importante si algo toca los archivos sin realizar modificaciones.
Desnudable
2
@RobertSiemer Por "local" me refería a los archivos de su directorio actual , que podrían estar debajo de su repositorio principal de git. La --porcelainsolución enumera todos los archivos no rastreados que se encuentran en todo el repositorio de git (menos los archivos ignorados por git), incluso si está dentro de uno de sus subdirectorios.
Eric O Lebigot
139

Asumiendo que estás en git 1.7.0 o posterior ...

Después de leer todas las respuestas en esta página y experimentar un poco, creo que el método que da con la combinación correcta de corrección y brevedad es:

test -n "$(git status --porcelain)"

Si bien git permite una gran cantidad de matices entre lo que se rastrea, ignora, no rastrea pero no se ignora, y así sucesivamente, creo que el caso de uso típico es para automatizar scripts de compilación, donde desea detener todo si su pago no está limpio.

En ese caso, tiene sentido simular lo que haría el programador: escribir git statusy mirar la salida. Pero no queremos confiar en que aparezcan palabras específicas, por lo que utilizamos el --porcelainmodo introducido en 1.7.0; cuando está habilitado, un directorio limpio no genera resultados.

Luego, usamos test -npara ver si hubo algún resultado o no.

Este comando devolverá 1 si el directorio de trabajo está limpio y 0 si hay cambios que confirmar. Puede cambiar el -na a -zsi desea lo contrario. Esto es útil para encadenar esto a un comando en un script. Por ejemplo:

test -z "$(git status --porcelain)" || red-alert "UNCLEAN UNCLEAN"

Esto efectivamente dice "o no hay cambios para hacer o activar una alarma"; esta frase podría ser preferible a una declaración if dependiendo del guión que esté escribiendo.

benzado
fuente
1
Para mí, todos los otros comandos estaban dando resultados diferentes en la misma representación entre Linux y Windows. Este comando me dio la misma salida en ambos.
Adarsha
66
Gracias por responder la pregunta y no divagar una y otra vez, nunca proporcionar una respuesta clara.
Nates el
Para una secuencia de comandos de implementación manual, combine esto con test -n "$(git diff origin/$branch)", para ayudar a evitar que se permitan los compromisos locales en una implementación
Erik Aronesty
14

Una implementación de la respuesta de VonC :

if [[ -n $(git status --porcelain) ]]; then echo "repo is dirty"; fi
Dean Rather
fuente
8

Eché un vistazo a algunas de estas respuestas ... (y tuve varios problemas con * nix y windows, que era un requisito que tenía) ... descubrí que lo siguiente funcionó bien ...

git diff --no-ext-diff --quiet --exit-code

Para verificar el código de salida en * nix

echo $?   
#returns 1 if the repo has changes (0 if clean)

Para verificar el código de salida en la ventana $

echo %errorlevel% 
#returns 1 if the repos has changes (0 if clean) 

Procedente de https://github.com/sindresorhus/pure/issues/115 Gracias a @paulirish en esa publicación por compartir

mlo55
fuente
4

¿Por qué no encapsular ' git statuscon un script que:

  • analizará la salida de ese comando
  • devolverá el código de error apropiado en función de lo que necesita

De esa manera, puede usar ese estado 'mejorado' en su script.


Como 0xfe menciona en su excelente respuesta , git status --porcelaines instrumental en cualquier solución basada en script

--porcelain

Proporcione la salida en un formato estable y fácil de analizar para los scripts.
Actualmente, esto es idéntico --short output, pero se garantiza que no cambiará en el futuro, por lo que es seguro para los scripts.

VonC
fuente
Porque soy vago, probablemente. Pensé que había algo incorporado para esto, ya que parece un caso de uso bastante frecuente.
Robert Munteanu
Publiqué una solución basada en su sugerencia, aunque no estoy extremadamente satisfecho con ella.
Robert Munteanu
4

Una posibilidad de bricolaje, actualizada para seguir la sugerencia de 0xfe

#!/bin/sh
exit $(git status --porcelain | wc -l) 

Como señaló Chris Johnsen , esto solo funciona en Git 1.7.0 o posterior.

Robert Munteanu
fuente
66
El problema con esto es que no puede esperar de forma confiable la cadena 'directorio de trabajo limpio' en futuras versiones. La bandera --porcelain estaba pensada para analizar, por lo que una mejor solución sería: salir $ (estado de git --porcelain | wc -l)
0xfe
@ 0xfe: ¿sabe cuándo --porcelainse agregó la bandera? No funciona con 1.6.4.2.
Robert Munteanu
@Robert: inténtalo git status --shortentonces.
VonC
3
git status --porcelainy git status --shortambos fueron introducidos en 1.7.0. --porcelainFue introducido específicamente para permitir git status --shortvariar su formato en el futuro. Por git status --shortlo tanto, sufriría el mismo problema que git status(la salida puede cambiar en cualquier momento ya que no es un comando de 'plomería').
Chris Johnsen
@ Chris, gracias por la información de fondo. He actualizado la respuesta para reflejar la mejor manera de hacerlo a partir de Git 1.7.0.
Robert Munteanu
2

A menudo necesitaba una forma simple de fallar una compilación si al final de la ejecución hay archivos rastreados modificados o archivos no rastreados que no se ignoran.

Esto es bastante importante para evitar casos en los que las compilaciones producen restos.

Hasta ahora, el mejor comando que terminé usando parece:

 test -z "$(git status --porcelain | tee /dev/fd/2)" || \
     {{ echo "ERROR: git unclean at the end, failing build." && return 1 }}

Puede parecer un poco complejo y agradecería si alguien encuentra una variante en corto que esto mantiene el comportamiento deseado:

  • sin salida y acceso al código de salida si todo está en orden
  • salir del código 1 si falla
  • mensaje de error en stderr explicando por qué falla
  • muestra la lista de archivos que causan la falla, stderr nuevamente.
Sorin
fuente
2

La respuesta de @ eduard-wirch fue bastante completa, pero como quería verificar ambas al mismo tiempo, aquí está mi variante final.

        set -eu

        u="$(git ls-files --others)"
        if ! git diff-index --name-only --quiet HEAD -- || [ -z "${u:-}" ]; then
            dirty="-dirty"
        fi

Cuando no se ejecuta usando set -e o equivalente, podemos hacer un u="$(git ls-files --others)" || exit 1(o devolver si esto funciona para una función usada)

Por lo tanto, untracked_files solo se establece si el comando se ejecuta correctamente.

después de lo cual, podemos verificar ambas propiedades y establecer una variable (o lo que sea).

Oliver
fuente
1

Esta es una variación más respetuosa con cáscara para averiguar si algún existen archivos sin seguimiento en el repositorio:

# Works in bash and zsh
if [[ "$(git status --porcelain 2>/dev/null)" = *\?\?* ]]; then
  echo untracked files
fi

Esto no bifurca un segundo proceso, grepy no necesita una verificación de si está en un repositorio git o no. Lo cual es útil para avisos de shell, etc.

docwhat
fuente
1

También puedes hacer

git describe --dirty

. Agregará la palabra "-dirty" al final si detecta un árbol de trabajo sucio. De acuerdo a git-describe(1):

   --dirty[=<mark>]
       Describe the working tree. It means describe HEAD and appends <mark> (-dirty by default) if
       the working tree is dirty.

. Advertencia: los archivos no rastreados no se consideran "sucios" porque, como dice la página de manual, solo le importa el árbol de trabajo.

Linus Arver
fuente
0

Es posible que haya una mejor combinación de respuestas de este hilo .. pero esto funciona para mí ... para su .gitconfig's [alias]sección ...

          # git untracked && echo "There are untracked files!"
untracked = ! git status --porcelain 2>/dev/null | grep -q "^??"
          # git unclean && echo "There are uncommited changes!"
  unclean = ! ! git diff --quiet --ignore-submodules HEAD > /dev/null 2>&1
          # git dirty && echo "There are uncommitted changes OR untracked files!"
    dirty = ! git untracked || git unclean
Alex Gray
fuente
0

La prueba automática más simple que uso para detectar el estado sucio = cualquier cambio, incluidos los archivos sin seguimiento :

git add --all
git diff-index --exit-code HEAD

NOTA:

  • Sin add --all diff-indexno nota archivos no rastreados.
  • Normalmente, ejecuto git resetdespués de probar el código de error para eliminar todo el escenario.
uvsmtid
fuente
La pregunta es específicamente "desde un script" ... no es una buena idea cambiar el índice solo para probar el estado sucio.
ScottJ
@ScottJ, cuando resuelve el problema, no todos son necesariamente estrictos sobre si el índice se puede modificar o no. Considere el caso del trabajo automatizado que trata sobre fuentes de parches automáticos con número de versión y haga una etiqueta; todo lo que necesita para asegurarse es que absolutamente ninguna otra modificación local se interponga en el camino (independientemente de si están en el índice o no). Hasta ahora, esta fue una prueba confiable para cualquier cambio, incluidos los archivos no rastreados .
uvsmtid
-3

Aquí está la mejor y más limpia manera. La respuesta seleccionada no funcionó para mí por alguna razón, no detectó los cambios organizados que eran archivos nuevos que no se confirmaron.

function git_dirty {
    text=$(git status)
    changed_text="Changes to be committed"
    untracked_files="Untracked files"

    dirty=false

    if [[ ${text} = *"$changed_text"* ]];then
        dirty=true
    fi

    if [[ ${text} = *"$untracked_files"* ]];then
        dirty=true
    fi

    echo $dirty
}
codyc4321
fuente