¿Cómo determino programáticamente si hay cambios no confirmados?

226

En un Makefile, me gustaría realizar ciertas acciones si hay cambios no confirmados (ya sea en el árbol de trabajo o en el índice). ¿Cuál es la forma más limpia y eficiente de hacer eso? Un comando que sale con un valor de retorno de cero en un caso y distinto de cero en el otro sería adecuado para mis propósitos.

Puedo ejecutar git statusy canalizar la salida grep, pero siento que debe haber una mejor manera.

Daniel Stutzbach
fuente

Respuestas:

289

ACTUALIZACIÓN : el OP Daniel Stutzbach señala en los comentarios que este simple comando git diff-indexfuncionó para él:

git update-index --refresh 
git diff-index --quiet HEAD --

( Nornagon menciona en los comentarios que, si hay archivos que se han tocado, pero cuyo contenido es el mismo que en el índice, deberá ejecutarlo git update-index --refreshantes git diff-index; de lo contrario diff-index, informará incorrectamente que el árbol está sucio)

Luego puede ver " ¿Cómo verificar si un comando tuvo éxito? " Si lo está utilizando en un script bash:

git diff-index --quiet HEAD -- || echo "untracked"; // do something about it

Nota: como se ha comentado por Anthony Sottile

git diff-index HEAD ...fallará en una rama que no tiene confirmaciones (como un repositorio recién inicializado).
Una solución que he encontrado esgit diff-index $(git write-tree) ...

Y haridsvseñala en los comentarios que git diff-filesen un nuevo archivo no lo detecta como una diferencia.
El enfoque más seguro parece ser ejecutar git addprimero en la especificación del archivo y luego usar git diff-indexpara ver si se agregó algo al índice antes de ejecutarse git commit.

git add ${file_args} && \
git diff-index --cached --quiet HEAD || git commit -m '${commit_msg}'

Y 6502 informes en los comentarios:

Un problema con el que me topé es que git diff-indexdirá que hay diferencias cuando de hecho no hay ninguna, excepto las marcas de tiempo de los archivos.
Ejecutar git diffuna vez resuelve el problema (sorprendentemente, en git diffrealidad cambia el contenido del sandbox, lo que significa aquí .git/index)

Estos problemas de marca de tiempo también pueden ocurrir si git se está ejecutando en Docker .


Respuesta original:

"Programáticamente" significa nunca confiar en los comandos de porcelana .
Siempre confíe en los comandos de plomería .

Consulte también " Verificación de un índice sucio o archivos sin seguimiento con Git " para alternativas (como git status --porcelain)

Puede inspirarse en la nueva " require_clean_work_treefunción " que está escrita mientras hablamos ;) (principios de octubre de 2010)

require_clean_work_tree () {
    # Update the index
    git update-index -q --ignore-submodules --refresh
    err=0

    # Disallow unstaged changes in the working tree
    if ! git diff-files --quiet --ignore-submodules --
    then
        echo >&2 "cannot $1: you have unstaged changes."
        git diff-files --name-status -r --ignore-submodules -- >&2
        err=1
    fi

    # Disallow uncommitted changes in the index
    if ! git diff-index --cached --quiet HEAD --ignore-submodules --
    then
        echo >&2 "cannot $1: your index contains uncommitted changes."
        git diff-index --cached --name-status -r --ignore-submodules HEAD -- >&2
        err=1
    fi

    if [ $err = 1 ]
    then
        echo >&2 "Please commit or stash them."
        exit 1
    fi
}
VonC
fuente
12
El principio de "fontanería vs. porcelana para scripting" es una lección que Jakub Narębski me mencionó repetidamente: " ¿Cómo enumerar todos los registros del proyecto actual en git? ", " Git: changelog día a día ", ...
VonC
18
Tras hacer clic en algunos de los enlaces que usted sugiere, encontré lo que estaba buscando: git diff-index --quiet HEAD.
Daniel Stutzbach
11
@DanielStutzbach: Eso podría fallar si tiene un archivo llamado HEADen el directorio de trabajo. Mejor uso git diff-index --quiet HEAD --.
David Ongaro
77
Y, sin embargo, el manual en git status --helpestados: - 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 todas las versiones de Git e independientemente de la configuración del usuario. Ver abajo para más detalles.
Ed Randall
77
@VonC que realmente no tiene sentido. De esta manera, puedes torcer todo en reversa. --la porcelana te da la impresión de que se romperá pronto. Si no es así, debería llamarse fontanería, no porcelana. El uso de --porcelain hace que su script no se rompa, lo que NO lo convierte en un script de porcelana ;-). Si quisieras que tu script se rompiera, no deberías usar --porcelain !!. Por lo tanto, esto es completamente incomprensible y desilusiona a todos.
Xennex81
104

Si bien las otras soluciones son muy completas, si quieres algo realmente rápido y sucio, prueba algo como esto:

[[ -z $(git status -s) ]]

Simplemente comprueba si hay algún resultado en el resumen de estado.

Nepthar
fuente
77
funciona para mi. use -n para el inverso (tiene cambios), por ejemplo, `if [[-n $ (git status -s)]]; entonces ... fi`
aaron
Esto funciona, pero ¿puedes decir qué [[ ... ]]está haciendo realmente la sintaxis? Nunca había visto algo así antes.
GMA
2
@EM, el código de retorno de git statusse ignora en esta prueba. Solo mira la salida. Echa un vistazo a esta página relacionada fiesta por detalles [, [[y como prueba de obras en bash.
Nepthar
2
Esta es la respuesta casi correcta, pero para el script es mejor usar el --porcelainparámetro como se muestra aquí
Mariusz Pawelski
2
Es posible que desee utilizar git status -s -uallpara incluir archivos sin seguimiento.
barfuin
59

git diff --exit-codedevolverá un valor distinto de cero si hay algún cambio; git diff --quietEs lo mismo sin salida. Como desea verificar el árbol de trabajo y el índice, use

git diff --quiet && git diff --cached --quiet

O

git diff --quiet HEAD

Cualquiera de los dos le dirá si hay cambios no confirmados que se organizan o no.

Josh Lee
fuente
66
Esos no son equivalentes. El comando git diff --quite HEADúnico solo le dirá si el árbol de trabajo está limpio, no si el índice está limpio. Por ejemplo, si filese cambió entre HEAD ~ y HEAD, después de git reset HEAD~ -- fileeso, seguirá saliendo de 0 aunque haya cambios por etapas presentes en el índice (wt == HEAD, pero index! = HEAD).
Chris Johnsen
2
Advertencia, esto no capturará los archivos eliminados del área de preparación con git rm, AFAICS.
RMN
24
Los archivos nuevos (sin seguimiento) no son detectados por git diff --quiet && git diff --cached --quiet.
4LegsDrivenCat
17

Ampliando la respuesta de @ Nepthar:

if [[ -z $(git status -s) ]]
then
  echo "tree is clean"
else
  echo "tree is dirty, please commit changes before running this"
  exit
fi
Travis Reeder
fuente
1
Esto es bueno; Lo uso para confirmar automáticamente archivos individuales mediante pruebas $(git status -s "$file")y luego en la elsecláusulagit add "$file"; git commit -m "your autocommit process" "$file"
toddkaufmann
Si usted hace que git status -sun git status --porcelain ; git clean -ndlugar, directorios junk estar recubiertos también en este caso, que son invisibles a git status.
ecmanaut
4

Como se señaló en otra respuesta, tan simple como dicho comando es suficiente:

git diff-index --quiet HEAD --

Si omite los dos últimos guiones, el comando fallará si tiene un archivo llamado HEAD.

Ejemplo:

#!/bin/bash
set -e
echo -n "Checking if there are uncommited changes... "
trap 'echo -e "\033[0;31mFAILED\033[0m"' ERR
git diff-index --quiet HEAD --
trap - ERR
echo -e "\033[0;32mAll set!\033[0m"

# continue as planned...

Advertencia: este comando ignora los archivos no rastreados.

sanmai
fuente
2
Como se señaló en los comentarios a esa respuesta, esto no detecta archivos recientemente agregados
minexew
No, detecta recién agregado a los archivos de índice. Acabo de revisarlo.
sanmai
Ver pregunta Los archivos no rastreados no son cambios . git addy git cleanal rescate
sanmai
4

Creé algunos prácticos alias de git para enumerar archivos sin clasificar y en etapas:

git config --global alias.unstaged 'diff --name-only'
git config --global alias.staged 'diff --name-only --cached'

Entonces puedes hacer cosas como:

[[ -n "$(git unstaged)" ]] && echo unstaged files || echo NO unstaged files
[[ -n "$(git staged)" ]] && echo staged files || echo NO staged files

Puede hacerlo más legible creando un script en algún lugar de su PATHllamada git-has:

#!/bin/bash
[[ $(git "$@" | wc -c) -ne 0 ]]

Ahora los ejemplos anteriores se pueden simplificar para:

git has unstaged && echo unstaged files || echo NO unstaged files
git has staged && echo staged files || echo NO staged files

Para completar, aquí hay alias similares para archivos no rastreados e ignorados:

git config --global alias.untracked 'ls-files --exclude-standard --others'
git config --global alias.ignored 'ls-files --exclude-standard --others --ignored'
stk
fuente
2

Con python y el paquete GitPython:

import git
git.Repo(path).is_dirty(untracked_files=True)

Devuelve True si el repositorio no está limpio

Pablo
fuente
Esto evitó algunos de los problemas de "marca de tiempo" mencionados en otros comentarios
Jason
1
Tenga en cuenta que GitPython solo está usando la CLI de git también. Si configuras LOGLEVEL=DEBUG, verás todos los comandos de Popen que usa para ejecutarsegit diff
Jason
-3

Aquí está la mejor y más limpia manera.

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
44
No, este no es el mejor. git statuses un comando de 'porcelana'. No use comandos de porcelana en los scripts, ya que pueden cambiar entre versiones de git. En su lugar, use los comandos de 'plomería'.
Spuder
3
Creo que si lo actualizaste para usarlo git status --porcelain(lo que está destinado para este propósito: un formato estable que puedes analizar en un script), posiblemente también con -z (¿separado por nulo en lugar de nueva línea?), Podrías hacer algo útil con esta idea . @ codyc4321 ver stackoverflow.com/questions/6976473/… para más detalles
msouth