Problème 1 : commits
Obtenir la liste des s'engage n'est que modérément difficile, car vous devez surtout exécuter des git rev-list
. Il existe toutefois des cas particuliers. En tant que la documentation sur les githooks dit :
Les informations sur ce qui doit être poussé sont fournies sur l'entrée standard du crochet sous la forme de lignes :
<local ref> SP <local sha1> SP <remote ref> SP <remote sha1> LF
Par exemple, si la commande git push origin master:foreign
était exécuté, le crochet recevrait une ligne comme la suivante :
refs/heads/master 67890 refs/heads/foreign 12345
bien que les SHA-1 complets de 40 caractères soient fournis. Si le référent étranger n'existe pas encore, l'option <remote SHA-1>
sera de 40 0
. Si une référence doit être supprimée, l'option <local ref>
sera fournie sous la forme (delete)
et le <local SHA-1>
sera de 40 0
. Si le commit local a été spécifié par quelque chose d'autre qu'un nom qui peut être développé (comme HEAD~, ou un SHA-1), il sera fourni tel qu'il a été donné à l'origine.
Vous devez donc lire chaque ligne stdin et l'analyser en ses composants, puis décider :
- S'agit-il d'un branche une mise à jour ? (Par exemple, la référence à distance a-t-elle la forme
refs/heads/*
(comme une correspondance globale ?) Si ce n'est pas le cas, voulez-vous vérifier les commits de toute façon ?
- La référence est-elle créée ou détruite ? Dans l'affirmative, que devez-vous faire ?
- Disposez-vous de l'objet spécifié par le hachage étranger (si ce n'est pas le cas) ? et la poussée réussit - elle peut très bien échouer - ce qui entraînera l'abandon d'un certain nombre d'objets commit, mais vous ne pouvez pas savoir lesquels. De plus, vous ne pouvez pas lister correctement les commits locaux qui seront transférés : vous savez ce que vous leur demandez de faire fixer mais vous ne savez pas quels sont les engagements que vous et eux ont en commun puisque vous ne pouvez pas parcourir leur historique).
En supposant que vous ayez déterminé les réponses à ces questions - disons qu'elles sont "no", "skip it", et "locally reject pushes that are not analyzable" - nous passons à la liste des commits, et ce n'est que le résultat de :
git rev-list remotehash..localhash
que vous pourriez utiliser :
proc = subprocess.Popen(['git', 'rev-list',
'{}..{}'.format(remotehash, localhash)], stdout=subprocess.PIPE)
text = proc.stdout.read()
if proc.wait():
raise ... # some appropriate error, as Git failed here
if not isinstance(text, str): # i.e., if python3
text = text.decode('utf-8') # convert bytes to str
lines = text.split('\n')
# now work with each commit hash
Il convient de noter que cette git rev-list
l'appel échouera (sortie avec un statut non nul) si le hash distant ou local est all-zeros, ou si le hash distant correspond à un objet qui n'existe pas dans votre référentiel local (vous pouvez le vérifier en utilisant la commande git rev-parse --verify --quiet
et vérifier le statut de retour, ou peut-être utiliser l'échec ici comme indication que vous ne pouvez pas vérifier les commits, bien qu'il y ait d'autres options lors de la création d'une nouvelle branche).
Notez que vous devez exécuter le programme git rev-list
pour chaque référence qui doit être mis à jour. Il est possible que les mêmes commits, ou un sous-ensemble des mêmes commits, soient envoyés pour différentes références. Par exemple :
git push origin HEAD:br1 HEAD:br2 HEAD~3:br3
demanderait que la mise à jour à distance de trois branches soit effectuée br1
à travers br3
, réglage br1
y br2
au même commit que HEAD
et la mise en place br3
à l'engagement trois pas en arrière de HEAD
. Nous ne savons pas (et ne pouvons pas savoir) quels commits sont vraiment nouveaux - le crochet de pré-réception à l'autre bout pourrait le déterminer, mais nous ne le pouvons pas - mais si les commits de l'autre bout sont vraiment nouveaux, nous ne pouvons pas le savoir. br1
y br2
sont tous deux mis à jour à partir de HEAD~3
a HEAD
et la fonction br3
est mis à jour à partir de HEAD~2
à l'envers a HEAD~3
, au maximum les commits HEAD~1
à travers HEAD
peut être nouvelle. Que vous souhaitiez vérifier HEAD~2
puisqu'il est désormais susceptible d'apparaître sur le site de l'Union européenne. br1
y br2
dans l'autre référentiel (même s'il est était déjà sur br3
), c'est aussi à vous de décider.
Problème 2 : fichiers
Vous avez maintenant un problème plus difficile à résoudre. Vous avez mentionné dans un éditorial que :
EDIT : Je pense qu'il est également important de noter que les mêmes fichiers peuvent être modifiés par plusieurs commits qui sont sur le point d'être poussés - il serait donc bon de ne pas valider le même fichier plusieurs fois.
Chaque livraison à envoyer a un aperçu complet du référentiel. En d'autres termes, chaque livraison contient tous les fichiers. Je n'ai aucune idée de la validation que vous avez l'intention d'effectuer, mais vous avez raison : si vous envoyez, disons, six livraisons au total, il est très probable que la plupart des fichiers des six livraisons soient identiques et que seuls quelques fichiers soient modifiés. Cependant, les fichiers foo.py
pourrait être modifié lors de l'engagement 1234567
(en ce qui concerne les 1234567
), et a ensuite modifié le commit parent de à nouveau dans l'engagement fedcba9
et vous devriez probablement vérifier les deux versions .
En outre, lorsqu'un engagement est un fusionner s'engager, il a (au moins) deux parents différents. Devriez-vous vérifier un fichier s'il est différent de soit parent ? Ou bien faut-il le vérifier uniquement s'il diffère de à la fois parents, indiquant qu'il contient des modifications provenant des "deux côtés" de la fusion ? S'il ne contient que des modifications d'un seul côté, le fichier est probablement "pré-vérifié" par les vérifications qui ont eu lieu pour le commit qui se trouve sur la page d'accueil. autres et, par conséquent, il n'est peut-être pas nécessaire de procéder à une nouvelle vérification (bien que cela dépende évidemment du type de vérification).
(Dans le cas d'une fusion de pieuvres, c'est-à-dire d'une fusion avec plus de deux parents, cette question est beaucoup plus difficile à résoudre).
Il est relativement facile de voir quels fichiers sont modifié dans un commit, par rapport à son ou ses parents : il suffit de lancer git diff-tree
avec les options appropriées (notamment, -r
pour rechercher des sous-arbres du commit). Le format de sortie par défaut est tout à fait analysable par la machine, bien que vous puissiez ajouter -z
pour le rendre plus facile à manipuler directement dans Python. Si vous les faites une à la fois - ce qui est tout à fait possible - vous voudrez probablement aussi utiliser --no-commit-id
de sorte que vous n'ayez pas besoin de lire et de sauter l'en-tête du commit.
C'est à vous de décider si vous voulez activer la détection des renommages et, si oui, à partir de quel seuil. En fonction, encore une fois, de ce que vous faites précisément pour vérifier les fichiers, il est souvent préférable de ne pas activer la détection des renommages : ainsi, vous "verrez" un fichier renommé comme une suppression de l'ancien chemin et un ajout du nouveau chemin.
La sortie de git diff-tree -r --no-commit-id
sur une livraison particulière ressemble à ceci :
:000000 100644 0000000000000000000000000000000000000000 b0b4c36f9780eaa600232fec1adee9e6ba23efe5 A Documentation/RelNotes/2.13.0.txt
:100755 100755 6a208e92bf30c849028268b5fca54b902f671bbd 817d1cf7ef2a2a99ab11e5a88a27dfea673fec79 M GIT-VERSION-GEN
:120000 120000 d09c3d51093ac9e4da65e8a127b17ac9023520b5 125bf78f3b9ed2f1444e1873ed02cce9f0f4c5b8 M RelNotes
Les ID de hachage sont les anciens et les nouveaux hachages de blob ; les codes de lettres et les noms de chemin sont tels que documentés. Vous pouvez ensuite récupérer le contenu du fichier en utilisant git cat-file -p
sur le nouvel ID de hachage. Si votre Git est suffisamment récent, vous pouvez même obtenir n'importe quel .gitattributes
-Le filtrage basé sur l'utilisation et la conversion de fin de ligne sont appliqués par l'ajout des éléments suivants --textconv
--filters
y --path=<path>
(ou en utilisant le chemin d'accès au fichier ainsi que l'ID du commit, au lieu de --path=...
pour nommer le hachage de l'objet à extraire). Vous pouvez également utiliser la forme de l'objet stocké dans le référentiel, si les filtres ne sont pas importants.
En fonction de ce que vous vérifiez, vous pouvez avoir besoin d'extraire l'intégralité du commit dans un arbre de travail temporaire (par exemple, un analyseur statique peut vouloir exécuter n'importe quel import
s.) Dans ce cas, vous pouvez tout aussi bien exécuter git checkout
à l'aide de l'outil GIT_INDEX_FILE
(transmettez-la par l'intermédiaire de la variable d'environnement subprocess
comme d'habitude) pour spécifier un fichier d'index temporaire afin que l'index principal ne soit pas perturbé. Spécifiez un arbre de travail alternatif avec --work-tree=
ou par l'intermédiaire du GIT_WORK_TREE
variable d'environnement. Dans tous les cas, la variable d'environnement git diff-tree
vous indiquera les fichiers qui ont été modifié et doit donc être vérifiée (vous pouvez utiliser la fonction shutil.rmtree
pour se débarrasser de l'arbre de travail temporaire une fois les tests terminés).
Si vous souhaitez vérifier les commits de fusion, prêtez une attention particulière à l'élément description de Différences combinées fait pour les fusions car ils nécessiteront un traitement quelque peu différent (ou la division de la fusion avec des -m
).
Edit : un peu de code pour montrer ce que je veux dire
Voici un petit bout de code qui permet d'obtenir toutes les données et de montrer que chaque engagement est ajouté à chaque branche étrangère. Notez que la liste des ajouté sera vide si les commits sont seulement en cours de traitement. supprimée . Ce document n'a été que très légèrement testé et n'est pas censé être robuste, maintenable, de bon style, etc.
import re, subprocess, sys
lines = sys.stdin.read().splitlines()
for line in lines:
localref, localhash, foreignref, foreignhash = line.split()
if not foreignref.startswith('refs/heads/'):
print('skip {}'.format(foreignref))
continue
if re.match('0+$', localhash):
print('deleting {}, do nothing'.format(foreignref))
continue
if re.match('0+$', foreignhash):
print('creating {}, too hard for now'.format(foreignref))
continue
proc = subprocess.Popen(['git', 'rev-parse', '--quiet', '--verify',
foreignhash],
stdout=subprocess.PIPE)
_ = proc.stdout.read()
status = proc.wait()
if status:
print('we do not have {} for {}, try '
'git fetch'.format(foreignhash, foreignref))
# can try to run git fetch here ourselves, but for now:
continue
print('sending these commits for {}:'.format(foreignref))
subprocess.call(['git', 'rev-list', '{}..{}'.format(localhash, foreignhash)])