Maison  >  Article  >  tutoriels informatiques  >  Les commits Git sont-ils des différences, des instantanés ou un historique ?

Les commits Git sont-ils des différences, des instantanés ou un historique ?

PHPz
PHPzavant
2024-02-19 11:39:421098parcourir

Git 提交是差异、快照还是历史记录?

Il est facile pour moi de comprendre comment les commits Git sont implémentés, mais il est difficile de comprendre comment les autres perçoivent les commits. J'ai donc posé quelques questions à d'autres sur Mastodon.

Que pensez-vous de la soumission Git ?

J'ai mené une enquête très peu scientifique demandant aux gens ce qu'ils pensent des commits Git : s'agit-il d'un instantané, d'une différence ou d'une liste de tous les commits précédents ? (Bien sûr, il est raisonnable de considérer cela comme les trois, mais je suis curieux de connaître le principal

Le résultat est :

  • 51% de différence
  • 42% Instantané
  • 4 % Historique de tous les commits précédents
  • 3% «Autre»

Je suis surpris de voir à quel point les ratios sont proches pour les deux options dans Différence et Instantané. Les gens ont également soulevé des points intéressants mais contradictoires, comme
« Il me semble qu'un commit est une différence, mais je pense qu'il est en fait implémenté sous forme d'instantané » et
« Il me semble qu'un commit est un instantané, mais je pense cela se présente en fait sous la forme d’une différence ». Nous parlerons plus tard de la façon dont la soumission est réellement mise en œuvre.

Avant d’aller plus loin : qu’entend-on par « une différence » ou « un instantané » ?

Quelle est la différence ?

La "différence" dont je parle est probablement assez évidente : la différence est ce que vous obtenez lorsque vous courez git show COMMIT_ID . Par exemple, voici une correction de faute de frappe dans le projet rbspy :

diff --git a/src/ui/summary.rs b/src/ui/summary.rs
index 5c4ff9c..3ce9b3b 100644
--- a/src/ui/summary.rs
+++ b/src/ui/summary.rs
@@ -160,7 +160,7 @@ mod tests {
";
let mut buf: Vec = Vec::new();
-stats.write(&mut buf).expect("Callgrind write failed");
+stats.write(&mut buf).expect("summary write failed");
let actual = String::from_utf8(buf).expect("summary output not utf8");
assert_eq!(actual, expected, "Unexpected summary output");
}

Vous pouvez le voir sur GitHub : https://github.com/rbspy/rbspy/commit/24ad81d2439f9e63dd91cc1126ca1bb5d3a4da5b

Qu'est-ce qu'un instantané ?

Par « instantané », j'entends « tous les fichiers que vous obtenez lorsque vous exécutez git checkout COMMIT_ID ».

Git fait généralement référence à la liste des fichiers soumis comme une « arborescence » (comme une « arborescence de répertoires »), et vous pouvez voir tous les fichiers soumis ci-dessus sur GitHub :

https://github.com/rbspy/rbspy/tree/24ad81d2439f9e63dd91cc1126ca1bb5d3a4da5b (c'est /tree/ 而不是 /commit/)

« Comment Git est implémenté » est-il vraiment la bonne façon de l'expliquer ?

Le conseil le plus courant que j'entends à propos de l'apprentissage de Git est probablement "apprenez simplement comment Git représente les choses en interne, et tout deviendra plus clair". J’aime évidemment beaucoup cette perspective (si vous avez passé du temps à lire ce blog, vous saurez que je l’adore

Mais pour apprendre Git, cela n’a pas fonctionné aussi bien que je l’avais espéré ! Normalement, je commencerais avec enthousiasme à expliquer "D'accord, donc Git
un commit est un instantané, il a un pointeur vers son commit parent, puis une branche est un pointeur vers le commit, alors...", mais j'essaie d'aider Les gens me diront qu'ils n'ont pas vraiment trouvé cette explication utile, qu'ils ne la comprennent toujours pas. J'ai donc examiné d'autres options.

Mais parlons d’abord de la mise en œuvre interne.

Comment Git représente les commits en interne - Snapshot

En interne, Git représente les commits sous forme d'instantanés (il stocke un "arbre" de la version actuelle de chaque fichier). Je suis dans un dépôt Git, où sont vos fichiers ? J'ai écrit à ce sujet dans , mais voici un aperçu très rapide du format interne.

Il s'agit d'une représentation de soumission :

$ git cat-file -p 24ad81d2439f9e63dd91cc1126ca1bb5d3a4da5b
tree e197a79bef523842c91ee06fa19a51446975ec35
parent 26707359cdf0c2db66eb1216bf7ff00eac782f65
author Adam Jensen1672104452 -0500
committer Adam Jensen1672104890 -0500
Fix typo in expectation message

Et, lorsque nous regardons cet objet arborescent, nous voyons une liste de chaque fichier/sous-répertoire sous la racine du référentiel dans ce commit :

$ git cat-file -p e197a79bef523842c91ee06fa19a51446975ec35
040000 tree 2fcc102acd27df8f24ddc3867b6756ac554b33ef.cargo
040000 tree 7714769e97c483edb052ea14e7500735c04713eb.github
100644 blob ebb410eb8266a8d6fbde8a9ffaf5db54a5fc979a.gitignore
100644 blob fa1edfb73ce93054fe32d4eb35a5c4bee68c5bf5ARCHITECTURE.md
100644 blob 9c1883ee31f4fa8b6546a7226754cfc84ada5726CODE_OF_CONDUCT.md
100644 blob 9fac1017cb65883554f821914fac3fb713008a34CONTRIBUTORS.md
100644 blob b009175dbcbc186fb8066344c0e899c3104f43e5Cargo.lock
100644 blob 94b87cd2940697288e4f18530c5933f3110b405bCargo.toml

Cela signifie que la vérification d’un commit Git est toujours rapide : il est tout aussi facile pour Git de vérifier le commit d’hier que de vérifier un million de commits il y a un million. Git n'a jamais besoin de réappliquer 10 000 différences pour déterminer l'état actuel, car les validations ne sont jamais stockées sous forme de différences.

Les instantanés sont compressés à l'aide du packfile

Je viens de mentionner qu'un commit Git est un instantané, mais quand quelqu'un dit "À mon avis, un commit est un instantané, mais je pense que c'est une différence dans l'implémentation"
, c'est aussi vrai ! Les commits Git
ne sont pas représentés sous la forme de différences auxquelles vous pourriez être habitué (ils ne sont pas stockés sur le disque en tant que différence par rapport au commit précédent), mais l'intuition de base est que si vous voulez faire un 10 000
fichier en ligne L'édition 500 fois, puis le stockage de 500 fichiers seront inefficaces.

Git dispose d'un moyen de stocker des fichiers sous forme de différences. C'est ce qu'on appelle un "packfile" et Git collectera périodiquement vos données dans un packfile pour économiser de l'espace disque. Git compresse également les données lorsque vous git clone un référentiel.

Je n'ai pas assez d'espace ici pour expliquer en détail le fonctionnement des packfiles (« Unpacking Git packfiles » d'Aditya Mukerjee est mon article préféré pour expliquer comment ils fonctionnent). Cependant, je peux résumer brièvement ma compréhension du fonctionnement des deltas et de la manière dont ils diffèrent des diff ici :

  • L'objet est stocké comme référence au « fichier original » et un « delta »
  • Un delta est une séquence d'instructions telles que « lire les octets 0 à 100, puis insérer l'octet « bonjour », puis lire les octets 120 à 200 ». Il rassemble le nouveau texte des fichiers originaux. Il n'y a donc pas de notion de "supprimer", il suffit de copier et d'ajouter.
  • Je pense qu'il y a moins de niveaux de deltas : je ne sais pas comment vérifier exactement combien de niveaux de deltas Git doit parcourir pour obtenir un objet donné, mais j'ai l'impression qu'il n'y en a généralement pas beaucoup. Peut-être moins de 10 étages ? J'aimerais cependant savoir comment le découvrir.
  • Le fichier original ne doit pas nécessairement provenir du commit précédent, il peut s'agir de n'importe quoi. Peut-être que cela pourrait même provenir d'un commit ultérieur ? Je ne suis pas sûr.
  • Il n’existe pas de « bon » algorithme pour calculer les changements, Git a juste quelques heuristiques approximatives

Quelque chose de bizarre se produit en fait quand on regarde les différences

Ce qui se passe réellement lorsque nous exécutons git show SOME_COMMIT pour voir la différence d'un commit est un peu contre-intuitif. Ma compréhension est la suivante :

  • Git recherchera dans le fichier pack et appliquera les modifications pour reconstruire l'arborescence de ce commit et de ses commits parents.
  • Git effectuera une comparaison de différence entre deux arborescences de répertoires (l'arborescence de répertoires du commit actuel et l'arborescence de répertoires du commit parent). C'est généralement rapide car presque tous les fichiers sont exactement les mêmes, donc git peut simplement comparer les hachages de fichiers identiques, presque toujours sans rien faire.
  • Enfin, Git montrera les différences
  • Ainsi, Git convertira les modifications en un instantané, puis calculera la différence. Cela semble un peu bizarre parce que cela commence par quelque chose comme une différence et se termine par autre chose comme une différence, mais la quantité de changement et la différence sont en réalité complètement différentes, donc c'est logique.

    Cela dit, je pense que Git stocke les commits sous forme d'instantanés et le packfile n'est qu'un détail d'implémentation pour économiser de l'espace disque et accélérer le clonage. Je n'ai jamais eu besoin de savoir comment fonctionne packfile, mais cela m'aide à comprendre comment les instantanés Git sont validés sans occuper trop d'espace disque.

    Une « mauvaise » compréhension de Git : les commits sont des différences

    Je pense qu’une compréhension assez courante des « erreurs » de Git est la suivante :

    • Les commits sont stockés sous forme de différences basées sur le commit précédent (plus un pointeur vers le commit parent, l'auteur et le message).
    • Pour obtenir l'état actuel d'un commit, Git doit réappliquer tous les commits précédents à partir de zéro.

    Cette compréhension est bien sûr fausse (en réalité, les commits sont stockés sous forme d'instantanés et les différences sont calculées à partir de ces instantanés), mais cela me semble très utile et logique ! C'est un peu bizarre quand on pense aux commits de fusion, mais peut-être pourrions-nous dire que c'est juste la différence basée sur le premier commit parent du commit de fusion.

    Je pense que ce malentendu est parfois très utile, et cela ne semble pas poser de problème pour une utilisation quotidienne de Git. J'aime vraiment le fait que les choses que nous utilisons le plus (différences) soient les éléments les plus fondamentaux - c'est très intuitif pour moi.

    J'ai également réfléchi à d'autres compréhensions utiles mais « fausses » de Git, telles que :

    • Les informations du commit peuvent être modifiées (en fait non, il vous suffit de copier un commit identique et de lui donner de nouvelles informations, l'ancien commit existe toujours)
    • Les commits peuvent être déplacés vers une base différente (de la même manière, ils sont copiés)

    Je pense qu'il existe une gamme de «mauvaises» compréhensions de Git qui ont beaucoup de sens, sont largement prises en charge par l'interface utilisateur de Git et ne posent pas de problèmes dans la plupart des cas. Mais cela peut devenir déroutant lorsque vous souhaitez annuler une modification ou que quelque chose ne va pas.

    Quelques avantages de considérer les soumissions comme des différences

    Même si je sais que les commits sont des instantanés dans Git, je les traite probablement comme des différences la plupart du temps parce que :

    • La plupart du temps, je me concentre sur les changements que j'apporte - si je change simplement une ligne de code, je pense évidemment principalement à cette ligne de code plutôt qu'à l'état actuel de l'ensemble de la base de code
    • Vous verrez la différence lorsque vous cliquerez sur Git commit sur GitHub ou utiliserez git show donc c'est juste quelque chose que j'ai l'habitude de voir
    • J'utilise beaucoup le rebasing, il s'agit de réappliquer des différences

    Quelques avantages de traiter les commits comme des instantanés

    Mais je considère aussi parfois les commits comme des instantanés car :

    • Git est souvent confus par le mouvement des fichiers : parfois je déplace un fichier et le modifie, et Git ne reconnaît pas qu'il a été déplacé, mais il s'affiche comme
      "old.py supprimé, new.py ajouté". En effet, Git ne stocke que des instantanés, donc quand il dit "Déplacer old.py -> new.py"
      Pour le moment, ce n'est qu'une supposition car le contenu de old.py et new.py est similaire.
    • De cette façon, c'est plus facile de comprendre ce que fait git checkout COMMIT_ID (l'idée de réappliquer 10 000 commits me stresse)
    • Les commits de fusion ressemblent davantage à des instantanés, puisque le commit fusionné peut être littéralement n'importe quoi (c'est juste un nouvel instantané !). Cela m'a aidé à comprendre pourquoi des modifications arbitraires peuvent être apportées lors de la résolution de conflits de fusion et pourquoi il faut être prudent lors de la résolution des conflits.

    Quelques autres compréhensions de la soumission

    Certaines réponses de Mastodon mentionnent également :

    • Informations hors bande « supplémentaires » sur les commits, telles que les e-mails, les pull request GitHub ou les conversations que vous avez avec des collègues
    • Considérez la « différence » comme un « état avant + état après »
    • Et, bien sûr, de nombreuses personnes perçoivent les soumissions différemment selon les circonstances

    Quelques autres mots que les gens utilisent pour parler de commits qui pourraient être moins ambigus :

    • "Révision" (ressemble plus à un instantané)
    • "Patch" (ressemble plus à un diff)

    Ça y est !

    Il m'est difficile de comprendre les différentes compréhensions que les gens ont de Git. Ce qui est particulièrement délicat, c'est que, même si les « mauvaises » compréhensions sont souvent très utiles, les gens se méfient tellement des « mauvais » modèles mentaux qu'ils hésitent à partager leurs « fausses » idées, de peur qu'un interprète de Git ne se lève. Sortez et expliquez-leur pourquoi ils ont tort. (Ces interprètes Git
    ont généralement de bonnes intentions, mais cela peut quand même avoir un impact négatif)

    Mais j'ai beaucoup appris ! Je ne sais toujours pas vraiment comment parler de commits, mais nous finirons par le découvrir.

    Merci à Marco Rogers, Marie Flanagan et à tout le monde chez Mastodon d'avoir parlé des commits Git avec moi.

    Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

    Déclaration:
    Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer