Comment utiliser git bisect?

438

J'ai lu des articles disant que git bisectc'est génial. Cependant, je ne suis pas natif et je ne comprends pas pourquoi c'est génial.

Quelqu'un pourrait-il démontrer avec un exemple de code:

  1. Comment l'utiliser?
  2. Est-ce juste comme ça svn blame?
IAdapter
la source
@ 01: Comme le dit le livre git: effectuez une recherche par force brute à travers l'historique du projet .
2011
7
Pas si /// brute :-), il utilise la recherche binaire.
cojocar
"git blame" est similaire à "svn blame". "git bisect" est une chose complètement différente
William Pursell
Pour ce que ça vaut, il y a aussi une bonne description de la bissect dans Pro Git . La réponse de Sylvain est une autre bonne idée. Si après avoir regardé tout cela, vous ne comprenez toujours pas, je vous suggère de poser une question plus spécifique. Les questions générales engendrent des réponses générales.
Cascabel

Réponses:

655

L'idée derrière git bisectest d'effectuer une recherche binaire dans l'histoire pour trouver une régression particulière. Imaginez que vous ayez l'historique de développement suivant:

... --- 0 --- 1 --- 2 --- 3 --- 4* --- 5 --- current

Vous savez que votre programme ne fonctionne pas correctement lors de la currentrévision et qu'il fonctionnait lors de la révision 0. Ainsi , la régression a probablement été introduite dans l' un des commits 1, 2, 3, 4, 5, current.

Vous pouvez essayer de vérifier chaque commit, de le construire, de vérifier si la régression est présente ou non. S'il y a un grand nombre de validations, cela peut prendre du temps. Il s'agit d'une recherche linéaire. Nous pouvons faire mieux en faisant une recherche binaire. C'est ce que fait la git bisectcommande. À chaque étape, il essaie de réduire de moitié le nombre de révisions potentiellement mauvaises.

Vous utiliserez la commande comme ceci:

$ git stash save
$ git bisect start
$ git bisect bad
$ git bisect good 0
Bisecting: 2 revisions left to test after this (roughly 2 steps)
[< ... sha ... >] 3

Après cette commande, gitva extraire un commit. Dans notre cas, ce sera commit 3. Vous devez créer votre programme et vérifier si la régression est présente ou non. Vous devrez également indiquer gitl'état de cette révision avec git bisect badsi la régression est présente ou git bisect goodsi elle ne l'est pas.

Supposons que la régression ait été introduite dans commit 4. Ensuite, la régression n'est pas présente dans cette révision, et nous le lui disons git.

$ make
$ make test
... ... ...
$ git bisect good
Bisecting: 0 revisions left to test after this (roughly 1 step)
[< ... sha ... >] 5

Il extraira ensuite un autre commit. Soit 4ou 5(car il n'y a que deux commits). Supposons qu'il ait choisi 5. Après une construction, nous testons le programme et voyons que la régression est présente. Nous lui disons ensuite git:

$ make
$ make test
... ... ...
$ git bisect bad
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[< ... sha ... >] 4

Nous testons la dernière révision, 4. Et puisque c'est celui qui a introduit la régression, on lui dit git:

$ make
$ make test
... ... ...
$ git bisect bad
< ... sha ... > is the first bad commit
< ... commit message ... >

Dans cette situation simple, nous ne devions tester 3 versions ( 3, 4, 5) au lieu de 4 (1 , 2, 3, 4). C'est une petite victoire, mais c'est parce que notre histoire est si petite. Si la plage de recherche est de N validations, nous devrions nous attendre à tester 1 + log2 N validations avec git bisectau lieu de grossièrement N / 2 validations avec une recherche linéaire.

Une fois que vous avez trouvé le commit qui a introduit la régression, vous pouvez l'étudier pour trouver le problème. Une fois cela fait, vous utilisez git bisect resetpour tout remettre à l'état d'origine avant d'utiliser la git bisectcommande.

Sylvain Defresne
la source
5
Je vais comparer ici, c'est une bonne explication de la bissecte mais ne m'aide vraiment pas à l'utiliser. Surtout depuis que j'ai réussi à trouver un bon commit et que je suis sur cette branche maintenant. De cette position, cette explication n'est d'aucune aide. Comment puis-je spécifier la mauvaise branche sans la vérifier, par exemple
PandaWood
4
Vous pouvez utiliser git bisect bad <rev> [<rev>...]pour marquer des révisions spécifiques comme mauvaises (ou bonnes avec git bisect good <rev> [<rev>...]). revpeut être n'importe quel identifiant de révision comme un nom de branche, une balise, un hachage de validation (ou un préfixe unique de hachage de validation), ...
Sylvain Defresne
39
... et une fois que vous avez terminé, vous tapez git bisect resetpour tout remettre sur le commit récent
peetonn
17
L'une des réponses les plus impressionnantes sur pile. Très bien articulé. Je fais ce processus manuellement depuis des années, juste pour choisir un point arbitraire à mi-chemin entre un bon et un mauvais commit, puis à nouveau entre celui-ci et le bon / mauvais selon qu'il était bon / mauvais lui-même. Cela a toujours été une énorme douleur dans le cul et je n'avais jamais entendu parler de cette sous-commande git jusqu'à aujourd'hui ... hahaha
Chev
3
@Nemoden, oui, cela peut, fondamentalement, il peut être utile pour tout type de projet. Il vous suffit de remplacer l'étape "faire un test" par "déployer le site Web et reproduire le problème"
alex.b
159

git bisect run bissect automatique

Si vous avez un ./testscript automatisé qui a le statut de sortie 0 si le test est OK, vous pouvez trouver automatiquement le bogue avec bisect run:

git checkout KNOWN_BAD_COMMIT
git bisect start

# Confirm that our test script is correct, and fails on the bad commit.
./test
# Should output != 0.
echo $?
# Tell Git that the current commit is bad.
git bisect bad

# Same for a known good commit in the past.
git checkout KNOWN_GOOD_COMMIT
./test
# Should output 0.
echo $?
# After this, git automatically checks out to the commit
# in the middle of KNOWN_BAD_COMMIT and KNOWN_GOOD_COMMIT.
git bisect good

# Bisect automatically all the way to the first bad or last good rev.
git bisect run ./test

# End the bisect operation and checkout to master again.
git bisect reset

Cela suppose bien sûr que si le script de test ./testest git tracké, il ne disparaît pas lors d'une validation antérieure lors de la bissection.

J'ai constaté que très souvent, vous pouvez vous en sortir en copiant simplement le script dans l'arborescence de l'arborescence, et éventuellement en jouant avec des PATHvariables de type similaire, et en l'exécutant à partir de là.

Bien sûr, si l'infrastructure de test sur laquelle test dépend dépend des validations plus anciennes, alors il n'y a pas de solution, et vous devrez faire les choses manuellement, en décidant comment tester les validations une par une.

J'ai trouvé cependant que l'utilisation de cette automatisation fonctionne souvent et peut être un énorme gain de temps pour les tests plus lents se trouvant dans votre carnet de tâches, où vous pouvez simplement le laisser fonctionner pendant la nuit, et éventuellement avoir votre bug identifié le lendemain matin, cela vaut la peine l'essayer.

Plus de conseils

Restez sur le premier commit échoué après la bissect au lieu de revenir à master:

git bisect reset HEAD

start+ initiale badet gooden une seule fois:

git bisect start KNOWN_BAD_COMMIT KNOWN_GOOD_COMMIT~

est le même que:

git checkout KNOWN_BAD_COMMIT
git bisect start
git bisect bad
git bisect good KNOWN_GOOD_COMMIT

Voir ce qui a été testé jusqu'à présent (par manuel goodet badou run):

git bisect log

Exemple de sortie:

git bisect log
git bisect start
# bad: [00b9fcdbe7e7d2579f212b51342f4d605e53253d] 9
git bisect bad 00b9fcdbe7e7d2579f212b51342f4d605e53253d
# good: [db7ec3d602db2d994fe981c0da55b7b85ca62566] 0
git bisect good db7ec3d602db2d994fe981c0da55b7b85ca62566
# good: [2461cd8ce8d3d1367ddb036c8f715c7b896397a5] 4
git bisect good 2461cd8ce8d3d1367ddb036c8f715c7b896397a5
# good: [8fbab5a3b44fd469a2da3830dac5c4c1358a87a0] 6
git bisect good 8fbab5a3b44fd469a2da3830dac5c4c1358a87a0
# bad: [dd2c05e71c246f9bcbd2fbe81deabf826c54be23] 8
git bisect bad dd2c05e71c246f9bcbd2fbe81deabf826c54be23
# bad: [c536b1b7242d5fcf92cd87e9a534bedb1c0c9c05] 7
git bisect bad c536b1b7242d5fcf92cd87e9a534bedb1c0c9c05
# first bad commit: [c536b1b7242d5fcf92cd87e9a534bedb1c0c9c0

Afficher les bonnes et les mauvaises références sur git log pour avoir une meilleure idée du temps:

git log --decorate --pretty=fuller --simplify-by-decoration master

Cela ne montre que les validations avec une référence correspondante, ce qui réduit considérablement le bruit, mais inclut les références générées automatiquement de type:

refs/bisect/good*
refs/bisect/bad*

qui nous disent quels engagements nous avons marqués comme bons ou mauvais.

Considérez ce repo de test si vous voulez jouer avec la commande.

L'échec est rapide, le succès est lent

Quelquefois:

  • l'échec se produit rapidement, par exemple l'un des premiers tests échoue
  • le succès prend un certain temps, par exemple les tests réussis échouent, et tous les autres tests dont nous ne nous soucions pas suivent

Pour ces cas, par exemple en supposant que l'échec se produit toujours avec 5 secondes, et si nous sommes paresseux pour rendre le test plus spécifique comme nous le devrions vraiment, nous pouvons utiliser timeoutcomme dans:

#!/usr/bin/env bash
timeout 5 test-command
if [ $? -eq 1 ]; then
  exit 1
fi

Cela fonctionne depuis les timeoutsorties 124tandis que l'échec des test-commandsorties1 .

Statuts de sortie magique

git bisect run est un peu pointilleux sur les états de sortie:

  • tout ce qui est supérieur à 127 fait échouer la bissection avec quelque chose comme:

    git bisect run failed:
    exit code 134 from '../test -aa' is < 0 or >= 128
    

    En particulier, un C assert(0)mène à un SIGABRTet sort avec le statut 134, très ennuyeux.

  • 125 est magique et permet de sauter la course git bisect skip.

    L'intention est d'aider à ignorer les versions cassées pour des raisons indépendantes.

Voir man git-bisectpour les détails.

Donc, vous voudrez peut-être utiliser quelque chose comme:

#!/usr/bin/env bash
set -eu
./build
status=0
./actual-test-command || status=$?
if [ "$status" -eq 125 ] || [ "$status" -gt 127 ]; then
  status=1
fi
exit "$status"

Testé sur git 2.16.1.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
7
Comment git sait-il que votre nouveau test ne disparaîtra pas lorsque vous revenez / bissectez à une révision précédente / mauvaise (qui n'avait pas votre test nouvellement écrit)?
thebjorn
8
@thebjorn vous avez un point: pour autant que je sache, soit le test doit être sur un exécutable externe dans PATH, soit dans un fichier non suivi dans le repo. Dans de nombreux cas, cela est possible: mettez le test dans un fichier séparé, incluez le passe-partout de test nécessaire avec une test_scriptsuite de tests bien conçue + modulaire, et exécutez-le à partir du fichier séparé tout en bissectant. Lorsque vous corrigez, fusionnez le test dans la suite de tests principale.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
@CiroSantilli 六四 事件 法轮功 纳米比亚 威 视 Désolé, j'ai annulé votre modification ci-dessous, car je pense que c'est plus explicatif pour les débutants et que cela ne fait qu'ajouter un autre point à votre réponse (pas une réponse exacte comme la vôtre - mentionnant donc que, son pour ajouter un point)
Nicks
1
Il existe de nombreuses façons de se tromper avec `` git bisect run '' - par exemple, voir comment un bon commit a été annulé par une mauvaise fusion. Il entre, sort, revient et repart et seul le dernier "out" est mauvais. Cependant, vous pouvez toujours faire une 'git bissect' manuelle. Parce que c'est une bissecte, cela ne prend que quelques étapes - par exemple 1024 commits en 10 étapes.
combinateur
1
@combinatorist vous avez raison, cela pourrait échouer. Je trouve bisect runparticulièrement utile lorsque le test prend beaucoup de temps pour se terminer, et je suis presque sûr que le système de test ne cassera pas. De cette façon, je peux simplement le laisser fonctionner en arrière-plan, ou du jour au lendemain s'il prend trop de ressources, sans perdre de temps de changement de contexte cérébral.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
124

TL; DR

Début:

$ git bisect start
$ git bisect bad
$ git bisect good <goodcommit>

Bisecting: X revisions left to test after this (roughly Y steps)

Répéter:

Le problème existe toujours?

  • Oui: $ git bisect bad
  • Non: $ git bisect good

Résultat:

<abcdef> is the first bad commit

Lorsque vous avez terminé:

git bisect reset
Geoffrey Hale
la source
3
Assurez-vous que vous êtes à la racine de votre dépôt git, ou vous obtiendrez un étrange "Vous devez exécuter cette commande à partir du niveau supérieur de l'arborescence de travail." Erreur.
PR Whitehead
J'ai donc git bad sur ma HEAD et git good sur le premier commit, quand l'erreur n'était pas présente. Que faire ensuite? quand le bug n'est pas présent git bissect bon pour passer au prochain commit?
Gobliins
@Gobliins Quand le bogue n'est pas présent, corrigez, git bisect goodpour passer au prochain commit.
Geoffrey Hale
40

Juste pour ajouter un autre point:

Nous pouvons spécifier un nom de fichier ou un chemin d'accès au git bisect startcas où nous saurions que le bogue provient de fichiers particuliers. Par exemple, supposons que nous savions que les modifications qui ont provoqué la régression se trouvaient dans le répertoire com / workingDir, alors nous pouvons exécuter git bisect start com/workingDirCela signifie que seuls les validations qui ont changé le contenu de ce répertoire seront vérifiées, ce qui rend les choses encore plus rapides.

De plus, s'il est difficile de dire si un commit particulier est bon ou mauvais, vous pouvez l'exécuter git bisect skip, ce qui l'ignorera. Étant donné qu'il y a suffisamment d'autres validations, git bisect en utilisera une autre pour affiner la recherche.

Nicks
la source
15

$ git bisect ..basiquement, un outil Git pour le débogage . 'Git Bisect' débogue en passant par les commits précédents depuis votre dernière (connue) de commit. Il utilise la recherche binaire pour parcourir tous ces commits, pour arriver à celui qui a introduit la régression / bug.

$ git bisect start # Démarrage de la bissect

$ git bisect bad # indiquant que le commit actuel (v1.5) a le point de régression / réglage «mauvais»

$ git bisect good v1.0 # en mentionnant le dernier bon commit de travail (sans régression)

Cette mention des points «mauvais» et «bons» aidera git bisect (recherche binaire) à choisir l'élément central (commit v1.3). Si la régression est là lors de la validation v1.3, vous la définirez comme le nouveau «mauvais» point, c'est-à-dire ( Bon -> v1.0 et Mauvais -> v1.3 )

$ git bisect bad

ou de même si la validation v1.3 est exempte de bogues, vous la définissez comme le nouveau «bon point», c'est-à-dire (* bon -> v1.3 et mauvais -> v1.6).

$ git bisect good
Nabeel Ahmed
la source
2

Remarque: les termes goodetbad ne sont pas les seuls que vous pouvez utiliser pour marquer un commit avec ou sans certaine propriété.

Git 2.7 (Q4 2015) a introduit de nouvelles git bisectoptions.

 git bisect start [--term-{old,good}=<term> --term-{new,bad}=<term>]
                  [--no-checkout] [<bad> [<good>...]] [--] [<paths>...]

Avec la documentation ajoutant:

Parfois, vous ne recherchez pas le commit qui a introduit une rupture, mais plutôt un commit qui a provoqué un changement entre un autre "ancien" état et un "nouvel" état .

Par exemple, vous recherchez peut-être le commit qui a introduit un correctif particulier.
Ou vous cherchez peut-être le premier commit dans lequel les noms de fichiers du code source ont finalement tous été convertis en la norme de dénomination de votre entreprise. Ou peu importe.

Dans de tels cas, il peut être très déroutant d'utiliser les termes «bon» et «mauvais» pour désigner «l'état avant le changement» et «l'état après le changement».

Donc, à la place, vous pouvez utiliser les termes " old" et " new", respectivement, à la place de " good" et " bad".
(Mais notez que vous ne pouvez pas mélanger " good" et " bad" avec " old" et " new" en une seule session.)

Dans cette utilisation plus générale, vous fournissez git bisectun " new" commit qui a une propriété et un " old" commit qui n'a pas cette propriété.

Chaque fois que vous git bisectrécupérez un commit, vous testez si ce commit a la propriété:
Si c'est le cas, marquez le commit comme " new"; sinon, marquez-le comme " old".

Une fois la bissection terminée, git bisectsignalera quel engagement a introduit le bien.


Voir commit 06e6a74 , commit 21b55e3 , commit fe67687 (29 juin 2015) par Matthieu Moy ( moy) .
Voir commit 21e5cfd (29 juin 2015) par Antoine Delaite ( CanardChouChinois) .
(Fusionné par Junio ​​C Hamano - gitster- en commit 22dd6eb , 05 oct.2015 )

VonC
la source