Dev (Back & Front)ARTIGO

Dica Git da semana: Revendo Índices

Na última dica, visitamos o objetivo do índice. Mas o que é isso realmente?

Na verdade, não é uma tree, como me referi da última vez. Ou seja, você não pode iterar os conteúdos com git ls-tree. Ele aponta para blobs no objeto do banco de dados, no entanto. Então, por que precisamos de um tipo diferente de objeto para se referir ao índice?

Algumas das razões são orientadas para o desempenho. Sempre que você faz um diff (ou outra operação repository-wide), o Git precisa de forma rápida e eficiente calcular se o estado da working tree mudou desde o último índice. Algumas ferramentas, incluindo o prompt shell, precisam ser capazes de determinar se a working tree é suja ou não rapidamente:

(master) $ git status
# On branch master
# Changes not staged for commit:
# (use "git add <file>…" to update what will be committed)
# (use "git checkout -- <file>…" to discard changes in working directory)
#
# modified: example
#
no changes added to commit (use "git add" and/or "git commit -a")
(master) $ export GIT_PS1_SHOWDIRTYSTATE=true
(master *) $

Se o Git só sabe se a tree está suja fazendo uma caminhada completa pelos de conteúdo (e calculando seus hashes SHA1), essa operação seria proibitivamente dispendiosa. Felizmente, o Git tem um número de otimizações que lhe permite evitar isso.

O índice armazena não apenas os nomes de arquivos, mas também o tempo da última modificação dos mesmos. Como resultado, o Git sabe se houve quaisquer alterações nos timestamps, por iteração de metadados dos arquivos e comparando os timestamps com os do índice. Se estiver faltando um arquivo do índice, ele é representado como uma adição. Se estiver faltando um arquivo do disco, ele é representado como uma exclusão. Se a data de modificação de um arquivo é diferente, em seguida, este é representado como uma modificação.

Além de armazenar timestamps, o índice armazena os hashes SHA1 de cada blob. Isso permite que o índice se atualize sozinho, se o arquivo for revertido para um estado anterior, mas com um timestamp mais recente.

Finalmente, o índice é também utilizado para merges de processamento. No índice, existe um conceito de ter números de índice múltiplos (ou números de fase). Normalmente, apenas 0 é utilizado, uma vez que ele  representa o estado atual da working tree. No entanto, se um conflito de merge surge, então o índice é usado para resolver a ambiguidade do estado dos arquivos em cada nível. Se você tem um conflito, então stage 0 é usado para representar a working tree atual, o stage 1 é utilizado para a sua mudança, então o stage 2 e 3 para as outras diferenças. Você pode ver o número do stage, executando git ls-files –stage (ou -s):

(master) $ git status
(master) $ git ls-files -s
100644 ce013625030ba8dba906f756967f9e9ca394464a 0 example
(master) $ git pull # with known conflict
Auto-merging example
CONFLICT (content): Merge conflict in example
Automatic merge failed; fix conflicts and then commit the result.
(master|MERGING) $ git ls-files -s
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 1 example
100644 a895c0db0a627cb9451ae390a2a0922495dbb161 2 example
100644 13940d48c3d3693113f7543d4fc5423a916ef55d 3 example

O arquivo do stage 1 aqui é o lugar dos dois arquivos derivados, para que possamos fazer 1..2 e 1..3 diffs para descobrir qual cada lado da tree mudou desde então. (Os mais astutos de vocês irão reconhecer e69…391 como o arquivo vazio.) Nós podemos mostrar o conteúdo dessas versões dos arquivos, ou carregá-los em uma ferramenta de comparação de 3 vias, se você tem tal coisa:

(master) $ git show e69de #empty file
(master) $ git show a895c
Left tree
(master) $ git show 13940
Right tree

Claro que, normalmente, o Git vai lidar com os diffs para você, e você não precisa se preocupar com as mudanças específicas, nem extrair o conteúdo de fora do índice. Mas ele ressalta o fato de que quando você terminar de fazer um merge de um único arquivo, executando git add no arquivo, coloca uma cópia em stage 0 do índice, removendo os índices 1,2,3:

(master|MERGING) $ git add example
(master|MERGING) $ git ls-files -s
100644 319c128291474d30f48e721ca87bd10425e8e296 0 example

É por isso que fazer merge de grandes alterações em conflito com o Git é fácil. Cada arquivo pode ser abordado em uma base “arquivo por arquivo”; quando você terminar de fazer o merge de um arquivo, você pode fazer um add dele no índice, que registra os seus conteúdos, bem como remove os outros arquivos do status de merge. Fazer o merge de muitos arquivos torna-se então um exercício de fusão um por um, e adições conforme você continua. E uma vez que todos eles são transitoriamente armazenados no índice, você pode continuar adicionando-os até que esteja pronto para executar um git commit.

?

Texto original disponível em http://alblue.bandlem.com/2011/10/git-tip-of-week-index-revisited.html

é guru em Eclipse e fanático pelo Mac.

Ver perfil