{
  "title": "Controle de Versão (Git)",
  "excerpt": "Como usar controle de versão _adequadamente_ e aproveitá-lo para te salvar de desastres, colaborar com outros e rapidamente encontrar e isolar mudanças problemáticas. Chega de `rm -rf; git clone`. Chega de conflitos de merge (bem, pelo menos menos deles). Chega de grandes blocos de código comentado. Chega de se preocupar sobre como encontrar o que quebrou seu código. Chega de \"oh não, será que deletamos o código que funcionava?!\".",
  "content_html": "<p>Sistemas de controle de versão (VCSs) são ferramentas usadas para rastrear mudanças no código-fonte (ou outras coleções de arquivos e pastas). Como o nome implica, essas ferramentas ajudam a manter um histórico de mudanças; além disso, elas facilitam a colaboração. VCSs rastreiam mudanças em uma pasta e seu conteúdo em uma série de snapshots, onde cada snapshot encapsula o estado completo de arquivos/pastas dentro de um diretório de nível superior. VCSs também mantêm metadados como quem criou cada snapshot, mensagens associadas a cada snapshot, e assim por diante.</p>\n\n<p>Por que o controle de versão é útil? Mesmo quando você está trabalhando sozinho, ele pode permitir que você veja snapshots antigos de um projeto, mantenha um registro de por que certas mudanças foram feitas, trabalhe em branches paralelos de desenvolvimento, e muito mais. Ao trabalhar com outros, é uma ferramenta inestimável para ver o que outras pessoas mudaram, bem como resolver conflitos no desenvolvimento concorrente.</p>\n\n<p>VCSs modernos também permitem que você responda facilmente (e frequentemente automaticamente) perguntas como:</p>\n\n<ul>\n<li>Quem escreveu este módulo?</li>\n<li>Quando esta linha específica deste arquivo específico foi editada? Por quem? Por que foi editada?</li>\n<li>Nas últimas 1000 revisões, quando/por que um teste unitário específico parou de funcionar?</li>\n</ul>\n\n<p>Embora outros VCSs existam, <strong>Git</strong> é o padrão de fato para controle de versão. Este <a href=\"https://xkcd.com/1597/\">quadrinho do XKCD</a> captura a reputação do Git:</p>\n\n<p><img src=\"https://imgs.xkcd.com/comics/git.png\" alt=\"xkcd 1597\" /></p>\n\n<p>Como a interface do Git é uma abstração com vazamentos, aprender Git de cima para baixo (começando com sua interface / interface de linha de comando) pode levar a muita confusão. É possível memorizar um punhado de comandos e pensar neles como encantamentos mágicos, e seguir a abordagem no quadrinho acima sempre que algo der errado.</p>\n\n<p>Embora o Git admitidamente tenha uma interface feia, seu design e ideias subjacentes são bonitos. Enquanto uma interface feia tem que ser <em>memorizada</em>, um design bonito pode ser <em>compreendido</em>. Por esta razão, damos uma explicação de baixo para cima do Git, começando com seu modelo de dados e depois cobrindo a interface de linha de comando. Uma vez que o modelo de dados é compreendido, os comandos podem ser melhor compreendidos em termos de como eles manipulam o modelo de dados subjacente.</p>\n\n<h1>Modelo de dados do Git</h1>\n\n<p>Existem muitas abordagens ad-hoc que você poderia adotar para controle de versão. O Git tem um modelo bem pensado que habilita todos os recursos agradáveis do controle de versão, como manter histórico, suportar branches e habilitar colaboração.</p>\n\n<h2>Snapshots</h2>\n\n<p>O Git modela o histórico de uma coleção de arquivos e pastas dentro de algum diretório de nível superior como uma série de snapshots. Na terminologia do Git, um arquivo é chamado de \"blob\", e é apenas um monte de bytes. Um diretório é chamado de \"tree\", e ele mapeia nomes para blobs ou trees (então diretórios podem conter outros diretórios). Um snapshot é a tree de nível superior que está sendo rastreada. Por exemplo, podemos ter uma tree como segue:</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>A tree de nível superior contém dois elementos, uma tree \"foo\" (que ela mesma contém um elemento, um blob \"bar.txt\"), e um blob \"baz.txt\".</p>\n\n<h2>Modelando histórico: relacionando snapshots</h2>\n\n<p>Como um sistema de controle de versão deveria relacionar snapshots? Um modelo simples seria ter um histórico linear. Um histórico seria uma lista de snapshots em ordem temporal. Por muitas razões, o Git não usa um modelo simples como este.</p>\n\n<p>No Git, um histórico é um grafo acíclico direcionado (DAG) de snapshots. Isso pode soar como uma palavra matemática sofisticada, mas não se intimide. Tudo isso significa é que cada snapshot no Git se refere a um conjunto de \"pais\", os snapshots que o precederam. É um conjunto de pais em vez de um único pai (como seria o caso em um histórico linear) porque um snapshot pode descender de múltiplos pais, por exemplo, devido à combinação (merge) de dois branches paralelos de desenvolvimento.</p>\n\n<p>O Git chama esses snapshots de \"commit\"s. Visualizar um histórico de commits pode parecer algo assim:</p>\n\n<pre><code>o &lt;-- o &lt;-- o &lt;-- o\n            ^\n             \\\n              --- o &lt;-- o\n</code></pre>\n\n<p>Na arte ASCII acima, os <code>o</code>s correspondem a commits individuais (snapshots). As setas apontam para o pai de cada commit (é uma relação \"vem antes\", não \"vem depois\"). Após o terceiro commit, o histórico se ramifica em dois branches separados. Isso pode corresponder a, por exemplo, duas funcionalidades separadas sendo desenvolvidas em paralelo, independentemente uma da outra. No futuro, esses branches podem ser mesclados para criar um novo snapshot que incorpora ambas as funcionalidades, produzindo um novo histórico que se parece com isto, com o commit de merge recém-criado mostrado em negrito:</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>Commits no Git são imutáveis. Isso não significa que erros não possam ser corrigidos, no entanto; é apenas que \"edições\" no histórico de commits estão na verdade criando commits inteiramente novos, e referências (veja abaixo) são atualizadas para apontar para os novos.</p>\n\n<h2>Modelo de dados, como pseudocódigo</h2>\n\n<p>Pode ser instrutivo ver o modelo de dados do Git escrito em pseudocódigo:</p>\n\n<pre><code>// um arquivo é um monte de bytes\ntype blob = array&lt;byte&gt;\n\n// um diretório contém arquivos e diretórios nomeados\ntype tree = map&lt;string, tree | blob&gt;\n\n// um commit tem pais, metadados e a tree de nível superior\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>É um modelo de histórico limpo e simples.</p>\n\n<h2>Objetos e endereçamento por conteúdo</h2>\n\n<p>Um \"objeto\" é um blob, tree ou commit:</p>\n\n<pre><code>type object = blob | tree | commit\n</code></pre>\n\n<p>No armazenamento de dados do Git, todos os objetos são endereçados por conteúdo pelo seu <a href=\"https://en.wikipedia.org/wiki/SHA-1\">hash 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>Blobs, trees e commits são unificados desta forma: eles são todos objetos. Quando eles referenciam outros objetos, eles não os <em>contêm</em> realmente em sua representação em disco, mas têm uma referência a eles pelo seu hash.</p>\n\n<p>Por exemplo, a tree para a estrutura de diretório de exemplo <a href=\"#snapshots\">acima</a> (visualizada usando <code>git cat-file -p 698281bc680d1995c5f4caaf3359721a5a58d48d</code>), se parece com isto:</p>\n\n<pre><code>100644 blob 4448adbf7ecd394f42ae135bbeed9676e894af85    baz.txt\n040000 tree c68d233a33c5c06e0340e4c224f0afca87c8ce87    foo\n</code></pre>\n\n<p>A tree em si contém ponteiros para seu conteúdo, <code>baz.txt</code> (um blob) e <code>foo</code> (uma tree). Se olharmos para o conteúdo endereçado pelo hash correspondente a baz.txt com <code>git cat-file -p 4448adbf7ecd394f42ae135bbeed9676e894af85</code>, obtemos o seguinte:</p>\n\n<pre><code>git is wonderful\n</code></pre>\n\n<h2>Referências</h2>\n\n<p>Agora, todos os snapshots podem ser identificados pelos seus hashes SHA-1. Isso é inconveniente, porque humanos não são bons em lembrar strings de 40 caracteres hexadecimais.</p>\n\n<p>A solução do Git para este problema são nomes legíveis por humanos para hashes SHA-1, chamados \"referências\". Referências são ponteiros para commits. Diferente de objetos, que são imutáveis, referências são mutáveis (podem ser atualizadas para apontar para um novo commit). Por exemplo, a referência <code>master</code> geralmente aponta para o último commit no branch principal de desenvolvimento.</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>Com isso, o Git pode usar nomes legíveis por humanos como \"master\" para se referir a um snapshot específico no histórico, em vez de uma longa string hexadecimal.</p>\n\n<p>Um detalhe é que frequentemente queremos uma noção de \"onde estamos atualmente\" no histórico, para que quando tiramos um novo snapshot, saibamos o que é relativo a (como definimos o campo <code>parents</code> do commit). No Git, esse \"onde estamos atualmente\" é uma referência especial chamada \"HEAD\".</p>\n\n<h2>Repositórios</h2>\n\n<p>Finalmente, podemos definir o que (aproximadamente) é um <em>repositório</em> Git: são os dados <code>objects</code> e <code>references</code>.</p>\n\n<p>No disco, tudo que o Git armazena são objetos e referências: isso é tudo que há no modelo de dados do Git. Todos os comandos <code>git</code> mapeiam para alguma manipulação do DAG de commits adicionando objetos e adicionando/atualizando referências.</p>\n\n<p>Sempre que você estiver digitando qualquer comando, pense sobre qual manipulação o comando está fazendo na estrutura de dados de grafo subjacente. Inversamente, se você está tentando fazer um tipo específico de mudança no DAG de commits, por exemplo, \"descartar mudanças não commitadas e fazer a ref 'master' apontar para o commit <code>5d83f9e</code>\", provavelmente há um comando para fazer isso (por exemplo, neste caso, <code>git checkout master; git reset --hard 5d83f9e</code>).</p>\n\n<h1>Área de staging</h1>\n\n<p>Este é outro conceito que é ortogonal ao modelo de dados, mas é uma parte da interface para criar commits.</p>\n\n<p>Uma maneira que você poderia imaginar implementar snapshots como descrito acima seria ter um comando \"criar snapshot\" que cria um novo snapshot baseado no <em>estado atual</em> do diretório de trabalho. Algumas ferramentas de controle de versão funcionam assim, mas não o Git. Queremos snapshots limpos, e pode nem sempre ser ideal fazer um snapshot do estado atual. Por exemplo, imagine um cenário onde você implementou duas funcionalidades separadas, e você quer criar dois commits separados, onde o primeiro introduz a primeira funcionalidade, e o próximo introduz a segunda funcionalidade. Ou imagine um cenário onde você tem declarações de print de debug adicionadas por todo o seu código, junto com uma correção de bug; você quer commitar a correção de bug enquanto descarta todas as declarações de print.</p>\n\n<p>O Git acomoda tais cenários permitindo que você especifique quais modificações devem ser incluídas no próximo snapshot através de um mecanismo chamado \"área de staging\".</p>\n\n<h1>Interface de linha de comando do Git</h1>\n\n<p>Para evitar duplicar informações, não vamos explicar os comandos abaixo em detalhes. Veja o altamente recomendado <a href=\"https://git-scm.com/book/en/v2\">Pro Git</a> para mais informações.</p>\n\n<h2>Básico</h2>\n\n<p>O comando <code>git init</code> inicializa um novo repositório Git, com metadados do repositório sendo armazenados no diretório <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>Como interpretamos esta saída? \"No commits yet\" basicamente significa que nosso histórico de versão está vazio. Vamos consertar isso.</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>Com isso, fizemos <code>git add</code> de um arquivo para a área de staging, e então fizemos <code>git commit</code> dessa mudança, adicionando uma mensagem de commit simples \"Initial commit\". Se não especificássemos uma opção <code>-m</code>, o Git abriria nosso editor de texto para nos permitir digitar uma mensagem de commit.</p>\n\n<p>Agora que temos um histórico de versão não vazio, podemos visualizar o histórico. Visualizar o histórico como um DAG pode ser especialmente útil para entender o status atual do repositório e conectá-lo com seu entendimento do modelo de dados do Git.</p>\n\n<p>O comando <code>git log</code> visualiza o histórico. Por padrão, ele mostra uma versão achatada, que esconde a estrutura de grafo. Se você usar um comando como <code>git log --all --graph --decorate</code>, ele mostrará o histórico de versão completo do repositório, visualizado em forma de grafo.</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>Isso não parece muito com um grafo, porque contém apenas um único nó. Vamos fazer mais algumas mudanças, criar um novo commit e visualizar o histórico mais uma vez.</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>Agora, se visualizarmos o histórico novamente, veremos um pouco da estrutura de grafo:</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>Além disso, note que ele mostra o HEAD atual, junto com o branch atual (master).</p>\n\n<p>Podemos olhar versões antigas usando o comando <code>git checkout</code>.</p>\n\n<pre><code class=\"language-console\">$ git checkout 4515d17  # hash do commit anterior; o seu será diferente\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>O Git pode mostrar como os arquivos evoluíram (diferenças, ou diffs) usando o comando <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>: obter ajuda para um comando git</li>\n<li><code>git init</code>: cria um novo repositório git, com dados armazenados no diretório <code>.git</code></li>\n<li><code>git status</code>: diz o que está acontecendo</li>\n<li><code>git add &lt;filename&gt;</code>: adiciona arquivos à área de staging</li>\n<li><code>git commit</code>: cria um novo commit\n<ul>\n<li>Escreva <a href=\"https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html\">boas mensagens de commit</a>!</li>\n<li>Ainda mais razões para escrever <a href=\"https://chris.beams.io/posts/git-commit/\">boas mensagens de commit</a>!</li>\n</ul></li>\n<li><code>git log</code>: mostra um log achatado do histórico</li>\n<li><code>git log --all --graph --decorate</code>: visualiza o histórico como um DAG</li>\n<li><code>git diff &lt;filename&gt;</code>: mostra mudanças que você fez relativas à área de staging</li>\n<li><code>git diff &lt;revision&gt; &lt;filename&gt;</code>: mostra diferenças em um arquivo entre snapshots</li>\n<li><code>git checkout &lt;revision&gt;</code>: atualiza HEAD e branch atual</li>\n</ul>\n\n<h2>Branching e merging</h2>\n\n<p>Branching permite que você \"bifurque\" o histórico de versão. Pode ser útil para trabalhar em funcionalidades independentes ou correções de bugs em paralelo. O comando <code>git branch</code> pode ser usado para criar novos branches; <code>git checkout -b &lt;branch name&gt;</code> cria um branch e faz checkout dele.</p>\n\n<p>Merging é o oposto de branching: ele permite que você combine históricos de versão bifurcados, por exemplo, mesclando um branch de funcionalidade de volta ao master. O comando <code>git merge</code> é usado para mesclar.</p>\n\n<ul>\n<li><code>git branch</code>: mostra branches</li>\n<li><code>git branch &lt;name&gt;</code>: cria um branch</li>\n<li><code>git checkout -b &lt;name&gt;</code>: cria um branch e muda para ele\n<ul>\n<li>mesmo que <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>: mescla no branch atual</li>\n<li><code>git mergetool</code>: usa uma ferramenta sofisticada para ajudar a resolver conflitos de merge</li>\n<li><code>git rebase</code>: rebaseia conjunto de patches em uma nova base</li>\n</ul>\n\n<h2>Remotos</h2>\n\n<ul>\n<li><code>git remote</code>: lista remotos</li>\n<li><code>git remote add &lt;name&gt; &lt;url&gt;</code>: adiciona um remoto</li>\n<li><code>git push &lt;remote&gt; &lt;local branch&gt;:&lt;remote branch&gt;</code>: envia objetos para o remoto e atualiza referência remota</li>\n<li><code>git branch --set-upstream-to=&lt;remote&gt;/&lt;remote branch&gt;</code>: configura correspondência entre branch local e remoto</li>\n<li><code>git fetch</code>: recupera objetos/referências de um remoto</li>\n<li><code>git pull</code>: mesmo que <code>git fetch; git merge</code></li>\n<li><code>git clone</code>: baixa repositório de um remoto</li>\n</ul>\n\n<h2>Desfazer</h2>\n\n<ul>\n<li><code>git commit --amend</code>: edita o conteúdo/mensagem de um commit</li>\n<li><code>git reset HEAD &lt;file&gt;</code>: remove um arquivo da staging</li>\n<li><code>git checkout -- &lt;file&gt;</code>: descarta mudanças</li>\n</ul>\n\n<h1>Git Avançado</h1>\n\n<ul>\n<li><code>git config</code>: Git é <a href=\"https://git-scm.com/docs/git-config\">altamente customizável</a></li>\n<li><code>git clone --depth=1</code>: clone raso, sem todo o histórico de versão</li>\n<li><code>git add -p</code>: staging interativo</li>\n<li><code>git rebase -i</code>: rebasing interativo</li>\n<li><code>git blame</code>: mostra quem editou por último qual linha</li>\n<li><code>git stash</code>: remove temporariamente modificações do diretório de trabalho</li>\n<li><code>git bisect</code>: busca binária no histórico (por exemplo, para regressões)</li>\n<li><code>.gitignore</code>: <a href=\"https://git-scm.com/docs/gitignore\">especifica</a> arquivos intencionalmente não rastreados para ignorar</li>\n</ul>\n\n<h1>Diversos</h1>\n\n<ul>\n<li><strong>GUIs</strong>: existem muitos <a href=\"https://git-scm.com/downloads/guis\">clientes GUI</a> por aí para Git. Pessoalmente não os usamos e usamos a interface de linha de comando em vez disso.</li>\n<li><strong>Integração com Shell</strong>: é super útil ter um status do Git como parte do seu prompt de shell (<a href=\"https://github.com/olivierverdier/zsh-git-prompt\">zsh</a>, <a href=\"https://github.com/magicmonty/bash-git-prompt\">bash</a>). Frequentemente incluído em frameworks como <a href=\"https://github.com/ohmyzsh/ohmyzsh\">Oh My Zsh</a>.</li>\n<li><strong>Integração com Editor</strong>: similarmente ao acima, integrações úteis com muitas funcionalidades. <a href=\"https://github.com/tpope/vim-fugitive\">fugitive.vim</a> é o padrão para Vim.</li>\n<li><strong>Workflows</strong>: ensinamos o modelo de dados, mais alguns comandos básicos; não dissemos quais práticas seguir ao trabalhar em grandes projetos (e existem <a href=\"https://nvie.com/posts/a-successful-git-branching-model/\">muitas</a> <a href=\"https://www.endoflineblog.com/gitflow-considered-harmful\">abordagens</a> <a href=\"https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow\">diferentes</a>).</li>\n<li><strong>GitHub</strong>: Git não é GitHub. GitHub tem uma maneira específica de contribuir código para outros projetos, chamada <a href=\"https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests\">pull requests</a>.</li>\n<li><strong>Outros provedores Git</strong>: GitHub não é especial: existem muitos hosts de repositório Git, como <a href=\"https://about.gitlab.com/\">GitLab</a> e <a href=\"https://bitbucket.org/\">BitBucket</a>.</li>\n</ul>\n\n<h1>Recursos</h1>\n\n<ul>\n<li><a href=\"https://git-scm.com/book/en/v2\">Pro Git</a> é <strong>leitura altamente recomendada</strong>. Passar pelos Capítulos 1--5 deve ensinar a maior parte do que você precisa para usar Git proficientemente, agora que você entende o modelo de dados. Os capítulos posteriores têm algum material interessante e avançado.</li>\n<li><a href=\"https://ohshitgit.com/\">Oh Shit, Git!?!</a> é um guia curto sobre como se recuperar de alguns erros comuns do Git.</li>\n<li><a href=\"https://eagain.net/articles/git-for-computer-scientists/\">Git for Computer Scientists</a> é uma explicação curta do modelo de dados do Git, com menos pseudocódigo e mais diagramas sofisticados do que estas notas de aula.</li>\n<li><a href=\"https://jwiegley.github.io/git-from-the-bottom-up/\">Git from the Bottom Up</a> é uma explicação detalhada dos detalhes de implementação do Git além de apenas o modelo de dados, para os curiosos.</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> é um jogo baseado em navegador que ensina Git.</li>\n</ul>",
  "source_hash": "sha256:1882fed561269610d4ac35bc5a461efe73f8481070dd58a45db04a8899598a98",
  "model": "claude-sonnet-4-5-20250929",
  "generated_at": "2026-01-02T04:09:20.141757+00:00"
}