Que fait GIT PUSH exactement?

Je n'arrive pas à trouver une bonne explication de ce.

Je sais ce que fait git pull :

1) A fetch , c'est-à-dire que toutes les validations supplémentaires du serveur sont copiées dans le repo local et que le pointeur de brancheorigin/master se déplace vers la fin de la chaîne de validation

2) fusion de la origin/master dans le maître branche, la maître branche pointeur de déplacement pour le nouvellement créé s'engager, tandis que le origine/maître pointeur rester en place.

Je suppose que git push fait quelque chose de très similaire, mais je ne sais pas avec certitude. Je crois qu'il fait l'un d'entre eux, ou quelque chose de similaire, ou autre chose (?):

  • copie tous les commits locaux et y effectue une fusion (l'inverse de ce que fait git pull); mais dans ce cas, le serveur n'a pas ma branche master locale, donc je ne peux pas voir ce qui fusionne

Ou

  • fusionne mon Master branche dans leorigin/master , en poussant le commit résultant sur le serveur et en le reliant à côté du commit final existant, en déplaçant également lemaster du serveur; cela ne semble pas correct car monorigin/master local n'est pas synchronisé avec celui du serveur.

J'utilise actuellement git pour les opérations de base, donc je vais bien, mais je veux bien comprendre ces internes.

24
demandé sur MD XF 2014-09-24 01:30:16

4 réponses

En supposant que vous comprenez déjà le modèle "objects" de git (vos commits et fichiers et ainsi de suite ne sont que des "objets dans la base de données git", avec des objets "lâches"-ceux qui ne sont pas emballés pour économiser de l'espace-stockés dans .git/objects/12/34567... et autres)...

Vous avez raison: git fetch récupère les objets " ils " (origin, dans ce cas) ont ce que vous n'avez pas, et y colle des étiquettes: origin/master et autres. Plus précisément, votre git appelle le leur sur Internet-téléphone (ou tout autre moyen de transport approprié) et demande: quoi branches avez-vous, et quels sont les identifiants de validation? Ils ont master et L'ID est 1234567..., donc votre git demande 1234567... et tous les autres objets nécessaires que vous n'avez pas déjà, et fait pointer votre origin/master vers commit object 1234567....

La partie de git push qui est symétrique ici est la suivante: votre git appelle leur git sur le même téléphone Internet comme d'habitude, mais cette fois, au lieu de leur poser des questions sur leurs branches, votre git leur parle de vos branches et vos objets Git repository, puis dit: "Que diriez-vous de vous amener à définir votre master à 56789ab...?"

Leur git regarde les objets que vous avez envoyés (le nouveau commit 56789ab... et tous les autres objets que vous avez qu'ils n'ont pas, qu'ils auraient besoin de le prendre). Leur git considère alors la demande de définir leur master à 56789ab....

Comme Chris K a déjà répondu , Il n'y a pas de fusion ici: votre git propose simplement que leur git remplacez leur master par ce nouvel ID de validation. C'est à leur git de décider d'autoriser cela.

Si "ils "(qui qu'ils soient) n'ont pas mis en place de règles spéciales, la règle par défaut que Git utilise ici est très simple: l'écrasement est autorisé si la modification est une "avance rapide". Il a une caractéristique supplémentaire: l'écrasement est également autorisé si la modification est effectuée avec le drapeau "force" défini. Ce n'est généralement pas une bonne idée de définir le drapeau force ici, comme règle par défaut, "seuls les plus rapides", c'est généralement l' droit la règle.

La question évidente ici est: qu'est-ce qu'une avance rapide? Nous y arriverons dans un moment; d'abord, je dois développer un peu les étiquettes, ou les "références" pour être plus formel.

Références de Git

Dans git, une branche ou une balise, ou même des choses comme la stash et HEAD sont toutes des références . La plupart d'entre eux se trouvent dans .git/refs/, un sous-répertoire du répertoire git. (Quelques références de haut niveau, y compris HEAD, sont justes dans .git lui-même.) Toute une référence est, est un fichier1 contenant un identifiant SHA-1 comme 7452b4b5786778d5d87f5c90a94fab8936502e20. Les identifiants SHA-1 sont encombrants et impossibles à mémoriser, nous utilisons donc des noms, comme v2.1.0 (une balise dans ce cas, la version 2.1.0 de git elle-même) pour les sauvegarder pour nous.

Certaines références sont-ou du moins sont destinées à être-totalement statiques. La balise v2.1.0 ne doit jamais se référer à autre chose que L'ID SHA-1 ci-dessus. Mais certaines références sont plus dynamiques. Plus précisément, vos propres branches locales, comme master, sont des cibles mobiles. Un cas particulier, HEAD, n'est même pas une cible à part entière: il contient généralement le nom de la branche moving-target. Il y a donc une exception pour les références "indirectes": HEAD contient généralement la chaîne ref: refs/heads/master, ou ref: refs/heads/branch, ou quelque chose dans ce sens; et git n'applique pas (et ne peut pas) appliquer une règle" never change " pour les références. Les Branches en particulier changent beaucoup.

Comment savez-vous si un la référence est censée changer? Eh bien, beaucoup de ceci est juste par convention: les branches se déplacent et les balises ne le font pas. mais vous devriez alors demander: comment savez-vous si une référence est une branche, ou une balise, ou quoi?

Espaces de noms des références: refs/heads/, refs/tags/, etc.

Autres que les références spéciales de haut niveau, toutes les références de git sont dans refs/ Comme nous l'avons déjà noté ci-dessus. Dans le répertoire refs/ (ou" dossier " si vous êtes sous Windows ou Mac), cependant, nous pouvons avoir toute une collection de les sous-répertoires. Git a, à ce stade, quatre sous-répertoires bien définis: refs/heads/ contient toutes vos branches, refs/tags/ contient toutes vos balises, refs/remotes/ contient toutes vos "branches de suivi à distance", et refs/notes/ contient les "notes" de git (que je vais ignorer ici car elles deviennent un peu compliquées).

Depuis tous vos branches sont dans refs/heads/, git peut dire que ceux-ci devraient être autorisés à modifier, et depuis tous vos balises sont dans refs/tags/, git peut dire qu'ils ne devraient pas.

Mouvement Automatique des branches

Lorsque vous effectuez une nouvelle validation, et que vous êtes sur une branche comme master, git déplace automatiquement la référence. Votre nouveau commit est créé avec son "commit parent" étant la branche précédente, et une fois que votre nouveau commit est sauvegardé en toute sécurité, git Change master pour contenir l'ID dunouveau commit . En d'autres termes, il s'assure que la branche name , la référence dans le sous-répertoire heads, pointe toujours vers le tip-most s'engager.

(en fait, la branche, au sens d'une collection de commits qui fait partie du graphique de commit stocké dans le référentiel, est une structure de données créée à partir des commits dans le référentiel. Sa seule connexion avec la branche name est que le commit tip de la branche elle-même est stocké dans l'étiquette de référence avec ce nom. Ceci est important plus tard, si et quand les noms de branche sont modifiés ou effacés à mesure que le référentiel augmente beaucoup plus de commits. Pour l'instant c'est juste quelque chose à garder à l'esprit: il y a une différence entre le "Conseil de branche", qui est l'endroit où le "nom de branche" pointe, et le branch-as-a-subset-of-commit-DAG. C'est un peu regrettable que git ait tendance à regrouper ces différents concepts sous un seul nom, "branch".)

Qu'est-ce que est exactement une avance rapide?

Habituellement, vous voyez "avance rapide" dans le contexte de la fusion, souvent avec la fusion effectuée comme deuxième étape dans un git pull. Mais en fait, "avance rapide" est en fait un propriété d'un déplacement d'étiquette .

Dessinons un petit graphique de commit. Les petits nœuds o représentent des commits, et chacun a une flèche pointant vers la gauche, vers la gauche et vers le haut ou vers la gauche et vers le bas (ou dans un cas, deux flèches) vers son parent (ou ses parents). Pour pouvoir faire référence à trois par nom, je vais leur donner des noms de lettres majuscules au lieu de o. En outre, cette œuvre basée sur des personnages n'a pas de flèches, vous devez donc les imaginer; rappelez-vous simplement qu'ils pointent tous vers la gauche ou vers la gauche, tout comme les trois noms.

            o - A   <-- name1
          /
o - o - o - o - B   <-- name2
      \       /
        o - C       <-- name3

Lorsque vous demandez à git de changer une référence, vous lui demandez simplement de coller un nouvel ID de validation dans l'étiquette. Dans ce cas, ces étiquettes vivent dans refs/heads/ et sont donc des noms de branche, donc elles sont supposées pouvoir prendre de nouvelles valeurs.

Si nous disons à git de mettre B dans name1, nous obtenons ceci:

            o - A
          /
o - o - o - o - B   <-- name1, name2
      \       /
        o - C       <-- name3

Notez que commit A a maintenant no name, et le o à gauche de celui-ci n'est trouvé qu'en trouvant A ... ce qui est dur puisque A n'a pas de nom. Commit A a été abandonné, et ces deux commits sont devenus éligibles pour"garbage collection". (Dans git, il y a un "nom fantôme" laissé dans le "reflog", qui garde la branche avec A pendant 30 jours en général. Mais c'est un autre sujet entièrement.)

Qu'en est-il de dire à git de mettre B dans name3? Si nous faisons cela ensuite, nous obtenons ceci:

            o - A
          /
o - o - o - o - B   <-- name1, name2, name3
      \       /
        o - C

Ici, commit C a encore un moyen de le trouver: commencez par B et travaillez bas et gauche, à son autre (deuxième) commit parent, et vous trouvez commit C. Afin de commettre C est pas abandonné.

Mise à Jour name1, comme c'est pas une avance rapide, mais la mise à jour name3 est.

Plus précisément, un reference-change est un" fast forward " si et seulement si l'objet-généralement un commit - que la référence utilisée pour pointer vers est toujours accessible en commençant par lenew place et en travaillant en arrière, le long de tous les possibles en arrière des chemins. En termes de graphique, c'est une avance rapide si l'ancien nœud est un ancêtre du nouveau.

Faire un {[67] } être une avance rapide, en fusionnant

Branch-name fast-forwards se produit lorsque la seule chose que vous faites est d'ajouter de nouveaux commits; mais aussi lorsque, Si vous avez ajouté de nouveaux commits, vous avez également fusionné-dans les nouveaux commits ajoutés par quelqu'un d'autre. C'est-à-dire, supposons que votre repo ait ceci, après avoir fait un nouveau commit:

             o   <-- master
           /
...- o - o       <-- origin/master

À ce stade, déplacer origin/master "vers le haut et droit" serait une avance rapide. Cependant, quelqu'un d'autre arrive et met à jour l'autre repo (origin), donc vous faites un git fetch et obtenez un nouveau commit d'eux. Votre git déplace votre étiquette origin/master (dans une opération d'avance rapide sur votre repo, comme il arrive):

             o   <-- master
           /
...- o - o - o   <-- origin/master

À ce stade, déplacer origin/master vers master ne serait pas une avance rapide, car elle abandonnerait ce nouveau commit.

Cependant, Vous pouvez faire un git merge origin/master opération afin de faire un nouveau commit sur votre master, avec deux ID de validation parent. Étiquetons celui-ci M (pour la fusion):

             o - M  <-- master
           /   /
...- o - o - o   <-- origin/master

Vous pouvez maintenant git push vers origin et demandez-leur de mettre leur master-qui que vous appelez origin/master-égal à votre (nouveau) M, parce que pour {en[91]}, c'est maintenant une avance rapide de l'opération!

Notez que vous pouvez également faire un git rebase, mais laissons cela pour un autre stackoverflow affichage. :-)


1en fait, les références git commencent toujours comme des fichiers individuels dans divers sous-répertoires, mais si une référence n'est pas mise à jour pendant longtemps, elle a tendance à être "emballée" (avec toutes les autres références principalement statiques) dans un seul fichier plein de références emballées. C'est juste une optimisation qui permet de gagner du temps, et la clé ici n'est pas de dépendre de l'implémentation exacte, mais plutôt d'utiliser les commandes rev-parse et update-ref de git pour extraire le SHA-1 actuel d'une référence, ou mettre à jour une référence pour contenir un nouveau SHA-1.

46
répondu torek 2017-05-23 12:18:09

Ma description la plus simple est, push suffit de faire ce qui suit: (en supposant que vous faites git push origin master )

  • Copiez les commits locaux qui n'existent pas dans le repo distant dans le repo distant
  • déplacez l'origine / maître (à la fois dans votre Git local et dans le Git distant) pour pointer vers le même commit local/maître
  • Push ne fusionne pas

Cependant , il vérifiera si votre local / master est basé sur l'origine / master. Conceptuellement, cela signifie dans le graphe git, à partir de local/master, vous pouvez revenir directement à origin / master (pas l'origine / master de votre Git local, mais le master sur le repo distant) en déplaçant uniquement "vers le bas", ce qui signifie qu'aucune modification n'a été apportée au repo distant avant votre push. Sinon push sera rejeté

3
répondu palazzo train 2014-09-24 04:55:04

Il n'effectue qu'une copie, pas de fusion.

Plus précisément, il copie les parties du magasin d'objets qui se trouvent dans le repo/branche local et qui sont manquantes du côté distant. Cela inclut, commit objets, refs, arbres et blobs.

Les balises

Sont une exception notable, elles nécessitent l'inclusion de l'indicateur --tags.

Le blog suivant, git est plus simple que vous le pensez a plus de détails.

3
répondu Chris K 2014-09-24 08:16:44

La réponse technique, chargée de jargon, de le manuel est la suivante:

git push "met à jour les refs distants en utilisant des refs locaux, lors de l'envoi objets nécessaires pour compléter les références données."

Donc, fondamentalement, il copie des informations, afin de s'assurer que votre télécommande est à jour avec votre repo local. Mais quelles sont les références, et quels sont les objets? Paraphrasant le manuel:

  • Ref saisie manuelle sont des fichiers qui "magasin le SHA-1 valeur [d'un objet, comme un commit] sous un nom simple afin que vous puissiez utiliser ce pointeur plutôt que la valeur SHA-1 brute " [pour trouver le contenu qui lui est associé]. Vous pouvez les voir en naviguant vers des répertoires comme .git/refs/heads/<branch name>, ou .git/refs/remotes/origin/<branch name> dans votre repo.

  • Les objets (Entrée manuelle ) incluent les commits, les arbres, les blobs et les balises (dont les derniers ne sont pas poussés par défaut). Par exemple, en citant Mark Longair de une autre réponse SO , " un commit enregistre le contenu exact du code source à ce moment-là avec la date, le nom de l'auteur et les références aux commits parents".

Ainsi, lorsque vous git push, git utilise des références locales (créées en tapant git commit) pour mettre à jour des fichiers équivalents sur la télécommande, mettant ainsi à jour les pointeurs vers les validations les plus récentes, puis tout nouveau contenu que vous avez créé est copié dans le système de git en tant qu'objets, étiquetés avec des métadonnées

Comme une illustration supplémentaire de ce qu'est un ref, ici dans L'API Github docs Ils montrent des exemples de résultats JSON d'appels API demandant des refs dans un repo donné. Cela pourrait vous aider à comprendre comment les différentes informations se rapportent les unes aux autres.

1
répondu Aidan Miles 2017-05-23 12:26:14