Até agora, nós já conversamos sobre commits, trees e objetos. Vimos como eles se ligam ao modelo lógico de objeto, bem como são representados no disco no diretório .git/objects.
Mas armazenar cada versão de cada arquivo em arquivos separados (embora compactados) será um enorme desperdício de espaço, certo? Sim, há algum compartilhamento de conteúdo idêntico entre commits, mas o Git dificilmente seria o armazenamento eficiente pelo qual é conhecido na sua estrutura.
Pack files
Felizmente, o Git tem a capacidade de fazer um merge de vários objetos em arquivos individuais, conhecidos como pack files. Estes são, em essência, vários objetos armazenados com um esquema eficiente de compressão delta como um único arquivo compactado. Você pode pensar nisso como algo semelhante a um arquivo Zip de vários objetos, que o Git pode extrair de forma eficiente quando necessário.
Pack Files são armazenados no diretório .git/objects/pack/. Para novos projetos, é provável que esteja vazio; o que acontece é que o Git começa adicionando todos os arquivos como objetos não-empacotados ou objetos soltos. Uma das razões pelas quais isso acontece é que, como você está trabalhando através de mudanças, é bastante provável que tenha que reescrever vários arquivos (blobs) e diretórios (trees) antes de comitar. Na verdade, cada vez que você fizer um git add para colocar um arquivo em stage, você está criando um novo objeto na estrutura de objetos soltos.
O que acontece é que periodicamente (ou sob demanda do usuário), o Git irá executar uma compressão nos objetos soltos. Isso é acionado quer por um pedido git gc, ou automaticamente após vários limiares terem sido cumpridos. O Git irá criar o pack file e remover os arquivos de objetos soltos.
(master) $ touch empty
(master) $ git add empty
(master) $ git commit -m "Empty"
[master (root-commit) cab1545] Empty
0 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 empty
(master) $ ls .git/objects/
41 ca e6 info pack
(master) $ ls .git/objects/pack/
Você pode reconhecer o diretório ‘e6’ como sendo o prefixo do arquivo vazio no Git, que nós vimos antes e é identificado por e69de29bb2d1d6434b8b29ae775ad8c2e48c5391. No entanto, nessa fase, não há o conteúdo no diretório pack. O que acontece se o empacotarmos?
(master) $ git gc
Counting objects: 3, done.
Writing objects: 100% (3/3), done.
Total 3 (delta 0), reused 0 (delta 0)
(master) $ ls .git/objects/
info pack
(master) $ ls .git/objects/pack/
pack-0c1ff4e31096ebcdb390b30ebe763ae15de650eb.idx
pack-0c1ff4e31096ebcdb390b30ebe763ae15de650eb.pack
Para onde que os objetos foram? Bem, eles foram compactados em um pack só de leitura. Nós ainda podemos endereçá-los usando seu hash, mesmo se eles não forem mais arquivos soltos:
(master) $ git cat-file -t e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
blob
(master) $ git cat-file -s e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
0
O conteúdo do pack no disco é menor do que o conjunto de arquivos soltos (embora em exemplos triviais como este, não há muita diferença entre eles). O pack file é na verdade formado de duas entradas; o índice (.idx) e os arquivos pack (.pack). Embora estes últimos armazenem dados, os primeiros armazenam uma lista de tabela-de-conteúdo de objetos contidos dentro do pacote em si:
(master) $ hexdump .git/objects/pack/pack-0c1ff4e31096ebcdb390b30ebe763ae15de650eb.idx
0000000 ff 74 4f 63 00 00 00 02 00 00 00 00 00 00 00 00
0000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
0000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01
0000110 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01
0000330 00 00 00 02 00 00 00 02 00 00 00 02 00 00 00 02
00003a0 00 00 00 03 00 00 00 03 00 00 00 03 00 00 00 03
0000400 00 00 00 03 00 00 00 03 41 7c 01 c8 79 5a 35 b8
0000410 e8 35 11 3a 85 a5 c0 c1 c7 7f 67 fb ca b1 54 54
0000420 f6 75 88 fb 81 27 96 75 09 38 77 09 7a 75 21 de
0000430 e6 9d e2 9b b2 d1 d6 43 4b 8b 29 ae 77 5a d8 c2
0000440 e4 8c 53 91 7d 73 67 fc 61 d0 d2 e8 6e 76 00 29
0000450 00 00 00 85 00 00 00 0c 00 00 00 b1 f5 5c c2 b5
0000460 8e 21 45 55 02 06 88 64 e9 1b 8b 52 75 c4 46 3d
0000470 57 df 0b ca 6a 8f f3 57 6c d4 97 78 df 30 1d bc
0000480 4d 24 1e a4
0000484
Você vai reconhecer no hex dump do índice o “objeto vazio”, armazenado em Git (e69d.. 5391), juntamente com a tree que contém o arquivo vazio (417c…67fb).
A finalidade do arquivo de índice é realmente um marcador para dizer ao Git que o objeto correspondente está neste pack file. Nesse caso, só temos um pack file, mas grandes repositórios terão vários desses arquivos. O índice permite ao Git carregar muitos arquivos pequenos para determinar a resposta à pergunta “Onde estão esses objetos?”, para que ele possa extraí-los da maneira mais eficiente.
Resumo
Embora o Git armazene objetos em forma solta enquanto você trabalha em novas mudanças, ele irá compactá-los em pack files para tirar o maior proveito de compressões delta. Isso acontece quando você executa um git gc ou quando vários limiares são cumpridos automaticamente. Ele também explica por que os requisitos de armazenamento do Git seguem uma estrutura cerrada; cada vez que a rampa sobe, é porque os novos objetos estão sendo criados, e cada vez que ela desce, é porque um pack foi executado e novos pack files foram criados (juntamente com os objetos correspondentes sendo excluídos).
?
Texto original disponível em http://alblue.bandlem.com/2011/09/git-tip-of-week-objects-and-packfiles.html