-
1. Primeiros Passos
- 1.1 Sobre Controle de Versão
- 1.2 Uma Breve História do Git
- 1.3 O que é Git?
- 1.4 A Linha de Comando
- 1.5 Instalando o Git
- 1.6 Configuração inicial do Git
- 1.7 Obtendo Ajuda
- 1.8 Resumo
-
2. Fundamentos do Git
-
3. Ramificação (Branching) no Git
-
4. Git no Servidor
- 4.1 Os Protocolos
- 4.2 Colocando o Git em um Servidor
- 4.3 Gerando a sua Chave Pública SSH
- 4.4 Configurando o Servidor
- 4.5 Git Daemon
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 Opções de Hospedagem de Terceiros
- 4.10 Resumo
-
5. Git Distribuído
-
6. GitHub
-
7. Ferramentas do Git
- 7.1 Seleção de Revisão
- 7.2 Área de Stage Interativa
- 7.3 Fazendo Stash e Limpando
- 7.4 Assinando Seu Trabalho
- 7.5 Pesquisando (Searching)
- 7.6 Reescrevendo o Histórico
- 7.7 Reset Desmistificado
- 7.8 Merging Avançado
- 7.9 Rerere
- 7.10 Depurando com Git
- 7.11 Submódulos
- 7.12 Empacotando (Bundling)
- 7.13 Substituir (Replace)
- 7.14 Armazenamento de Credenciais
- 7.15 Resumo
-
8. Customizando o Git
- 8.1 Configuração do Git
- 8.2 Atributos do Git
- 8.3 Hooks do Git
- 8.4 Uma Política de Exemplo Imposta pelo Git
- 8.5 Resumo
-
9. Git e Outros Sistemas
- 9.1 Git como Cliente
- 9.2 Migrando para o Git
- 9.3 Resumo
-
10. Git Internals (Por Dentro do Git)
- 10.1 Encanamento e Porcelana (Plumbing and Porcelain)
- 10.2 Objetos do Git
- 10.3 Referências do Git
- 10.4 Arquivos de pacote (Packfiles)
- 10.5 Refspec
- 10.6 Protocolos de Transferência
- 10.7 Manutenção e Recuperação de Dados
- 10.8 Variáveis de Ambiente
- 10.9 Resumo
-
A1. Appendix A: O Git em Outros Ambientes
- A1.1 Interfaces Gráficas
- A1.2 Git no Visual Studio
- A1.3 Git no Visual Studio Code
- A1.4 Git no IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine
- A1.5 Git no Sublime Text
- A1.6 Git no Bash
- A1.7 Git no Zsh
- A1.8 Git no PowerShell
- A1.9 Resumo
-
A2. Appendix B: Incorporando o Git nos seus Aplicativos
- A2.1 Linha de Comando do Git
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
A3. Appendix C: Comandos do Git
- A3.1 Configuração e Ajustes
- A3.2 Obtendo e Criando Projetos
- A3.3 Snapshots Básicos
- A3.4 Branches e Merges
- A3.5 Compartilhando e Atualizando Projetos
- A3.6 Inspeção e Comparação
- A3.7 Depuração
- A3.8 Patches
- A3.9 E-mail
- A3.10 Sistemas Externos
- A3.11 Administração
- A3.12 Comandos de Encanamento
10.2 Git Internals (Por Dentro do Git) - Objetos do Git
Objetos do Git
O Git é um sistema a caráter num arquivo endereçável por conteúdos da sua base. Ótimo. O que isto significa afinal? Quer dizer o que em decorrência da forma central do Git que lhe baseia consiste nas instâncias num simples valor chave pelas armazenagens aos dados. Isso significa que você pode inserir qualquer conteúdo em um repositório Git, e o Git devolverá uma chave exclusiva para recuperá-lo mais tarde.
Para demonstrar, examinemos git hash-object, que recebe dados, armazena-os em .git/objects (o banco de dados de objetos) e devolve a chave que referencia esse objeto.
Primeiro, inicialize um repositório Git e verifique se não há nada em objects:
$ git init test
Initialized empty Git repository in /tmp/test/.git/
$ cd test
$ find .git/objects
.git/objects
.git/objects/info
.git/objects/pack
$ find .git/objects -type f
O Git inicializou o diretório objects diretamente e criou os subdiretórios pack e info dentro dele, mas não há nenhum arquivo regular.
Agora, use git hash-object para criar e armazenar um novo objeto:
$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4
Em sua forma mais simples, git hash-object recebe o conteúdo e retorna a chave que seria usada para armazená-lo.
A opção -w instrui o comando a também gravar o objeto no banco de dados.
--stdin instrui git hash-object a obter o conteúdo da entrada padrão; sem ela, o comando esperaria um nome de arquivo.
A saída é um hash de checksum com 40 caracteres. Esse é o hash SHA-1 – um checksum do conteúdo e de um cabeçalho, sobre o qual você aprenderá em breve. Agora você pode ver como o Git armazenou seus dados:
$ find .git/objects -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
Ao examinar novamente objects, você verá um arquivo para o novo conteúdo.
O Git armazena inicialmente cada conteúdo em um arquivo nomeado com o checksum SHA-1 do conteúdo e de seu cabeçalho.
O subdiretório é nomeado com os dois primeiros caracteres do SHA-1, e o nome do arquivo são os 38 caracteres restantes.
Você pode examinar o banco de dados de objetos com git cat-file.
Esse comando é um canivete suíço para a inspeção de objetos do Git.
A opção -p faz cat-file descobrir o tipo do conteúdo e exibi-lo adequadamente:
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content
Agora, você pode adicionar conteúdo para o Git e recuperá-lo de volta. Você também pode fazer isso para conteúdos em arquivos. Por exemplo, você pode fazer um controle de versão simples em um arquivo. Primeiro, crie um arquivo e salve seus conteúdos em seu banco de dados:
$ echo 'version 1' > test.txt
$ git hash-object -w test.txt
83baae61804e65cc73a7201a7252750c76066a30
Depois, escreva alguns conteúdos novos nesse arquivo, e salve-o novamente:
$ echo 'version 2' > test.txt
$ git hash-object -w test.txt
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
O banco de dados agora contém as duas versões desse arquivo e o primeiro conteúdo:
$ find .git/objects -type f
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
Agora exclua a cópia local de test.txt e recupere a primeira versão do banco de dados:
$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt
$ cat test.txt
version 1
ou à segunda versão:
$ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a > test.txt
$ cat test.txt
version 2
Memorizar o SHA-1 de cada versão não é prático; além disso, você armazena apenas o conteúdo, não o nome do arquivo.
Esse tipo de objeto é chamado de blob.
Dado o SHA-1 de um objeto, obtenha seu tipo com git cat-file -t:
$ git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
blob
Objetos Tree
O próximo tipo é a tree, que armazena nomes e permite reunir arquivos. O Git armazena o conteúdo em uma maneira similar a um sistema de arquivos UNIX, porém um pouco simplificado. Todo o conteúdo é armazenado como objetos tree e blob, com as trees correspondendo a entradas de um diretório UNIX e blobs correspondendo mais ou menos a inodes ou conteúdos de arquivos. Uma tree contém entradas com o SHA-1 de um blob ou subtree, seu modo, tipo e nome. Suponha que a tree mais recente seja semelhante a esta:
$ git cat-file -p master^{tree}
100644 blob a906cb2a4a904a152e80877d4088654daad0c859 README
100644 blob 8f94139338f9404f26296befa88755fc2598c289 Rakefile
040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0 lib
A sintaxe master^{tree} especifica o objeto tree que é apontado pelo último commit em sua branch master .
Note que o subdiretório lib não é um blob, mas uma referência para outra tree:
$ git cat-file -p 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0
100644 blob 47c6340d6459e05787f644c2447d2595f5d3a54b simplegit.rb
|
Note
|
Dependendo do shell, No CMD, duplique o caractere de escape No ZSH, coloque a expressão entre aspas: |
Conceitualmente, os dados armazenados se parecem com isto:
Você pode criar facilmente a sua própria tree.
O Git normalmente cria uma tree a partir do estado da sua área de stage ou index e escrevendo uma série de objetos tree a partir dela.
Então, para criar um objeto tree, você primeiro precisa popular um index adicionando alguns arquivos.
Para criar um index com a primeira versão de test.txt, use git update-index.
Você usa esse comando para adicionar artificialmente a versão anterior do arquivo test.txt à nova área de stage.
Você precisa passar a ele a opção --add porque o arquivo ainda não existe em sua área de stage (você nem precisa ter uma área de stage ainda) e a opção --cacheinfo porque o arquivo que você está adicionando não está em seu diretório mas está no seu banco de dados.
Depois você especifica o modo, o SHA-1 e o nome do arquivo:
$ git update-index --add --cacheinfo 100644 \
83baae61804e65cc73a7201a7252750c76066a30 test.txt
Neste caso, você está especificando um modo 100644, o que significa se trata de um arquivo normal.
Outras opções são 100755, o que significa que é um arquivo executável; e 120000, que especifica um link simbólico.
O modo vem dos modos UNIX, mas esses três são os únicos válidos para arquivos (blobs) no Git; outros são usados para diretórios e submódulos.
Use git write-tree para gravar a área de stage em uma tree.
-w não é necessário – o comando cria uma tree a partir do index caso ela ainda não exista:
$ git write-tree
d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
100644 blob 83baae61804e65cc73a7201a7252750c76066a30 test.txt
Confirme que esse é um objeto tree usando git cat-file:
$ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579
tree
Você pode criar um novo arquivo tree com a segunda versão de test.txt, além de um novo arquivo:
$ echo 'new file' > new.txt
$ git update-index --cacheinfo 100644 \
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
$ git update-index --add new.txt
Sua área de stage agora tem a nova versão de test.txt, bem como o novo arquivo new.txt.
Escreva essa tree (grave o estado da área de stage ou index para um objeto) e veja como ela se parece:
$ git write-tree
0155eb4229851634a0f03eb265b69f5a2d56f341
$ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
Essa tree contém duas entradas, e o SHA-1 de test.txt é o da “versão 2” (1f7a7a).
Apenas por diversão, adicione a primeira tree como um subdiretório neste aqui.
Leia trees para a área de stage com git read-tree.
Use --prefix para ler uma tree como subtree:
$ git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ git write-tree
3c4e9cd789d88d8d89c1073707c3585e41b0e614
$ git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614
040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 bak
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
Se você criasse um diretório de trabalho a partir da nova tree que você criou, você teria os dois arquivos no nível mais alto do diretório de trabalho e um subdiretório chamado bak que conteria a primeira versão do arquivo test.txt.
Você pode pensar nos dados que o Git armazena para essas estruturas como sendo algo assim:
Objetos de Commit (Commit Objects)
Agora há três trees para os snapshots, mas é preciso lembrar seus três SHA-1 para recuperá-los. Você também não tem nenhuma informação sobre quem salvou os snapshots, quando eles foram salvos, ou porque eles foram salvos. Essas são informações básicas que o objeto commit armazena para você.
Para criar um objeto commit, você chama commit-tree e especifica o SHA-1 de uma única tree e quais objetos commit precedem diretamente ele, se houver.
Começando com a primeira tree que você escreveu:
$ echo 'First commit' | git commit-tree d8329f
fdf4fc3344e67ab068f836878b6c4951e3b15f3d
|
Note
|
Você irá obter um valor diferente para o hash por causa das diferentes hora de criação e dados do autor. Embora um commit possa ser reproduzido a partir desses dados, detalhes históricos do livro fazem os hashes impressos talvez não corresponderem aos commits apresentados. Substitua os hashes de commit e tag pelos seus próprios checksums posteriormente neste capítulo. |
Examine o novo objeto commit com git cat-file:
$ git cat-file -p fdf4fc3
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author Scott Chacon <schacon@gmail.com> 1243040974 -0700
committer Scott Chacon <schacon@gmail.com> 1243040974 -0700
First commit
Um commit especifica a tree do snapshot; os pais; autor e committer; uma linha em branco; e a mensagem.
A seguir, você irá escrever outros dois objetos commit, cada um referenciando o commit que veio diretamente antes dele:
$ echo 'Second commit' | git commit-tree 0155eb -p fdf4fc3
cac0cab538b970a37ea1e769cbbde608743bc96d
$ echo 'Third commit' | git commit-tree 3c4e9c -p cac0cab
1a410efbd13591db07496601ebc7a059dd55cfe9
Cada um dos três objetos commit aponta para uma das três trees de snapshot que você criou.
Curiosamente, você tem agora um histórico do Git real que você pode ver com o comando git log, se você executá-lo no SHA-1 do último commit:
$ git log --stat 1a410e
commit 1a410efbd13591db07496601ebc7a059dd55cfe9
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:15:24 2009 -0700
Third commit
bak/test.txt | 1 +
1 file changed, 1 insertion(+)
commit cac0cab538b970a37ea1e769cbbde608743bc96d
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:14:29 2009 -0700
Second commit
new.txt | 1 +
test.txt | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:09:34 2009 -0700
First commit
test.txt | 1 +
1 file changed, 1 insertion(+)
Incrível.
Você acabou de fazer operações de baixo nível para criar um histórico do Git sem usar nenhum dos comandos de front-end.
É isso que git add e git commit fazem: armazenam blobs, atualizam o index e gravam trees e commits que referenciam seus antecessores.
Blob, tree e commit são armazenados inicialmente como arquivos separados em .git/objects.
Estes são todos os objetos no diretório de exemplo, comentados com o que eles armazenam:
$ find .git/objects -type f
.git/objects/01/55eb4229851634a0f03eb265b69f5a2d56f341 # tree 2
.git/objects/1a/410efbd13591db07496601ebc7a059dd55cfe9 # commit 3
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a # test.txt v2
.git/objects/3c/4e9cd789d88d8d89c1073707c3585e41b0e614 # tree 3
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30 # test.txt v1
.git/objects/ca/c0cab538b970a37ea1e769cbbde608743bc96d # commit 2
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 # 'test content'
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579 # tree 1
.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 # new.txt
.git/objects/fd/f4fc3344e67ab068f836878b6c4951e3b15f3d # commit 1
Se você seguir as referências internas, você obterá um grafo de objetos mais ou menos como este:
Armazenamento de Objetos (Object Storage)
Um cabeçalho é armazenado com cada objeto enviado ao banco de dados. Vamos examinar como o Git armazena objetos. Você armazenará interativamente em Ruby um blob com a string “what is up, doc?”.
Você pode iniciar o modo interativo do Ruby com o comando irb:
$ irb
>> content = "what is up, doc?"
=> "what is up, doc?"
Primeiro, o Git constrói um cabeçalho que identifica o tipo – nesse caso, blob. Ele acrescenta um espaço, o tamanho em bytes e um byte nulo:
>> header = "blob #{content.bytesize}\0"
=> "blob 16\u0000"
O Git concatena o cabeçalho e o conteúdo original e então calcula o checksum SHA-1 do novo conteúdo.
Você pode calcular o valor SHA-1 de uma string em Ruby incluindo a biblioteca SHA1 digest com o comando require e então chamando Digest::SHA1.hexdigest() com a string:
>> store = header + content
=> "blob 16\u0000what is up, doc?"
>> require 'digest/sha1'
=> true
>> sha1 = Digest::SHA1.hexdigest(store)
=> "bd9dbf5aae1a3862dd1526723246b20206e5fc37"
Compare com a saída de git hash-object.
Usamos echo -n para não acrescentar uma quebra de linha.
$ echo -n "what is up, doc?" | git hash-object --stdin
bd9dbf5aae1a3862dd1526723246b20206e5fc37
O Git comprime o novo conteúdo com zlib, o que você pode fazer em Ruby com a biblioteca zlib.
Primeiro, você precisa incluir a biblioteca e então executar Zlib::Deflate.deflate() no conteúdo:
>> require 'zlib'
=> true
>> zlib_content = Zlib::Deflate.deflate(store)
=> "x\x9CK\xCA\xC9OR04c(\xCFH,Q\xC8,V(-\xD0QH\xC9O\xB6\a\x00_\x1C\a\x9D"
Por último, você irá salvar o seu conteúdo comprimido com zlib em um objeto no disco.
Você irá determinar o caminho do objeto que você quer escrever (sendo os dois primeiros caracteres do valor do SHA-1 o nome do subdiretório, e os últimos 38 caracteres sendo o nome do arquivo dentro desse diretório).
==== Objetos de Commit (Commit Objects) Em Ruby, você pode usar a função FileUtils.mkdir_p() para criar o subdiretório se ele não existir.
Depois, abra o arquivo com File.open() e escreva o conteúdo previamente comprimido com zlib no arquivo chamando write() no file handle resultante:
>> path = '.git/objects/' + sha1[0,2] + '/' + sha1[2,38]
=> ".git/objects/bd/9dbf5aae1a3862dd1526723246b20206e5fc37"
>> require 'fileutils'
=> true
>> FileUtils.mkdir_p(File.dirname(path))
=> ".git/objects/bd"
>> File.open(path, 'w') { |f| f.write zlib_content }
=> 32
Verifique o conteúdo com git cat-file:
---
$ git cat-file -p bd9dbf5aae1a3862dd1526723246b20206e5fc37
what is up, doc?
---
É isso - você criou um objeto blob do Git válido.
Todos os objetos são armazenados do mesmo jeito, apenas com diferentes tipos - em vez da string blob, o cabeçalho começará com commit ou tree. Além disso, embora o conteúdo de um blob possa ser quase qualquer coisa, os conteúdos de um commit ou tree estão formatados de uma forma muito específica.