Executando verificação de segurança...
1

[GIT] Modelo de objetos do Git - Parte 1

O que falar dessa ferramenta fantástica que todo desenvolvedor (e agora a maioria dos sysadmins) utiliza diariamente?!

Você já parou para pensar como é que ele funciona internamente para permitir um controle de versões tão robusto assim?

Vem comigo que você vai se surpreender com a simplicidade e com a genialidade do modelo de objetos do Git!

Atributos

Cada objeto no git possui ao menos 4 atributos:

  • tipo
  • tamanho
  • nome
  • conteúdo

O nome dos objetos no git é obtido a partir da representação sha1, com 40 dígitos, do seu conteúdo. Desta forma, dois arquivos com o mesmo conteúdo, mesmo se tiverem nomes diferentes, serão armazenados uma única vez na "base de dados" do git.

Exemplo:

Crie dois arquivos com o mesmo conteúdo:

echo "Conteudo identico" > file1
echo "Conteudo identico" > file2

Agora calcule o sha1 destes dois arquivos:

# file1
(printf "blob %s\0" $(cat file1 | wc -c); cat file1) | sha1sum | cut -b "1-40"

# file2
(printf "blob %s\0" $(cat file2 | wc -c); cat file2) | sha1sum | cut -b "1-40"

Reparou que o resultado é igual? Pois é desta forma que o git nomeia os seus objetos.

Onde ficam armazenados?

Os objetos do git ficam armazenados na pasta .git/objects da raiz do seu repositório.

Os dois primeiros dígitos do nome do objeto (sha1) definem o nome do subdiretório dentro de .git/objects e os 38 digitos restantes serão o nome do arquivo dentro deste subdiretório.

Exemplo:

Imagine que o sha1 de um arquivo seja 6889d19eff6b518f019dabc01de84024edf428af. O objeto git que representa este arquivo será armazenado em:

.git
  |-- objects
      |-- 68 (2 primeiros dígitos)
          |-- 89d19eff6b518f019dabc01de84024edf428af (38 dígitos restantes)

Tipos de objeto

De forma simples e direta o git possui 3 tipos principais de objetos. São eles: Blob, Tree e Commit.

Blob

Os objetos do tipo blob são utilizados para armazenar dados. Em resumo eles representam o conteúdo dos arquivos que estão armazenados dentro do repositório git.

Tree

Podemos fazer uma analogia aos objetos do tipo tree aos diretórios de um sistema de arquivos. Eles fazem referência aos blobs (arquivos) e a outros objetos do tipo tree (subdiretórios).
O objeto o tipo tree contém uma linha para cada arquivo ou subdiretório. Cada linha de um objeto do tipo tree contém:

  • As permissões do arquivo/subdiretório
  • O tipo de objeto (tree/blob)
  • O hash do objeto
  • O nome do arquivo (blob) ou subdiretório (tree)

Commit

Os commits podem ser imagiados como uma fotografia do repositório em um determinado momento na linha do tempo.

Um commit é composto por seus metadados (autor/committer, data e mensagem do commit), uma referência ao commit anterior a ele e uma referência à main tree (arvore raiz).

Visão geral

ObjectTypes

Vamos ver na prática?

Chega de teoria né?! Vamos ver na prática como isso ocorre?

Crie inicialize um novo repositório git local vazio:

mkdir teste
cd teste
git init

A pasta teste contem apenas uma subpasta chamada .git com os diretórios e arquivos default do git:

# tree -a .
.
└── .git
    ├── HEAD
    ├── config
    ├── description
    ├── hooks
    │   ├── applypatch-msg.sample
    │   ├── commit-msg.sample
    │   ├── fsmonitor-watchman.sample
    │   ├── post-update.sample
    │   ├── pre-applypatch.sample
    │   ├── pre-commit.sample
    │   ├── pre-merge-commit.sample
    │   ├── pre-push.sample
    │   ├── pre-rebase.sample
    │   ├── pre-receive.sample
    │   ├── prepare-commit-msg.sample
    │   ├── push-to-checkout.sample
    │   └── update.sample
    ├── info
    │   └── exclude
    ├── objects
    │   ├── info
    │   └── pack
    └── refs
        ├── heads
        └── tags

Agora vamos criar um primeiro arquivo:

echo "Conteudo inicial" > file1

Se você listar novamente o conteúdo da pasta .git/objects irá observar que ela permanece inalterada.

Isto ocorre por que ainda não salvamos nada no repositório git, ou seja, ainda não adicionamos o arquivo ao git.

# git status
On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	file1

nothing added to commit but untracked files present (use "git add" to track)

Vamos então adicionar o arquivo no git:

git add file1

Agora, se listarmos novamente o conteúdo da pasta .git/objects vamos observar que o git criou um novo arquivo:

tree -a .
.
├── .git
...
│   ├── objects
│   │   ├── c1
│   │   │   └── 93db3a62e2f09e1d214e671c8747d5deee566a
│   │   ├── info
│   │   └── pack
...
└── file1

O comando git cat-file -t nos permite verificar qual o tipo deste arquivo. Passamos como argumento para este comando os 40 digitos que representam o sha1:

# git cat-file -t c193db3a62e2f09e1d214e671c8747d5deee566a
blob

Ou seja, até o momento o git apenas criou o blob que representa o conteúdo do arquivo file1.

Vamos agora criar um novo arquivo com o mesmo conteúdo e adicioná-lo ao git:

echo "Conteudo inicial" > file2
git add file2

Repare que o git não criou um segundo objeto:

.
├── .git
...
│   ├── objects
│   │   ├── c1
│   │   │   └── 93db3a62e2f09e1d214e671c8747d5deee566a
│   │   ├── info
│   │   └── pack
...
├── file1
└── file2

Por que isso ocorre? Como o git representa o blob a partir do SHA1 do conteúdo do objeto e ambos os arquivos possuem o mesmo conteúdo, o git não duplica o objeto representando-o apenas uma única vez. Genial né?!

Agora vamos fazer um commit e ver o que acontece...

# git commit -m "Commit inicial"
[master (root-commit) 8c55d9d] Commit inicial
 2 files changed, 2 insertions(+)
 create mode 100644 file1
 create mode 100644 file2

Agora o git criou dois novos objetos:

tree -a .
.
├── .git
...
│   ├── objects
│   │   ├── 20
│   │   │   └── 31650a0a72f28b71fffc6cca7f0fc5b10a4c12
│   │   ├── 8c
│   │   │   └── 55d9d2dcb867dc132fceda4c19e4c590d49d8a
│   │   ├── c1
│   │   │   └── 93db3a62e2f09e1d214e671c8747d5deee566a
│   │   ├── info
│   │   └── pack
...
├── file1
└── file2

Vamos ver de que tipo são eles?

# git cat-file -t 2031650a0a72f28b71fffc6cca7f0fc5b10a4c12
tree

# git cat-file -t 8c55d9d2dcb867dc132fceda4c19e4c590d49d8a
commit

Ou seja, além do blob agora temos uma tree e um commit!

O comando git cat-file -p nos permite visualizar o conteúdo destes arquivos. Vamos conferir?

blob:

# git cat-file -p c193db3a62e2f09e1d214e671c8747d5deee566a
Conteudo inicial

tree:

# git cat-file -p 2031650a0a72f28b71fffc6cca7f0fc5b10a4c12
100644 blob c193db3a62e2f09e1d214e671c8747d5deee566a	file1
100644 blob c193db3a62e2f09e1d214e671c8747d5deee566a	file2

commit:

# git cat-file -p 8c55d9d2dcb867dc132fceda4c19e4c590d49d8a
tree 2031650a0a72f28b71fffc6cca7f0fc5b10a4c12
author Autor do Commit <[email protected]> 1669659806 -0300
committer Autor do Commit <[email protected]> 1669659806 -0300

Commit inicial

Observe que:

  • O tree faz referênia ao mesmo blob, porém, atribui nomes diferentes aos arquivos
  • O commit faz referência à tree que por sua vez aponta para os dois arquivos

O que acontece agora se alterarmos o conteúdo de um os arquivos e fazer um novo commit? Vamos ver?

echo "Conteudo alterado" > file1
git add file1
git commit -m "Primeira alteracao"

Agora temos 3 novos objetos:

tree -a .
.
├── .git
...
│   ├── objects
│   │   ├── 0a
│   │   │   └── 182f1783c1714c9cd4b0c54114fe8a8ecddfd1 (novo)
│   │   ├── 20
│   │   │   └── 31650a0a72f28b71fffc6cca7f0fc5b10a4c12
│   │   ├── 8c
│   │   │   └── 55d9d2dcb867dc132fceda4c19e4c590d49d8a
│   │   ├── 8f
│   │   │   └── 496dcf12ad1f1cea92ec68ce5b930c1d60f96a (novo)
│   │   ├── c1
│   │   │   └── 93db3a62e2f09e1d214e671c8747d5deee566a
│   │   ├── f3
│   │   │   └── a8128d402ffda65e73f01c1a9119ba0e700196 (novo)
...
├── file1
└── file2

Vamos entender o que são estes 3 novos objetos com o comando git cat-file -t:

# git cat-file -t f3a8128d402ffda65e73f01c1a9119ba0e700196
blob

# git cat-file -t 0a182f1783c1714c9cd4b0c54114fe8a8ecddfd1
tree

# git cat-file -t 8f496dcf12ad1f1cea92ec68ce5b930c1d60f96a
commit

Vamos usar o git cat-file -p para ver o que tem nestes novos objetos:

blob:

# git cat-file -p f3a8128d402ffda65e73f01c1a9119ba0e700196
Conteudo alterado

tree:

# git cat-file -p 0a182f1783c1714c9cd4b0c54114fe8a8ecddfd1
100644 blob f3a8128d402ffda65e73f01c1a9119ba0e700196	file1
100644 blob c193db3a62e2f09e1d214e671c8747d5deee566a	file2

commit:

# git cat-file -p 8f496dcf12ad1f1cea92ec68ce5b930c1d60f96a
tree 0a182f1783c1714c9cd4b0c54114fe8a8ecddfd1
parent 8c55d9d2dcb867dc132fceda4c19e4c590d49d8a
author Autor do Commit <[email protected]> 1669660492 -0300
committer Autor do Commit <[email protected]> 1669660492 -0300

Primeira alteracao

O git criou um novo comimt, apontando para uma nova tree e, como mudamos o conteúdo do file1, criou tambem um novo blob. Agora esta nova tree aponta para dois blobs diferentes pois o file1 e file2 possuem conteúdos distintos,

O git é ou não é genial?!

Para facilitar vou deixar aqui um link para um mapa mental no qual você pode revisar todos estes conceitos além de conferir muitos outros comandos e ferramentas úteis!

Em breve postarei uma parte 2 deste artigo falando sobre as referências no git (branches, tags, remotes, etc)...

Carregando publicação patrocinada...