{
  "title": "Contrôle de version (Git)",
  "excerpt": "Comment utiliser le contrôle de version _correctement_, et en tirer parti pour vous sauver d'un désastre, collaborer avec d'autres, et trouver et isoler rapidement les changements problématiques. Fini le `rm -rf; git clone`. Fini les conflits de fusion (enfin, moins nombreux au moins). Fini les énormes blocs de code commenté. Fini l'angoisse de savoir comment trouver ce qui a cassé votre code. Fini le \"oh non, avons-nous supprimé le code qui fonctionnait ?!\".",
  "content_html": "<p>Les systèmes de contrôle de version (VCS) sont des outils utilisés pour suivre les modifications apportées au code source (ou à d'autres collections de fichiers et dossiers). Comme leur nom l'indique, ces outils aident à maintenir un historique des modifications ; de plus, ils facilitent la collaboration. Les VCS suivent les modifications apportées à un dossier et à son contenu dans une série d'instantanés, où chaque instantané encapsule l'état complet des fichiers/dossiers dans un répertoire de niveau supérieur. Les VCS maintiennent également des métadonnées comme qui a créé chaque instantané, les messages associés à chaque instantané, etc.</p>\n\n<p>Pourquoi le contrôle de version est-il utile ? Même lorsque vous travaillez seul, il peut vous permettre de consulter d'anciens instantanés d'un projet, de conserver un journal des raisons pour lesquelles certaines modifications ont été apportées, de travailler sur des branches de développement parallèles, et bien plus encore. Lorsque vous travaillez avec d'autres, c'est un outil inestimable pour voir ce que d'autres personnes ont modifié, ainsi que pour résoudre les conflits dans le développement concurrent.</p>\n\n<p>Les VCS modernes vous permettent également de répondre facilement (et souvent automatiquement) à des questions telles que :</p>\n\n<ul>\n<li>Qui a écrit ce module ?</li>\n<li>Quand cette ligne particulière de ce fichier particulier a-t-elle été modifiée ? Par qui ? Pourquoi a-t-elle été modifiée ?</li>\n<li>Au cours des 1000 dernières révisions, quand/pourquoi un test unitaire particulier a-t-il cessé de fonctionner ?</li>\n</ul>\n\n<p>Bien que d'autres VCS existent, <strong>Git</strong> est la norme de facto pour le contrôle de version. Cette <a href=\"https://xkcd.com/1597/\">bande dessinée XKCD</a> capture la réputation de Git :</p>\n\n<p><img src=\"https://imgs.xkcd.com/comics/git.png\" alt=\"xkcd 1597\" /></p>\n\n<p>Parce que l'interface de Git est une abstraction qui fuit, apprendre Git de haut en bas (en commençant par son interface / interface en ligne de commande) peut conduire à beaucoup de confusion. Il est possible de mémoriser une poignée de commandes et de les considérer comme des incantations magiques, et de suivre l'approche de la bande dessinée ci-dessus chaque fois que quelque chose ne va pas.</p>\n\n<p>Bien que Git ait certes une interface laide, sa conception et ses idées sous-jacentes sont belles. Alors qu'une interface laide doit être <em>mémorisée</em>, une belle conception peut être <em>comprise</em>. Pour cette raison, nous donnons une explication ascendante de Git, en commençant par son modèle de données et en couvrant ensuite l'interface en ligne de commande. Une fois le modèle de données compris, les commandes peuvent être mieux comprises en termes de la façon dont elles manipulent le modèle de données sous-jacent.</p>\n\n<h1>Modèle de données de Git</h1>\n\n<p>Il existe de nombreuses approches ad hoc que vous pourriez adopter pour le contrôle de version. Git a un modèle bien pensé qui permet toutes les belles fonctionnalités du contrôle de version, comme le maintien de l'historique, le support des branches et la collaboration.</p>\n\n<h2>Instantanés</h2>\n\n<p>Git modélise l'historique d'une collection de fichiers et de dossiers dans un répertoire de niveau supérieur comme une série d'instantanés. Dans la terminologie Git, un fichier est appelé un \"blob\", et c'est juste un tas d'octets. Un répertoire est appelé un \"tree\" (arbre), et il mappe des noms à des blobs ou des arbres (donc les répertoires peuvent contenir d'autres répertoires). Un instantané est l'arbre de niveau supérieur qui est suivi. Par exemple, nous pourrions avoir un arbre comme suit :</p>\n\n<pre><code>&lt;root&gt; (tree)\n|\n+- foo (tree)\n|  |\n|  + bar.txt (blob, contents = \"hello world\")\n|\n+- baz.txt (blob, contents = \"git is wonderful\")\n</code></pre>\n\n<p>L'arbre de niveau supérieur contient deux éléments, un arbre \"foo\" (qui contient lui-même un élément, un blob \"bar.txt\"), et un blob \"baz.txt\".</p>\n\n<h2>Modélisation de l'historique : relier les instantanés</h2>\n\n<p>Comment un système de contrôle de version devrait-il relier les instantanés ? Un modèle simple serait d'avoir un historique linéaire. Un historique serait une liste d'instantanés dans l'ordre chronologique. Pour de nombreuses raisons, Git n'utilise pas un modèle simple comme celui-ci.</p>\n\n<p>Dans Git, un historique est un graphe acyclique orienté (DAG) d'instantanés. Cela peut sembler être un mot mathématique sophistiqué, mais ne soyez pas intimidé. Tout cela signifie que chaque instantané dans Git fait référence à un ensemble de \"parents\", les instantanés qui l'ont précédé. C'est un ensemble de parents plutôt qu'un seul parent (comme ce serait le cas dans un historique linéaire) car un instantané peut descendre de plusieurs parents, par exemple, en raison de la combinaison (fusion) de deux branches parallèles de développement.</p>\n\n<p>Git appelle ces instantanés des \"commit\"s. La visualisation d'un historique de commits pourrait ressembler à ceci :</p>\n\n<pre><code>o &lt;-- o &lt;-- o &lt;-- o\n            ^\n             \\\n              --- o &lt;-- o\n</code></pre>\n\n<p>Dans l'art ASCII ci-dessus, les <code>o</code>s correspondent à des commits individuels (instantanés). Les flèches pointent vers le parent de chaque commit (c'est une relation \"vient avant\", pas \"vient après\"). Après le troisième commit, l'historique se divise en deux branches distinctes. Cela pourrait correspondre, par exemple, à deux fonctionnalités distinctes développées en parallèle, indépendamment l'une de l'autre. À l'avenir, ces branches peuvent être fusionnées pour créer un nouvel instantané qui incorpore les deux fonctionnalités, produisant un nouvel historique qui ressemble à ceci, avec le commit de fusion nouvellement créé affiché en gras :</p>\n\n<pre class=\"highlight\">\n<code>\no &lt;-- o &lt;-- o &lt;-- o &lt;---- <strong>o</strong>\n            ^            /\n             \\          v\n              --- o &lt;-- o\n</code>\n</pre>\n\n<p>Les commits dans Git sont immuables. Cela ne signifie pas que les erreurs ne peuvent pas être corrigées, cependant ; c'est juste que les \"modifications\" de l'historique des commits créent en fait des commits entièrement nouveaux, et les références (voir ci-dessous) sont mises à jour pour pointer vers les nouveaux.</p>\n\n<h2>Modèle de données, en pseudocode</h2>\n\n<p>Il peut être instructif de voir le modèle de données de Git écrit en pseudocode :</p>\n\n<pre><code>// un fichier est un tas d'octets\ntype blob = array&lt;byte&gt;\n\n// un répertoire contient des fichiers et des répertoires nommés\ntype tree = map&lt;string, tree | blob&gt;\n\n// un commit a des parents, des métadonnées et l'arbre de niveau supérieur\ntype commit = struct {\n    parents: array&lt;commit&gt;\n    author: string\n    message: string\n    snapshot: tree\n}\n</code></pre>\n\n<p>C'est un modèle d'historique propre et simple.</p>\n\n<h2>Objets et adressage par contenu</h2>\n\n<p>Un \"objet\" est un blob, un arbre ou un commit :</p>\n\n<pre><code>type object = blob | tree | commit\n</code></pre>\n\n<p>Dans le magasin de données Git, tous les objets sont adressés par contenu par leur <a href=\"https://en.wikipedia.org/wiki/SHA-1\">hachage SHA-1</a>.</p>\n\n<pre><code>objects = map&lt;string, object&gt;\n\ndef store(object):\n    id = sha1(object)\n    objects[id] = object\n\ndef load(id):\n    return objects[id]\n</code></pre>\n\n<p>Les blobs, les arbres et les commits sont unifiés de cette manière : ce sont tous des objets. Lorsqu'ils font référence à d'autres objets, ils ne les <em>contiennent</em> pas réellement dans leur représentation sur disque, mais ont une référence à eux par leur hachage.</p>\n\n<p>Par exemple, l'arbre pour la structure de répertoire d'exemple <a href=\"#snapshots\">ci-dessus</a> (visualisé en utilisant <code>git cat-file -p 698281bc680d1995c5f4caaf3359721a5a58d48d</code>), ressemble à ceci :</p>\n\n<pre><code>100644 blob 4448adbf7ecd394f42ae135bbeed9676e894af85    baz.txt\n040000 tree c68d233a33c5c06e0340e4c224f0afca87c8ce87    foo\n</code></pre>\n\n<p>L'arbre lui-même contient des pointeurs vers son contenu, <code>baz.txt</code> (un blob) et <code>foo</code> (un arbre). Si nous regardons le contenu adressé par le hachage correspondant à baz.txt avec <code>git cat-file -p 4448adbf7ecd394f42ae135bbeed9676e894af85</code>, nous obtenons ce qui suit :</p>\n\n<pre><code>git is wonderful\n</code></pre>\n\n<h2>Références</h2>\n\n<p>Maintenant, tous les instantanés peuvent être identifiés par leurs hachages SHA-1. C'est peu pratique, car les humains ne sont pas doués pour se souvenir de chaînes de 40 caractères hexadécimaux.</p>\n\n<p>La solution de Git à ce problème est des noms lisibles par l'homme pour les hachages SHA-1, appelés \"références\". Les références sont des pointeurs vers des commits. Contrairement aux objets, qui sont immuables, les références sont mutables (peuvent être mises à jour pour pointer vers un nouveau commit). Par exemple, la référence <code>master</code> pointe généralement vers le dernier commit dans la branche principale de développement.</p>\n\n<pre><code>references = map&lt;string, string&gt;\n\ndef update_reference(name, id):\n    references[name] = id\n\ndef read_reference(name):\n    return references[name]\n\ndef load_reference(name_or_id):\n    if name_or_id in references:\n        return load(references[name_or_id])\n    else:\n        return load(name_or_id)\n</code></pre>\n\n<p>Avec cela, Git peut utiliser des noms lisibles par l'homme comme \"master\" pour faire référence à un instantané particulier dans l'historique, au lieu d'une longue chaîne hexadécimale.</p>\n\n<p>Un détail est que nous voulons souvent une notion de \"où nous sommes actuellement\" dans l'historique, de sorte que lorsque nous prenons un nouvel instantané, nous savons ce qu'il est par rapport à (comment nous définissons le champ <code>parents</code> du commit). Dans Git, ce \"où nous sommes actuellement\" est une référence spéciale appelée \"HEAD\".</p>\n\n<h2>Dépôts</h2>\n\n<p>Enfin, nous pouvons définir ce qu'est (approximativement) un <em>dépôt</em> Git : ce sont les données <code>objects</code> et <code>references</code>.</p>\n\n<p>Sur le disque, tout ce que Git stocke sont des objets et des références : c'est tout ce qu'il y a dans le modèle de données de Git. Toutes les commandes <code>git</code> correspondent à une manipulation du DAG de commits en ajoutant des objets et en ajoutant/mettant à jour des références.</p>\n\n<p>Chaque fois que vous tapez une commande, pensez à quelle manipulation la commande effectue sur la structure de données graphique sous-jacente. Inversement, si vous essayez d'apporter un type particulier de modification au DAG de commits, par exemple \"annuler les modifications non validées et faire pointer la référence 'master' vers le commit <code>5d83f9e</code>\", il y a probablement une commande pour le faire (par exemple dans ce cas, <code>git checkout master; git reset --hard 5d83f9e</code>).</p>\n\n<h1>Zone de staging</h1>\n\n<p>C'est un autre concept qui est orthogonal au modèle de données, mais c'est une partie de l'interface pour créer des commits.</p>\n\n<p>Une façon dont vous pourriez imaginer implémenter la prise d'instantanés comme décrit ci-dessus serait d'avoir une commande \"créer un instantané\" qui crée un nouvel instantané basé sur l'<em>état actuel</em> du répertoire de travail. Certains outils de contrôle de version fonctionnent comme cela, mais pas Git. Nous voulons des instantanés propres, et il n'est peut-être pas toujours idéal de créer un instantané à partir de l'état actuel. Par exemple, imaginez un scénario où vous avez implémenté deux fonctionnalités distinctes, et vous voulez créer deux commits distincts, où le premier introduit la première fonctionnalité, et le suivant introduit la deuxième fonctionnalité. Ou imaginez un scénario où vous avez des instructions d'impression de débogage ajoutées partout dans votre code, ainsi qu'une correction de bug ; vous voulez valider la correction de bug tout en supprimant toutes les instructions d'impression.</p>\n\n<p>Git s'adapte à de tels scénarios en vous permettant de spécifier quelles modifications doivent être incluses dans le prochain instantané grâce à un mécanisme appelé la \"zone de staging\".</p>\n\n<h1>Interface en ligne de commande Git</h1>\n\n<p>Pour éviter de dupliquer les informations, nous n'allons pas expliquer les commandes ci-dessous en détail. Consultez le très recommandé <a href=\"https://git-scm.com/book/en/v2\">Pro Git</a> pour plus d'informations.</p>\n\n<h2>Bases</h2>\n\n<p>La commande <code>git init</code> initialise un nouveau dépôt Git, avec les métadonnées du dépôt stockées dans le répertoire <code>.git</code> :</p>\n\n<pre><code class=\"language-console\">$ mkdir myproject\n$ cd myproject\n$ git init\nInitialized empty Git repository in .git\n$ git status\nOn branch master\nNo commits yet\nnothing to commit (create/copy files and use \"git add\" to track)\n</code></pre>\n\n<p>Comment interpréter cette sortie ? \"No commits yet\" signifie essentiellement que notre historique de version est vide. Corrigeons cela.</p>\n\n<pre><code class=\"language-console\">$ echo \"hello, git\" &gt; hello.txt\n$ git add hello.txt\n$ git status\nOn branch master\nNo commits yet\nChanges to be committed:\n  (use \"git rm --cached &lt;file&gt;...\" to unstage)\n        new file:   hello.txt\n$ git commit -m 'Initial commit'\n[master (root-commit) 4515d17] Initial commit\n 1 file changed, 1 insertion(+)\n create mode 100644 hello.txt\n</code></pre>\n\n<p>Avec cela, nous avons ajouté un fichier à la zone de staging avec <code>git add</code>, puis validé ce changement avec <code>git commit</code>, en ajoutant un simple message de commit \"Initial commit\". Si nous ne spécifions pas d'option <code>-m</code>, Git ouvrirait notre éditeur de texte pour nous permettre de taper un message de commit.</p>\n\n<p>Maintenant que nous avons un historique de version non vide, nous pouvons visualiser l'historique. Visualiser l'historique comme un DAG peut être particulièrement utile pour comprendre l'état actuel du dépôt et le connecter avec votre compréhension du modèle de données Git.</p>\n\n<p>La commande <code>git log</code> visualise l'historique. Par défaut, elle affiche une version aplatie, qui cache la structure du graphe. Si vous utilisez une commande comme <code>git log --all --graph --decorate</code>, elle vous montrera l'historique complet du dépôt, visualisé sous forme de graphe.</p>\n\n<pre><code class=\"language-console\">$ git log --all --graph --decorate\n* commit 4515d17a167bdef0a91ee7d50d75b12c9c2652aa (HEAD -&gt; master)\n  Author: Subramanya N &lt;subramanyanagabhushan@gmail.com&gt;\n  Date: Tue Dec 21 22:18:36 2020 -0500\n      Initial commit\n</code></pre>\n\n<p>Cela ne ressemble pas vraiment à un graphe, car il ne contient qu'un seul nœud. Faisons quelques modifications supplémentaires, créons un nouveau commit et visualisons à nouveau l'historique.</p>\n\n<pre><code class=\"language-console\">$ echo \"another line\" &gt;&gt; hello.txt\n$ git status\nOn branch master\nChanges not staged for commit:\n  (use \"git add &lt;file&gt;...\" to update what will be committed)\n  (use \"git checkout -- &lt;file&gt;...\" to discard changes in working directory)\n        modified:   hello.txt\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\n$ git add hello.txt\n$ git status\nOn branch master\nChanges to be committed:\n  (use \"git reset HEAD &lt;file&gt;...\" to unstage)\n        modified:   hello.txt\n$ git commit -m 'Add a line'\n[master 35f60a8] Add a line\n 1 file changed, 1 insertion(+)\n</code></pre>\n\n<p>Maintenant, si nous visualisons à nouveau l'historique, nous verrons une partie de la structure du graphe :</p>\n\n<pre><code>* commit 35f60a825be0106036dd2fbc7657598eb7b04c67 (HEAD -&gt; master)\n| Author: Subramanya N &lt;subramanyanagabhushan@gmail.com&gt;\n| Date:   Tue Dec 21 22:26:20 2020 -0500\n|     Add a line\n* commit 4515d17a167bdef0a91ee7d50d75b12c9c2652aa\n  Author: Subramanya N &lt;subramanyanagabhushan@gmail.com&gt;\n  Date: Tue Dec 21 22:18:36 2020 -0500\n      Initial commit\n</code></pre>\n\n<p>Notez également qu'il affiche le HEAD actuel, ainsi que la branche actuelle (master).</p>\n\n<p>Nous pouvons consulter les anciennes versions en utilisant la commande <code>git checkout</code>.</p>\n\n<pre><code class=\"language-console\">$ git checkout 4515d17  # hachage du commit précédent ; le vôtre sera différent\nNote: checking out '4515d17'.\nYou are in 'detached HEAD' state. You can look around, make experimental\nchanges and commit them, and you can discard any commits you make in this\nstate without impacting any branches by performing another checkout.\nIf you want to create a new branch to retain commits you create, you may\ndo so (now or later) by using -b with the checkout command again. Example:\n  git checkout -b &lt;new-branch-name&gt;\nHEAD is now at 4515d17 Initial commit\n$ cat hello.txt\nhello, git\n$ git checkout master\nPrevious HEAD position was 4515d17 Initial commit\nSwitched to branch 'master'\n$ cat hello.txt\nhello, git\nanother line\n</code></pre>\n\n<p>Git peut vous montrer comment les fichiers ont évolué (différences, ou diffs) en utilisant la commande <code>git diff</code> :</p>\n\n<pre><code class=\"language-console\">$ git diff 4515d17 hello.txt\ndiff --git c/hello.txt w/hello.txt\nindex 94bab17..f0013b2 100644\n--- c/hello.txt\n+++ w/hello.txt\n@@ -1 +1,2 @@\n hello, git\n +another line\n</code></pre>\n\n<ul>\n<li><code>git help &lt;command&gt;</code> : obtenir de l'aide pour une commande git</li>\n<li><code>git init</code> : crée un nouveau dépôt git, avec les données stockées dans le répertoire <code>.git</code></li>\n<li><code>git status</code> : vous indique ce qui se passe</li>\n<li><code>git add &lt;filename&gt;</code> : ajoute des fichiers à la zone de staging</li>\n<li><code>git commit</code> : crée un nouveau commit\n<ul>\n<li>Écrivez de <a href=\"https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html\">bons messages de commit</a> !</li>\n<li>Encore plus de raisons d'écrire de <a href=\"https://chris.beams.io/posts/git-commit/\">bons messages de commit</a> !</li>\n</ul></li>\n<li><code>git log</code> : affiche un journal aplati de l'historique</li>\n<li><code>git log --all --graph --decorate</code> : visualise l'historique comme un DAG</li>\n<li><code>git diff &lt;filename&gt;</code> : affiche les modifications que vous avez apportées par rapport à la zone de staging</li>\n<li><code>git diff &lt;revision&gt; &lt;filename&gt;</code> : affiche les différences dans un fichier entre les instantanés</li>\n<li><code>git checkout &lt;revision&gt;</code> : met à jour HEAD et la branche actuelle</li>\n</ul>\n\n<h2>Branchement et fusion</h2>\n\n<p>Le branchement vous permet de \"bifurquer\" l'historique des versions. Il peut être utile pour travailler sur des fonctionnalités ou des corrections de bugs indépendantes en parallèle. La commande <code>git branch</code> peut être utilisée pour créer de nouvelles branches ; <code>git checkout -b &lt;branch name&gt;</code> crée une branche et y bascule.</p>\n\n<p>La fusion est l'opposé du branchement : elle vous permet de combiner des historiques de versions bifurqués, par exemple, fusionner une branche de fonctionnalité dans master. La commande <code>git merge</code> est utilisée pour la fusion.</p>\n\n<ul>\n<li><code>git branch</code> : affiche les branches</li>\n<li><code>git branch &lt;name&gt;</code> : crée une branche</li>\n<li><code>git checkout -b &lt;name&gt;</code> : crée une branche et y bascule\n<ul>\n<li>identique à <code>git branch &lt;name&gt;; git checkout &lt;name&gt;</code></li>\n</ul></li>\n<li><code>git merge &lt;revision&gt;</code> : fusionne dans la branche actuelle</li>\n<li><code>git mergetool</code> : utilise un outil sophistiqué pour aider à résoudre les conflits de fusion</li>\n<li><code>git rebase</code> : rebase un ensemble de correctifs sur une nouvelle base</li>\n</ul>\n\n<h2>Dépôts distants</h2>\n\n<ul>\n<li><code>git remote</code> : liste les dépôts distants</li>\n<li><code>git remote add &lt;name&gt; &lt;url&gt;</code> : ajoute un dépôt distant</li>\n<li><code>git push &lt;remote&gt; &lt;local branch&gt;:&lt;remote branch&gt;</code> : envoie des objets au dépôt distant et met à jour la référence distante</li>\n<li><code>git branch --set-upstream-to=&lt;remote&gt;/&lt;remote branch&gt;</code> : établit la correspondance entre la branche locale et la branche distante</li>\n<li><code>git fetch</code> : récupère les objets/références d'un dépôt distant</li>\n<li><code>git pull</code> : identique à <code>git fetch; git merge</code></li>\n<li><code>git clone</code> : télécharge le dépôt depuis un dépôt distant</li>\n</ul>\n\n<h2>Annuler</h2>\n\n<ul>\n<li><code>git commit --amend</code> : modifie le contenu/message d'un commit</li>\n<li><code>git reset HEAD &lt;file&gt;</code> : retire un fichier de la zone de staging</li>\n<li><code>git checkout -- &lt;file&gt;</code> : annule les modifications</li>\n</ul>\n\n<h1>Git avancé</h1>\n\n<ul>\n<li><code>git config</code> : Git est <a href=\"https://git-scm.com/docs/git-config\">hautement personnalisable</a></li>\n<li><code>git clone --depth=1</code> : clone superficiel, sans l'historique complet des versions</li>\n<li><code>git add -p</code> : staging interactif</li>\n<li><code>git rebase -i</code> : rebasing interactif</li>\n<li><code>git blame</code> : affiche qui a modifié quelle ligne en dernier</li>\n<li><code>git stash</code> : supprime temporairement les modifications du répertoire de travail</li>\n<li><code>git bisect</code> : recherche binaire dans l'historique (par exemple pour les régressions)</li>\n<li><code>.gitignore</code> : <a href=\"https://git-scm.com/docs/gitignore\">spécifie</a> les fichiers intentionnellement non suivis à ignorer</li>\n</ul>\n\n<h1>Divers</h1>\n\n<ul>\n<li><strong>Interfaces graphiques</strong> : il existe de nombreux <a href=\"https://git-scm.com/downloads/guis\">clients GUI</a> pour Git. Personnellement, nous ne les utilisons pas et utilisons plutôt l'interface en ligne de commande.</li>\n<li><strong>Intégration shell</strong> : il est très pratique d'avoir un statut Git dans votre invite de shell (<a href=\"https://github.com/olivierverdier/zsh-git-prompt\">zsh</a>, <a href=\"https://github.com/magicmonty/bash-git-prompt\">bash</a>). Souvent inclus dans des frameworks comme <a href=\"https://github.com/ohmyzsh/ohmyzsh\">Oh My Zsh</a>.</li>\n<li><strong>Intégration éditeur</strong> : de même que ci-dessus, intégrations pratiques avec de nombreuses fonctionnalités. <a href=\"https://github.com/tpope/vim-fugitive\">fugitive.vim</a> est la norme pour Vim.</li>\n<li><strong>Workflows</strong> : nous vous avons enseigné le modèle de données, plus quelques commandes de base ; nous ne vous avons pas dit quelles pratiques suivre lorsque vous travaillez sur de grands projets (et il existe <a href=\"https://nvie.com/posts/a-successful-git-branching-model/\">de</a> <a href=\"https://www.endoflineblog.com/gitflow-considered-harmful\">nombreuses</a> <a href=\"https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow\">approches</a> différentes).</li>\n<li><strong>GitHub</strong> : Git n'est pas GitHub. GitHub a une manière spécifique de contribuer du code à d'autres projets, appelée <a href=\"https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests\">pull requests</a>.</li>\n<li><strong>Autres fournisseurs Git</strong> : GitHub n'est pas spécial : il existe de nombreux hébergeurs de dépôts Git, comme <a href=\"https://about.gitlab.com/\">GitLab</a> et <a href=\"https://bitbucket.org/\">BitBucket</a>.</li>\n</ul>\n\n<h1>Ressources</h1>\n\n<ul>\n<li><a href=\"https://git-scm.com/book/en/v2\">Pro Git</a> est une lecture <strong>fortement recommandée</strong>. Parcourir les chapitres 1 à 5 devrait vous enseigner la plupart de ce dont vous avez besoin pour utiliser Git de manière compétente, maintenant que vous comprenez le modèle de données. Les chapitres ultérieurs contiennent du matériel intéressant et avancé.</li>\n<li><a href=\"https://ohshitgit.com/\">Oh Shit, Git!?!</a> est un guide court sur la façon de se remettre de certaines erreurs Git courantes.</li>\n<li><a href=\"https://eagain.net/articles/git-for-computer-scientists/\">Git for Computer Scientists</a> est une brève explication du modèle de données de Git, avec moins de pseudocode et plus de diagrammes sophistiqués que ces notes de cours.</li>\n<li><a href=\"https://jwiegley.github.io/git-from-the-bottom-up/\">Git from the Bottom Up</a> est une explication détaillée des détails d'implémentation de Git au-delà du simple modèle de données, pour les curieux.</li>\n<li><a href=\"https://smusamashah.github.io/blog/2017/10/14/explain-git-in-simple-words\">How to explain git in simple words</a></li>\n<li><a href=\"https://learngitbranching.js.org/\">Learn Git Branching</a> est un jeu basé sur navigateur qui vous enseigne Git.</li>\n</ul>",
  "source_hash": "sha256:1882fed561269610d4ac35bc5a461efe73f8481070dd58a45db04a8899598a98",
  "model": "claude-sonnet-4-5-20250929",
  "generated_at": "2026-01-02T04:09:27.570196+00:00"
}