Nesta página
- Um branch é uma ref, não uma cópia dos seus arquivos
- Acompanhe a mudança de refs/heads ao criar branches
- Criar, renomear e excluir branches
- Recuperar um branch excluído por acidente
- Onde os comandos de branch dão errado
- Comandos de branch em resumo
- Perguntas sobre branches, respondidas
- Teste seu entendimento
Git Branch: por dentro do sistema leve de ponteiros do Git
Em resumo: no Git, um branch não é uma cópia do diretório nem um snapshot pesado dos arquivos-fonte. No formato de referência files padrão do Git, ela é simplesmente uma referência leve e mutável — um arquivo de texto de 41 bytes que contém um hash SHA-1 de commit com 40 caracteres e um caractere de nova linha — armazenado no diretório .git/refs/heads/. Criar, renomear ou excluir branches no Git é praticamente instantâneo porque essas operações apenas alteram os arquivos de referência, em vez de modificar ou copiar os arquivos do seu diretório de trabalho.
Nota sobre o escopo: a descrição do “arquivo de 41 bytes” é exata para o backend de referências
filespadrão do Git quando um branch está desempacotada. As referências também podem ser empacotadas em.git/packed-refspelogit gc, e o Git oferece um backend alternativo reftable, no qual os branches não são arquivos individuais. Os comandos e o comportamento são idênticos em todos os casos — só muda a representação no disco. Consulte Git Refs para saber mais.
Um branch é uma ref, não uma cópia dos seus arquivos
Para entender por que o modelo de branches do Git é tão revolucionário, é útil compará-lo a sistemas de controle de versão (VCS) mais antigos, como Subversion (SVN) ou Perforce. Nesses sistemas legados, criar um branch significava fazer uma cópia física de toda a árvore de diretórios do projeto. Se sua base de código tivesse 5 GB, criar um branch significava copiar 5 GB de arquivos no servidor. Essa operação demorava, desperdiçava espaço em disco e desencorajava os desenvolvedores a criar branches para tarefas pequenas.
O Git não copia arquivos. Em vez disso, o Git organiza o histórico do projeto como um grafo acíclico direcionado (DAG) de objetos de commit. Cada commit aponta para seu(s) commit(s) pai(s), criando uma estrutura semelhante a uma árvore para o histórico. Nesse grafo:
- Um commit representa um snapshot dos arquivos rastreados do seu projeto em um momento específico, junto com metadados do autor e links para os commits pais. Consulte /git-commit para ver detalhes sobre a estrutura dos objetos de commit.
- Um branch é simplesmente uma referência nomeada que aponta para um commit específico nesse grafo. Ela é um ponto de partida a partir do qual o Git pode percorrer os ponteiros para os commits pais e construir o histórico daquele branch.
Imagine um branch como uma nota adesiva presa a um nó específico em uma cadeia de commits. À medida que você trabalha e cria novos commits no branch ativo, o Git move automaticamente a nota adesiva para a frente, para apontar para o commit mais recente. Como um branch é apenas uma referência (ou “ref”), as operações de branching do Git exigem apenas a escrita ou a alteração de uma pequena string de 40 caracteres.
Para uma análise mais detalhada do sistema geral de referências do Git, leia nosso guia sobre /git-refs.
Três referências costumam se confundir: refs/heads/main é o seu branch local; refs/remotes/origin/main é o seu registro local do branch remoto, atualizado por comandos como git fetch; e HEAD identifica o branch atualmente em checkout, ou um commit diretamente no estado detached HEAD. Um git push bem-sucedido também pode atualizar a ref de rastreamento remoto correspondente.
Acompanhe a mudança de refs/heads ao criar branches
Para compreender totalmente a mecânica dos branches do Git, vamos fazer um passo a passo no terminal e observar como o diretório .git/ muda durante as operações de branching.
Etapa 1: inicializar um repositório limpo
Vamos criar um repositório novo, adicionar um commit e examinar a estrutura do diretório .git/.
$ mkdir git-branch-demo
$ cd git-branch-demo
$ git init
Initialized empty Git repository in /users/admin/git-branch-demo/.git/
Neste estágio, se listarmos o conteúdo de .git/refs/heads/, veremos que ele está completamente vazio:
$ ls -la .git/refs/heads/
total 0
drwxr-xr-x 2 admin staff 64 Jul 7 10:25 .
drwxr-xr-x 4 admin staff 128 Jul 7 10:25 ..
Ainda não existe um arquivo de branch padrão porque nenhum commit foi criado. Vamos ver para onde .git/HEAD aponta:
$ cat .git/HEAD
ref: refs/heads/main
Embora refs/heads/main ainda não exista no disco, HEAD já está configurado para apontar para ele. Esse estado é conhecido como “branch não nascido” (unborn branch). Vamos criar nosso primeiro commit para fazer o branch existir.
$ echo "Hello Git Branching!" > README.md
$ git add README.md
$ git commit -m "Initial commit"
[main (root-commit) f24e93e] Initial commit
1 file changed, 1 insertion(+)
create mode 100644 README.md
Agora que existe um commit, vamos inspecionar novamente .git/refs/heads/:
$ ls -la .git/refs/heads/
total 8
drwxr-xr-x 3 admin staff 96 Jul 7 10:27 .
drwxr-xr-x 4 admin staff 128 Jul 7 10:25 ..
-rw-r--r-- 1 admin staff 41 Jul 7 10:27 main
Um arquivo chamado main apareceu! Vamos imprimir o conteúdo desse arquivo:
$ cat .git/refs/heads/main
f24e93e25b90f46c6a7e0c4a45adbb474f8e6c7c
O arquivo contém exatamente um hash de commit SHA-1 de 40 caracteres seguido por um caractere de nova linha. Vamos verificar para qual objeto esse hash aponta usando o comando de baixo nível git cat-file:
$ git cat-file -t f24e93e25b90f46c6a7e0c4a45adbb474f8e6c7c
commit
E vamos imprimir o conteúdo do objeto de commit para ver como ele representa nosso snapshot:
$ git cat-file -p f24e93e25b90f46c6a7e0c4a45adbb474f8e6c7c
tree 5b38a7c29375ab2b4a53239a584a22c5432a138b
author User <user@example.com> 1783420000 +0000
committer User <user@example.com> 1783420000 +0000
Initial commit
Etapa 2: criar um novo branch
Agora vamos criar um novo branch chamado feature-login usando o comando git branch:
$ git branch feature-login
Vamos conferir o conteúdo de .git/refs/heads/ para ver o que o Git fez internamente:
$ ls -la .git/refs/heads/
total 16
drwxr-xr-x 4 admin staff 128 Jul 7 10:30 .
drwxr-xr-x 4 admin staff 128 Jul 7 10:25 ..
-rw-r--r-- 1 admin staff 41 Jul 7 10:30 feature-login
-rw-r--r-- 1 admin staff 41 Jul 7 10:27 main
Um novo arquivo chamado feature-login foi criado. Vamos ler seu conteúdo:
$ cat .git/refs/heads/feature-login
f24e93e25b90f46c6a7e0c4a45adbb474f8e6c7c
Ele contém exatamente o mesmo hash de commit que main! O Git não copiou nenhum arquivo-fonte. Ele simplesmente criou um arquivo de texto de 41 bytes apontando para o commit existente (supondo o formato de referência files padrão e desempacotado). Isso explica por que a criação de branches é instantânea.
Vamos conferir nosso branch ativo lendo .git/HEAD:
$ cat .git/HEAD
ref: refs/heads/main
HEAD ainda aponta para main, o que significa que continuamos no branch main. Para trocar de branch, precisamos atualizar a referência HEAD, algo normalmente feito por comandos como git checkout ou git switch. Leia mais sobre esse mecanismo em /git-checkout.
Etapa 3: fazer commit em um branch
Vamos trocar para nosso novo branch e fazer uma alteração.
$ git checkout feature-login
Switched to branch 'feature-login'
Agora vamos verificar .git/HEAD:
$ cat .git/HEAD
ref: refs/heads/feature-login
HEAD foi atualizado para apontar para refs/heads/feature-login. Vamos criar um novo commit:
$ echo "Login Form UI" > login.html
$ git add login.html
$ git commit -m "Add login page"
[feature-login a7c8e9d] Add login page
1 file changed, 1 insertion(+)
create mode 100644 login.html
Vamos conferir agora o conteúdo das referências das nossos branches:
$ cat .git/refs/heads/feature-login
a7c8e9d8928424ef245eefb925b42d3851b2c129
$ cat .git/refs/heads/main
f24e93e25b90f46c6a7e0c4a45adbb474f8e6c7c
feature-login avançou para apontar para nosso novo commit a7c8e9d.... Já main continua apontando para f24e93e.... Se conferirmos o histórico usando /git-log:
$ git log --oneline --graph --all
* a7c8e9d (HEAD -> feature-login) Add login page
* f24e93e (main) Initial commit
O grafo mostra que feature-login divergiu. O ponteiro do branch main permanece estático, enquanto feature-login avançou junto com HEAD. Se decidirmos mesclar esses branches mais tarde, o Git lerá esses ponteiros para calcular seus históricos. Saiba mais em /git-merge.
Criar, renomear e excluir branches
O comando git branch não serve apenas para criar referências; ele é uma ferramenta versátil para gerenciar, consultar e auditar o grafo de branches.
1. Listar e filtrar branches
Ao gerenciar projetos grandes, você frequentemente terá dezenas de branches locais e remotas. O Git oferece filtros para manter tudo organizado.
- Listar todas os branches (locais e remotas):
Isso mostra os branches locais em uma cor (por exemplo, verde) e os branches de acompanhamento remoto (armazenadas em$ git branch -a.git/refs/remotes/) em outra (por exemplo, vermelho). - Filtrar pelo status de mesclagem:
Antes de limpar branches antigas, você precisa saber quais branches foram mescladas com segurança à branch atual:
Para ver branches que contêm trabalho ainda não mesclado à branch atual:$ git branch --merged
Executar$ git branch --no-mergedgit branch -dem qualquer branch da lista--no-mergedfalhará, a menos que seja forçado. - Listagem detalhada com acompanhamento do upstream:
Isso mostra o hash do commit mais recente, a linha de assunto do commit e o status de acompanhamento do upstream (por exemplo, se seu branch local está à frente ou atrás do branch remoto correspondente).$ git branch -vv
2. Detalhes da exclusão e coleta de lixo
Excluir um branch é uma tarefa comum, mas seu funcionamento depende das flags usadas.
- Exclusão segura (
-dou--delete):
O Git impedirá a exclusão se o branch contiver commits que não sejam alcançáveis a partir do branch atual ou do branch upstream. Isso evita a perda acidental de trabalho.$ git branch -d feature-login - Exclusão forçada (
-Dou-d --force):
Isso diz ao Git: “Exclua a referência, independentemente de qualquer coisa.”$ git branch -D feature-login
O que acontece com os commits?
Excluir um branch remove a ref dela; isso não apaga os objetos de commit imediatamente. Se nenhuma outra ref alcançar esses commits, eles ficam inacessíveis e podem continuar recuperáveis pelo reflog por um tempo. O Git pode eventualmente remover objetos inacessíveis, e o momento depende da configuração e do estado do repositório. Se o trabalho for importante, crie outra branch antes da limpeza; veja Git objects para entender o ciclo de vida dos objetos.
3. Acompanhar branches upstream
Ao colaborar, suos branches locais acompanham branches em um servidor remoto (como o GitHub). Essa configuração de acompanhamento é armazenada em .git/config.
Para definir um branch upstream para o branch ativo:
$ git branch -u origin/feature-login
# Or:
$ git branch --set-upstream-to=origin/feature-login
Isso modifica o arquivo .git/config para incluir metadados de acompanhamento:
[branch "feature-login"]
remote = origin
merge = refs/heads/feature-login
Essa configuração permite simplesmente digitar git pull ou git push sem especificar o remoto e o nome do branch.
Recuperar um branch excluído por acidente
Imagine que você está limpando seu diretório de trabalho. Você acha que mesclou seu branch feature-payment em main, então executa:
$ git branch -D feature-payment
Deleted branch feature-payment (was c3b91a2).
De repente, você percebe que feature-payment não foi mesclado e que acabou de excluir dias de trabalho! Como os arquivos não estão mais visíveis no editor, parece um desastre.
Como recuperar
Como os branches do Git são apenas referências, os commits ainda estão no banco de dados. Você só precisa encontrar o hash do commit e anexar a ele uma nova referência de branch.
- Pesquisar o reflog:
O reflog registra todas as vezes que
HEADmuda de posição. Execute:
Observando com atenção, vemos que$ git reflog f24e93e HEAD@{0}: checkout: moving from feature-payment to main c3b91a2 HEAD@{1}: commit: Add Stripe Integration and webhooks f24e93e HEAD@{2}: checkout: moving from main to feature-paymentc3b91a2era o commit feito no branchfeature-paymentantes de voltar paramain. - Recriar o ponteiro do branch:
Agora, execute
git branchseguido do novo nome do branch e do hash recuperado:
Pronto! O Git cria um novo arquivo de referência em$ git branch feature-payment c3b91a2.git/refs/heads/feature-paymentcontendoc3b91a2, restaurando seu trabalho instantaneamente.
Onde os comandos de branch dão errado
1. Achar que git branch troca para o novo branch
Executar git branch new-feature apenas cria o ponteiro. Isso não atualiza HEAD. Se você começar a editar arquivos imediatamente, fará commits no branch antigo (por exemplo, main). Lembre-se sempre de executar git checkout new-feature (ou usar git checkout -b new-feature para criar e trocar em uma única etapa).
2. Colisões de namespace (arquivo versus diretório)
Como o Git armazena as refs dos branches como arquivos em .git/refs/heads/, elas precisam seguir as regras de caminho de arquivo do seu sistema operacional.
- Se você tiver um branch chamado
feature, o Git criará um arquivo chamado.git/refs/heads/feature. - Se depois você tentar criar um branch chamado
feature/login, o Git precisará criar um diretório chamadofeaturee colocar dentro dele um arquivologin. - Na maioria dos sistemas, não é possível ter um arquivo e um diretório com exatamente o mesmo nome na mesma pasta pai. Isso resulta no seguinte erro:
error: cannot lock ref 'refs/heads/feature/login': 'refs/heads/feature' exists; cannot create 'refs/heads/feature/login' - Correção: evite criar um branch genérico chamado
featurese pretende usar namespaces com barra, comofeature/loginefeature/payment.
3. Referências locais obsoletas para branches remotos excluídos
Quando um colega exclui um branch no servidor remoto, o Git local ainda mantém o registro dela em .git/refs/remotes/origin/. Isso pode causar confusão.
- Correção: limpe branches de acompanhamento remoto obsoletas executando:
$ git fetch --prune
Comandos de branch em resumo
| Comando | Descrição | Ação interna do Git |
|---|---|---|
git branch | Lista todas os branches locais | Lê arquivos dentro de .git/refs/heads/ |
git branch <name> | Cria uma nova referência de branch | Cria um arquivo de 41 bytes em .git/refs/heads/<name> (formato files padrão desempacotado) |
git branch -d <name> | Exclui com segurança um branch se ele tiver sido mesclado | Exclui o arquivo de ref; verifica a alcançabilidade primeiro |
git branch -D <name> | Exclui um branch à força | Exclui o arquivo de ref independentemente da alcançabilidade |
git branch -m <new-name> | Renomeia o branch atual | Renomeia o arquivo de ref em .git/refs/heads/ |
git branch -a | Lista branches locais e remotas | Lê .git/refs/heads/ e .git/refs/remotes/ |
git branch -vv | Mostra o status de acompanhamento e os commits | Correlaciona arquivos de ref locais com configurações upstream |
Perguntas sobre branches, respondidas
O que significa “HEAD desanexado”?
O estado de “HEAD desanexado” ocorre quando HEAD aponta diretamente para um hash de commit específico, em vez de apontar para uma referência de branch nomeada.
Por exemplo, .git/HEAD pode conter um ID de commit em vez de ref: refs/heads/main. Um commit feito nesse estado não pertence a nenhum branch; depois de mudar de branch, ele pode ficar inacessível a partir de refs nomeadas, embora o reflog possa ajudar a recuperá-lo. Para preservar o trabalho, crie um branch enquanto ainda estiver nesse commit com git switch -c rescue-work. Veja Git refs para entender como HEAD e as refs de branch se relacionam.
Dois branches podem apontar para o mesmo commit?
Sim. Vários branches podem apontar para exatamente o mesmo hash de commit. Na verdade, quando você cria um novo branch a partir de main, os dois branches apontam para o mesmo commit até que você crie um novo commit em uma delas. O Git não tem problema com várias refs contendo a mesma string SHA-1 de 40 caracteres.
Como enviar um branch local para um servidor remoto?
Para enviar seu branch a um repositório remoto e configurá-lo para acompanhar esse remoto, use o comando:
$ git push -u origin <branch-name>
A flag -u (ou --set-upstream) instrui o Git a criar uma associação de acompanhamento, atualizando seu arquivo .git/config para que futuros pushes e pulls nesse branch exijam apenas git push ou git pull.
Teste seu entendimento
Carregando quiz…