Como Verificar se um Commit Git é Acessível a partir do HEAD

GitBeginner
Pratique Agora

Introdução

Neste laboratório, você aprenderá como determinar se um commit Git específico é alcançável a partir do HEAD atual. Esta é uma habilidade fundamental para entender o histórico do seu repositório e identificar commits que fazem parte da linha de desenvolvimento ativa.

Exploraremos dois métodos principais: usar o comando git log --ancestry-path para visualizar a linhagem de commits entre dois pontos e empregar o comando git merge-base --is-ancestor para uma verificação direta de ancestralidade. Você praticará essas técnicas configurando um repositório de amostra com múltiplos branches e commits, incluindo alguns que são intencionalmente tornados inalcançáveis a partir do HEAD, permitindo que você teste e confirme sua compreensão.

Usar git log --ancestry-path

Nesta etapa, exploraremos como usar o comando git log --ancestry-path. Este comando é útil para visualizar o histórico de commits ao longo de um caminho específico entre dois commits. Ele ajuda você a entender a linhagem das alterações.

Primeiro, vamos criar um repositório Git simples e fazer alguns commits para configurar um cenário para usar --ancestry-path.

Navegue até o diretório do seu projeto:

cd ~/project

Crie um novo diretório para este laboratório e inicialize um repositório Git:

mkdir ancestry-lab
cd ancestry-lab
git init

Você deve ver a saída indicando que um repositório Git vazio foi inicializado:

Initialized empty Git repository in /home/labex/project/ancestry-lab/.git/

Agora, vamos criar um arquivo e fazer o primeiro commit:

echo "Initial content" > file1.txt
git add file1.txt
git commit -m "Initial commit"

Você verá a saída confirmando o commit:

[master (root-commit) <commit-hash>] Initial commit
 1 file changed, 1 insertion(+)
 create mode 100644 file1.txt

Em seguida, vamos fazer outro commit:

echo "Adding more content" >> file1.txt
git add file1.txt
git commit -m "Add more content"

Você verá a saída para o segundo commit:

[master <commit-hash>] Add more content
 1 file changed, 1 insertion(+)

Agora, vamos criar um novo branch e fazer um commit nesse branch:

git branch feature
git checkout feature
echo "Feature work" > file2.txt
git add file2.txt
git commit -m "Add feature file"

Você verá a saída para criar o branch, alternar para ele e o novo commit:

Switched to a new branch 'feature'
[feature <commit-hash>] Add feature file
 1 file changed, 1 insertion(+)
 create mode 100644 file2.txt

Vamos voltar para o branch master e fazer outro commit:

git checkout master
echo "More master work" >> file1.txt
git add file1.txt
git commit -m "More master content"

Você verá a saída para alternar branches e o novo commit:

Switched to branch 'master'
[master <commit-hash>] More master content
 1 file changed, 1 insertion(+)

Agora temos um histórico de commits com um branch. Vamos usar git log para ver o histórico completo:

git log --all --decorate --oneline

Você verá um log semelhante a este (hashes de commit e ordem podem variar):

<commit-hash> (HEAD -> master) More master content
<commit-hash> Add more content
<commit-hash> (feature) Add feature file
<commit-hash> Initial commit

Agora, vamos usar git log --ancestry-path. Este comando requer duas referências de commit. Ele mostrará os commits que são ancestrais do segundo commit e descendentes do primeiro commit.

Vamos encontrar o hash do commit para "Initial commit" e "More master content". Você pode obtê-los da saída git log --all --decorate --oneline. Substitua <initial-commit-hash> e <master-commit-hash> pelos hashes reais da sua saída.

git log --ancestry-path <initial-commit-hash> <master-commit-hash> --oneline

Este comando mostrará os commits no caminho do commit inicial para o commit mais recente no branch master. Você deve ver os commits "Initial commit", "Add more content" e "More master content".

A opção --ancestry-path é útil para entender a linha direta de desenvolvimento entre dois pontos no seu histórico, ignorando commits de outros branches que podem ter sido mesclados posteriormente.

Executar git merge-base --is-ancestor

Nesta etapa, aprenderemos sobre git merge-base --is-ancestor. Este comando é usado para verificar se um commit é um ancestral de outro commit. É uma verificação simples que retorna um código de status (0 para verdadeiro, 1 para falso) em vez de gerar informações de commit. Isso é particularmente útil em scripts ou para verificações rápidas.

Continuaremos usando o repositório ancestry-lab que criamos na etapa anterior. Certifique-se de estar no diretório correto:

cd ~/project/ancestry-lab

Recorde o histórico de commits da etapa anterior. Temos commits nos branches master e feature.

Vamos encontrar os hashes de commit para o "Initial commit" e o commit mais recente no branch master ("More master content"). Você pode usar git log --oneline para ver os commits recentes.

git log --oneline

A saída será semelhante a:

<master-commit-hash> (HEAD -> master) More master content
<commit-hash> Add more content
<initial-commit-hash> Initial commit

Agora, vamos usar git merge-base --is-ancestor para verificar se o "Initial commit" é um ancestral do commit mais recente em master. Substitua <initial-commit-hash> e <master-commit-hash> pelos hashes reais.

git merge-base --is-ancestor <initial-commit-hash> <master-commit-hash>
echo $?

O comando echo $? imprime o status de saída do comando anterior. Se o primeiro commit for um ancestral do segundo, o status de saída será 0. Caso contrário, será 1.

Como o "Initial commit" é de fato um ancestral do commit mais recente em master, a saída de echo $? deve ser 0.

Agora, vamos verificar se o "Initial commit" é um ancestral do commit mais recente no branch feature. Primeiro, encontre o hash do commit para o commit "Add feature file".

git log --all --decorate --oneline

A saída será semelhante a:

<master-commit-hash> (HEAD -> master) More master content
<commit-hash> Add more content
<feature-commit-hash> (feature) Add feature file
<initial-commit-hash> Initial commit

Agora, use git merge-base --is-ancestor para verificar se o "Initial commit" é um ancestral do commit "Add feature file". Substitua <initial-commit-hash> e <feature-commit-hash> pelos hashes reais.

git merge-base --is-ancestor <initial-commit-hash> <feature-commit-hash>
echo $?

Novamente, a saída de echo $? deve ser 0 porque o "Initial commit" é o ponto de partida para ambos os branches.

Finalmente, vamos verificar se o commit mais recente em feature é um ancestral do commit mais recente em master. Substitua <feature-commit-hash> e <master-commit-hash> pelos hashes reais.

git merge-base --is-ancestor <feature-commit-hash> <master-commit-hash>
echo $?

Neste caso, o commit mais recente em feature não é um ancestral do commit mais recente em master (eles estão em branches diferentes após a divisão inicial). Portanto, a saída de echo $? deve ser 1.

Entender a relação de ancestralidade entre commits é fundamental para entender como o Git rastreia o histórico e como operações como merge e rebase funcionam. A flag --is-ancestor fornece uma maneira simples de verificar essa relação.

Testar Commits Inacessíveis

Nesta etapa, exploraremos o conceito de commits "inatingíveis" no Git. Um commit inatingível é um commit que não pode ser alcançado a partir de nenhum branch, tag ou outra referência. Esses commits não fazem parte do histórico atual do seu projeto, conforme visto por comandos padrão como git log.

Continuaremos usando o repositório ancestry-lab. Certifique-se de estar no diretório correto:

cd ~/project/ancestry-lab

Atualmente, todos os nossos commits são alcançáveis a partir do branch master ou feature. Vamos criar um cenário em que um commit se torna inatingível.

Primeiro, vamos fazer um novo commit no branch master:

echo "Temporary commit" >> file1.txt
git add file1.txt
git commit -m "Temporary commit"

Você verá a saída para este novo commit:

[master <commit-hash>] Temporary commit
 1 file changed, 1 insertion(+)

Agora, vamos redefinir o branch master de volta ao commit anterior. Isso tornará o "Temporary commit" inatingível a partir do branch master. Usaremos git reset --hard HEAD~1. O HEAD~1 se refere ao commit imediatamente anterior ao HEAD atual.

Tenha cuidado com git reset --hard, pois ele descarta as alterações! Neste caso, estamos intencionalmente descartando o "Temporary commit" do histórico do branch master.

git reset --hard HEAD~1

Você verá a saída indicando que o HEAD agora está no commit anterior:

HEAD is now at <previous-commit-hash> More master content

Agora, vamos olhar para o git log padrão:

git log --oneline

Você verá que o "Temporary commit" não está mais na saída do log para o branch master.

<previous-commit-hash> (HEAD -> master) More master content
<commit-hash> Add more content
<initial-commit-hash> Initial commit

O "Temporary commit" ainda existe no banco de dados Git, mas não é referenciado por nenhum branch ou tag. Ele agora é um commit "inatingível".

Como podemos ver commits inatingíveis? O Git tem uma referência especial chamada reflog, que registra atualizações para a ponta dos branches e outras referências. Podemos usar git log com a opção --walk-reflogs ou simplesmente git reflog para ver esses commits.

Vamos usar git reflog:

git reflog

Você verá um log das ações tomadas, incluindo o commit que acabamos de fazer e, em seguida, redefinir:

<master-commit-hash> (HEAD -> master) master@{0}: reset: moving to HEAD~1
<temporary-commit-hash> master@{1}: commit: Temporary commit
<previous-commit-hash> master@{2}: commit: More master content
<commit-hash> master@{3}: commit: Add more content
<initial-commit-hash> master@{4}: commit (initial): Initial commit

Observe a entrada para "Temporary commit". Ele é alcançável via master@{1} no reflog. No entanto, não é alcançável a partir do HEAD atual ou de qualquer ponta de branch.

Commits inatingíveis são eventualmente limpos pela coleta de lixo do Git (git gc), mas permanecem acessíveis via reflog por um período padrão (geralmente 30 ou 90 dias). Isso pode ser uma tábua de salvação se você redefinir ou excluir commits acidentalmente.

Entender commits inatingíveis ajuda você a entender a diferença entre o banco de dados de objetos interno do Git e as referências (branches, tags, HEAD) que apontam para commits dentro desse banco de dados.

Resumo

Neste laboratório, aprendemos como verificar se um commit Git é alcançável a partir do HEAD usando dois métodos principais. Primeiro, exploramos o comando git log --ancestry-path, que nos permite visualizar o histórico de commits ao longo do caminho entre dois commits especificados. Configuramos um repositório simples com múltiplos branches e commits para demonstrar como este comando ajuda a entender a linhagem e identificar se um commit é um ancestral de outro.

Em segundo lugar, aprenderemos como usar o comando git merge-base --is-ancestor, que fornece uma maneira mais direta e programática de determinar se um commit é um ancestral de outro. Finalmente, testaremos esses métodos com commits inatingíveis para solidificar nossa compreensão de como verificar a alcançabilidade dentro de um repositório Git.