Vérifier un index sale ou des fichiers non suivis avec Git

243

Comment puis-je vérifier si j'ai des modifications non validées dans mon référentiel git:

  1. Modifications ajoutées à l'index mais non validées
  2. Fichiers non suivis

à partir d'un script?

git-status semble toujours retourner zéro avec la version 1.6.4.2 de git.

Robert Munteanu
la source
3
git status renverra 1 s'il existe des fichiers modifiés non mis en scène. Mais en général, je trouve que les outils git ne sont pas particulièrement approfondis avec le statut de retour. EG git diff renvoie 0 qu'il y ait ou non des différences.
intuition
11
@intuited: Si vous devez diffindiquer la présence ou l'absence de différences plutôt que l'exécution réussie de la commande, vous devez utiliser --exit-codeou --quiet. Les commandes git sont généralement très cohérentes avec le retour d'un code de sortie nul ou non nul pour indiquer le succès de la commande.
CB Bailey
1
@Charles Bailey: Hé bien, j'ai raté cette option. Merci! Je suppose que je n'en ai jamais vraiment eu besoin pour cela, sinon j'aurais probablement parcouru la page de manuel pour cela. Heureux que vous m'ayez corrigé :)
intuition
1
robert - c'est un sujet très déroutant pour la communauté (cela n'aide pas que la «porcelaine» soit utilisée différemment dans différents contextes). La réponse que vous avez sélectionnée ignore une réponse votée 2,5 fois plus élevée qui est plus robuste et suit la conception git. Avez-vous vu la réponse de @ChrisJ?
Mike
1
@Robert - J'espère que vous pourriez envisager de modifier votre réponse acceptée. Celui que vous avez sélectionné repose sur des commandes de porcelaine git plus fragiles; la réponse correcte réelle (également avec 2x les votes positifs) repose sur les commandes correctes de plomberie git. Cette réponse correcte a été à l'origine par Chris Johnsen. J'en parle car aujourd'hui, c'est la 3ème fois que je dois référer quelqu'un à cette page, mais je ne peux pas simplement pointer vers la réponse, j'ai expliqué pourquoi la réponse acceptée est sous-optimale / limite incorrecte. Je vous remercie!
Mike

Réponses:

173

Bon timing! J'ai écrit un article de blog à ce sujet il y a quelques jours, quand j'ai compris comment ajouter des informations sur l'état de git à mon invite.

Voici ce que je fais:

  1. Pour le statut sale:

    # Returns "*" if the current git branch is dirty.
    function evil_git_dirty {
      [[ $(git diff --shortstat 2> /dev/null | tail -n1) != "" ]] && echo "*"
    }
  2. Pour les fichiers non suivis (notez l' --porcelainindicateur git statusauquel vous donne une belle sortie analysable ):

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

Bien que ce git diff --shortstatsoit plus pratique, vous pouvez également l'utiliser git status --porcelainpour obtenir des fichiers sales:

# 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)

Remarque: Le 2>/dev/nullfiltre les messages d'erreur afin que vous puissiez utiliser ces commandes sur des répertoires non git. (Ils reviendront simplement 0pour le nombre de fichiers.)

Modifier :

Voici les articles:

Ajout d'informations d'état Git à votre invite de terminal

Amélioration de l'invite du shell compatible avec Git

0xfe
la source
8
Il est à noter que l'achèvement de git bash est livré avec une fonction shell pour faire à peu près ce que vous faites avec votre invite - __git_ps1. Il affiche les noms des branches, y compris un traitement spécial si vous êtes en train de rebaser, d'appliquer, de fusionner ou de bissecter. Et vous pouvez définir la variable d'environnement GIT_PS1_SHOWDIRTYSTATEpour obtenir un astérisque pour les modifications non échelonnées et plus pour les modifications échelonnées. (Je pense que vous pouvez également l'obtenir pour indiquer les fichiers non suivis et vous donner une git-describesortie)
Cascabel
8
Un avertissement: git diff --shortstatdonnera un faux négatif si des changements sont déjà dans l'index.
Marko Topolnik
4
git status --porcelainest préférable, car git diff --shortstatn'attrapera pas les fichiers vides nouvellement créés. Vous pouvez l'essayer dans n'importe quel arbre de travail propre:touch foo && git diff --shortstat
Campadrenalin
7
NON - la porcelaine signifie que la sortie est destinée aux humains et se casse facilement !! voir la réponse de @ChrisJohnsen, qui utilise correctement les options stables et conviviales pour les scripts.
Mike
7
@mike de la page de manuel git-status sur l'option porcelain: "Donnez la sortie dans un format facile à analyser pour les scripts. Ceci est similaire à la sortie courte, mais restera stable dans toutes les versions de Git et quelle que soit la configuration de l'utilisateur. "
itsadok
400

La clé d'un «script» fiable de Git est d'utiliser les commandes de «plomberie».

Les développeurs prennent soin lors du changement des commandes de plomberie pour s'assurer qu'ils fournissent des interfaces très stables (c'est-à-dire qu'une combinaison donnée d'état du référentiel, stdin, options de ligne de commande, arguments, etc. produira la même sortie dans toutes les versions de Git où la commande / existe). De nouvelles variations de sortie dans les commandes de plomberie peuvent être introduites via de nouvelles options, mais cela ne peut pas poser de problèmes pour les programmes qui ont déjà été écrits sur des versions plus anciennes (ils n'utiliseraient pas les nouvelles options, car elles n'existaient pas (ou du moins étaient non utilisé) au moment de l’écriture du script).

Malheureusement, les commandes Git «quotidiennes» sont les commandes «porcelaine», donc la plupart des utilisateurs de Git peuvent ne pas être familiers avec les commandes de plomberie. La distinction entre la porcelaine et la commande de plomberie est faite dans la page de manuel principale de git (voir les sous-sections intitulées Commandes de haut niveau (porcelaine) et Commandes de bas niveau (plomberie) .


Pour en savoir plus sur les modifications non validées, vous aurez probablement besoin de git diff-index(comparer l'index (et peut-être des morceaux d'arbre de travail suivis) à d'autres arborescents (par exemple HEAD)), peut-être git diff-files(comparer l'arbre de travail à l'index), et éventuellement git ls-files(lister les fichiers; par exemple, lister les non suivis) , fichiers non ignorés).

(Notez que dans les commandes ci-dessous, HEAD --est utilisé à la place de HEADcar sinon la commande échoue s'il y a un fichier nommé HEAD.)

Pour vérifier si un référentiel a des modifications par étapes (pas encore validées), utilisez ceci:

git diff-index --quiet --cached HEAD --
  • S'il sort avec 0alors il n'y a pas de différences (cela 1signifie qu'il y a des différences).

Pour vérifier si une arborescence de travail comporte des modifications pouvant être mises en scène:

git diff-files --quiet
  • Le code de sortie est le même que pour git diff-index( 0== pas de différences; 1== différences).

Pour vérifier si la combinaison de l'index et des fichiers suivis dans l'arborescence de travail a des changements par rapport à HEAD:

git diff-index --quiet HEAD --
  • C'est comme une combinaison des deux précédents. Une différence principale est qu'il signalera toujours «aucune différence» si vous avez un changement par étapes que vous avez «annulé» dans l'arborescence de travail (retourné au contenu qui s'y trouve HEAD). Dans cette même situation, les deux commandes distinctes renverraient toutes les deux des rapports de «différences présentes».

Vous avez également mentionné les fichiers non suivis. Vous pourriez signifier «non suivi et non ignoré», ou simplement «non suivi» (y compris les fichiers ignorés). Quoi qu'il en soit, git ls-filesest l'outil pour le travail:

Pour «non suivi» (comprendra les fichiers ignorés, le cas échéant):

git ls-files --others

Pour «non suivi et non ignoré»:

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

Ma première pensée est de simplement vérifier si ces commandes ont une sortie:

test -z "$(git ls-files --others)"
  • S'il 0se termine avec alors il n'y a pas de fichiers non suivis. S'il 1se termine avec alors il y a des fichiers non suivis.

Il y a une petite chance que cela se traduise par des sorties anormales de git ls-filesdans «aucun fichier non suivi» (les deux entraînent des sorties non nulles de la commande ci-dessus). Une version un peu plus robuste pourrait ressembler à ceci:

u="$(git ls-files --others)" && test -z "$u"
  • L'idée est la même que la commande précédente, mais elle permet à des erreurs inattendues de git ls-filesse propager. Dans ce cas, une sortie non nulle pourrait signifier «il y a des fichiers non suivis» ou cela pourrait signifier qu'une erreur s'est produite. Si vous souhaitez que les résultats "erreur" soient combinés avec le résultat "pas de fichiers non suivis" à la place, utilisez test -n "$u"(où sortie de 0signifie "certains fichiers non suivis" et non nul signifie erreur ou "pas de fichiers non suivis").

Une autre idée consiste à utiliser --error-unmatchpour provoquer une sortie non nulle lorsqu'il n'y a pas de fichiers non suivis. Cela risque également de confondre «aucun fichier non suivi» (sortie 1) avec «une erreur s'est produite» (sortie non nulle, mais probablement 128). Mais la vérification des codes de sortie 0vs. 1vs non-zéro est probablement assez robuste:

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

N'importe lequel des git ls-filesexemples ci-dessus peut être --exclude-standardutilisé si vous souhaitez considérer uniquement les fichiers non suivis et non ignorés.

Chris Johnsen
la source
5
Je voudrais souligner que git ls-files --othersdonne les fichiers locaux non suivis, tandis que la git status --porcelainréponse acceptée donne tous les fichiers non suivis qui sont sous le référentiel git. Je ne sais pas laquelle de ces affiches originales voulait, mais la différence entre les deux est intéressante.
Eric O Lebigot
1
@phunehehe: Vous devez fournir une pathspec avec --error-unmatch. Essayez (par exemple) git ls-files --other --error-unmatch --exclude-standard .(notez la période de fin, elle fait référence au cwd; exécutez-la à partir du répertoire de niveau supérieur de l'arborescence de travail).
Chris Johnsen
8
@phs: vous devrez peut-être le faire git update-index -q --refreshavant le diff-indexpour éviter certains «faux positifs» causés par des informations stat (2) incompatibles.
Chris Johnsen
1
J'ai été mordu par le git update-indexbesoin! Ceci est important si quelque chose touche les fichiers sans apporter de modifications.
Nakedible
2
@RobertSiemer Par "local", je voulais dire les fichiers sous votre répertoire actuel , qui pourraient être en dessous de son dépôt git principal. La --porcelainsolution répertorie tous les fichiers non suivis trouvés dans l' ensemble du référentiel git (moins les fichiers ignorés par git), même si vous êtes dans l'un de ses sous-répertoires.
Eric O Lebigot
140

En supposant que vous utilisez git 1.7.0 ou une version ultérieure ...

Après avoir lu toutes les réponses sur cette page et expérimenté, je pense que la méthode qui atteint la bonne combinaison d'exactitude et de concision est:

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

Bien que git permette beaucoup de nuances entre ce qui est suivi, ignoré, non suivi mais non ignoré, etc., je pense que le cas d'utilisation typique est d'automatiser les scripts de construction, où vous voulez tout arrêter si votre paiement n'est pas propre.

Dans ce cas, il est logique de simuler ce que le programmeur ferait: taper git statuset regarder la sortie. Mais nous ne voulons pas compter sur des mots spécifiques qui apparaissent, nous utilisons donc le --porcelainmode introduit dans 1.7.0; lorsqu'il est activé, un répertoire propre n'entraîne aucune sortie.

Ensuite, nous utilisons test -npour voir s'il y a eu une sortie ou non.

Cette commande renverra 1 si le répertoire de travail est propre et 0 s'il y a des modifications à valider. Vous pouvez changer le -nen a -zsi vous voulez le contraire. Ceci est utile pour chaîner cela à une commande dans un script. Par exemple:

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

Cela indique en fait "soit qu'il n'y a pas de changement à faire ou déclencher une alarme"; ce one-liner peut être préférable à une instruction if selon le script que vous écrivez.

benzado
la source
1
Pour moi, toutes les autres commandes donnaient des résultats différents sur le même représentant entre Linux et Windows. Cette commande m'a donné la même sortie dans les deux.
Adarsha
6
Merci d'avoir répondu à la question et de ne pas vous promener indéfiniment, sans jamais fournir de réponse claire.
NateS
Pour un script de déploiement manuel, combinez-le avec test -n "$(git diff origin/$branch)", pour aider à empêcher les commits locaux d'être autorisés dans un déploiement
Erik Aronesty
14

Une implémentation de la réponse de VonC :

if [[ -n $(git status --porcelain) ]]; then echo "repo is dirty"; fi
Dean plutôt
la source
8

J'ai regardé quelques-unes de ces réponses ... (et j'ai eu divers problèmes sur * nix et windows, ce qui était une exigence que j'avais) ... j'ai trouvé que ce qui suit fonctionnait bien ...

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

Pour vérifier le code de sortie dans * nix

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

Pour vérifier le code de sortie dans la fenêtre $

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

Provenant de https://github.com/sindresorhus/pure/issues/115 Merci à @paulirish sur ce post pour le partage

mlo55
la source
4

Pourquoi ne pas encapsuler ' git statusavec un script qui:

  • analysera la sortie de cette commande
  • renverra le code d'erreur approprié en fonction de ce dont vous avez besoin

De cette façon, vous pouvez utiliser ce statut «amélioré» dans votre script.


Comme 0xfe le mentionne dans son excellente réponse , joue ungit status --porcelain rôle dans toute solution basée sur un script

--porcelain

Donnez la sortie dans un format stable et facile à analyser pour les scripts.
Actuellement, c'est identique à --short output, mais il est garanti qu'il ne changera pas à l'avenir, ce qui le rend sûr pour les scripts.

VonC
la source
Parce que je suis paresseux, probablement. Je pensais qu'il y avait une fonction intégrée pour cela, car cela semble un cas d'utilisation assez souvent rencontré.
Robert Munteanu
J'ai posté une solution basée sur votre suggestion, même si je n'en suis pas extrêmement satisfaite.
Robert Munteanu
4

Une possibilité de bricolage, mise à jour pour suivre la suggestion de 0xfe

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

Comme l'a noté Chris Johnsen , cela ne fonctionne que sur Git 1.7.0 ou plus récent.

Robert Munteanu
la source
6
Le problème avec cela est que vous ne pouvez pas vous attendre de manière fiable à la chaîne «répertoire de travail propre» dans les futures versions. Le drapeau --porcelain était destiné à l'analyse, donc une meilleure solution serait: exit $ (git status --porcelain | wc -l)
0xfe
@ 0xfe - Savez-vous quand le --porcelaindrapeau a été ajouté? Ne fonctionne pas avec 1.6.4.2.
Robert Munteanu
@Robert: essayez git status --shortalors.
VonC
3
git status --porcelainet git status --shortont tous deux été introduits dans 1.7.0. --porcelainIl a été introduit spécifiquement pour permettre git status --shortde varier son format à l'avenir. Donc, git status --shortsubirait le même problème que git status(la sortie peut changer à tout moment car ce n'est pas une commande de «plomberie»).
Chris Johnsen
@Chris, merci pour les informations générales. J'ai mis à jour la réponse pour refléter la meilleure façon de le faire depuis Git 1.7.0.
Robert Munteanu
2

J'avais assez souvent besoin d'un moyen simple d'échouer une génération si à la fin de l'exécution il y a des fichiers suivis modifiés ou des fichiers non suivis qui ne sont pas ignorés.

Ceci est très important pour éviter le cas où les builds produisent des restes.

Jusqu'à présent, la meilleure commande que j'ai utilisée ressemble à:

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

Cela peut sembler un peu complexe et j'apprécierais que quelqu'un trouve une variante raccourcie qui maintienne le comportement souhaité:

  • aucune sortie et code de sortie de réussite si tout est en ordre
  • quitter le code 1 s'il échoue
  • message d'erreur sur stderr expliquant pourquoi il échoue
  • afficher la liste des fichiers à l'origine de l'échec, stderr à nouveau.
Sorin
la source
2

@ eduard-wirch était assez complète, mais comme je voulais vérifier les deux en même temps, voici ma dernière variante.

        set -eu

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

Lorsque vous n'exécutez pas à l'aide de set -e ou équivalent, nous pouvons à la place faire un u="$(git ls-files --others)" || exit 1(ou retourner si cela fonctionne pour une fonction utilisée)

Donc, untracked_files, n'est défini que si la commande réussit correctement.

après quoi, nous pouvons vérifier les deux propriétés et définir une variable (ou autre).

Oliver
la source
1

Il s'agit d'une variante plus conviviale pour découvrir si des fichiers non suivis existent dans le référentiel:

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

Cela ne débouche pas sur un deuxième processus, grepet n'a pas besoin de vérifier si vous êtes dans un référentiel git ou non. Ce qui est pratique pour les invites de shell, etc.

docwhat
la source
1

Vous pouvez aussi faire

git describe --dirty

. Il ajoutera le mot "-dirty" à la fin s'il détecte un arbre de travail sale. Selon 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.

. Attention: les fichiers non suivis ne sont pas considérés comme "sales", car, comme l'indique la page de manuel, il ne se soucie que de l'arbre de travail.

Linus Arver
la source
0

Il peut y avoir une meilleure combinaison des réponses de ce fil .. mais cela fonctionne pour moi ... pour .gitconfigde » [alias]section ...

          # 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
la source
0

Le test automatique le plus simple que j'utilise pour détecter l' état sale = toutes les modifications, y compris les fichiers non suivis :

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

REMARQUE:

  • Sans add --all diff-indexne remarque pas les fichiers non suivis.
  • Normalement, je lance git resetaprès avoir testé le code d'erreur pour tout remettre en scène.
uvsmtid
la source
La question est spécifiquement "à partir d'un script" ... ce n'est pas une bonne idée de changer l'index juste pour tester l'état sale.
ScottJ
@ScottJ, quand il résout le problème, tout le monde n'est pas nécessairement strict quant à savoir si l'index peut être modifié ou non. Considérez le cas du travail automatisé qui concerne les sources de correctifs automatiques avec le numéro de version et créez une balise - tout ce dont vous avez besoin pour vous assurer est qu'aucune autre modification locale ne reste sur le chemin (qu'elles soient dans l'index ou non). Jusqu'à présent, c'était un test fiable pour toutes les modifications, y compris les fichiers non suivis .
uvsmtid
-3

Voici le meilleur moyen, le plus propre. La réponse sélectionnée n'a pas fonctionné pour moi pour une raison quelconque, elle n'a pas détecté les modifications par étapes qui étaient de nouveaux fichiers qui n'étaient pas validés.

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
la source