15%

Poupe 15% em todos os serviços

Teste as suas habilidades e obtenha Desconto em qualquer plano

Utilizar o código:

Skills
Começar a trabalhar
22.10.2024
1 +1

Estrutura do Repositório Git: Um Guia Técnico Completo

Git é um sistema de controlo de versões distribuído que armazena o histórico do projeto como um grafo acíclico dirigido (DAG) de objetos de instantâneos imutáveis. Cada repositório Git é construído a partir de três zonas lógicas — o diretório de trabalho, o índice de staging e o armazenamento de objetos dentro de .git/ — mais um conjunto de ponteiros leves (branches, tags, remotos) que navegam nesse histórico. Compreender como estas camadas interagem é a diferença entre usar o Git mecanicamente e usá-lo com precisão cirúrgica.

Se aloja os seus repositórios num VPS, dominar esta estrutura interna permite-lhe recuperar de desastres, conceber pipelines CI/CD eficientes e auditar cada byte do histórico do seu projeto sem depender de uma plataforma de terceiros.

O Modelo das Três Zonas: Como o Git Move Dados

Antes de mergulhar nos componentes individuais, interiorize o modelo de fluxo de dados que governa cada operação Git:

Working Directory  -->  Staging Area (Index)  -->  .git/ Object Store
     (edit)               (git add)                  (git commit)

As alterações viajam da esquerda para a direita quando constrói um commit, e da direita para a esquerda quando restaura ou repõe. Cada comando Git é essencialmente uma operação de leitura ou escrita em uma ou mais destas zonas.

Diretório de Trabalho

O diretório de trabalho (também chamado de árvore de trabalho) é a vista do sistema de ficheiros do seu projeto num estado de checkout específico. Quando executa git clone ou git checkout, o Git reconstrói ficheiros a partir de objetos comprimidos em .git/objects/ e escreve-os neste diretório.

Os ficheiros no diretório de trabalho existem num de quatro estados:

  • Não rastreado — o Git nunca viu este ficheiro; existe apenas no disco.
  • Rastreado, não modificado — o ficheiro corresponde exatamente ao último instantâneo confirmado.
  • Rastreado, modificado — o ficheiro difere do último instantâneo confirmado mas não foi colocado em staging.
  • Rastreado, eliminado — o ficheiro foi removido do disco mas a eliminação não foi colocada em staging.

Uma nuance crítica que confunde muitos programadores: o diretório de trabalho não é uma simples cópia do repositório. O Git reconstrói-o lendo objetos de árvore e descomprimindo objetos blob. Se .git/ estiver intacto, pode sempre regenerar o diretório de trabalho do zero — o inverso não é verdade.

Sparse Checkout para Grandes Monorepos

Em repositórios com dezenas de milhares de ficheiros (comum em arquiteturas monorepo), pode limitar quais os caminhos que o Git materializa no diretório de trabalho:

git sparse-checkout init --cone
git sparse-checkout set services/api services/auth

Isto é inestimável num VPS com I/O de disco limitado, porque o Git ignora a descompressão de blobs para caminhos fora do cone.

Área de Staging (Índice)

A área de staging, internamente chamada de índice, é um ficheiro binário localizado em .git/index. Atua como um próximo commit proposto — um instantâneo mutável que se situa entre o seu diretório de trabalho e o armazenamento de objetos permanente.

git add <file>          # Stage a specific file
git add -p              # Interactively stage hunks within a file
git add -u              # Stage all tracked modifications and deletions
git status              # Compare working directory and index against HEAD
git diff --cached       # Show diff between index and HEAD

Por Que Existe o Índice

O índice resolve um problema que ferramentas VCS mais simples ignoram: commits parciais. Pode ter modificado cinco ficheiros mas apenas querer três deles no próximo commit. O índice permite-lhe compor exatamente o instantâneo que pretende registar, independentemente do que o seu editor tem aberto.

Caso extremo — corrupção do índice: Se uma falha do sistema interromper um git add, o ficheiro de índice pode ficar corrompido. Os sintomas incluem git status a bloquear ou a reportar resultados estranhos. Recuperação:

rm .git/index
git reset

O Git reconstrói o índice a partir de HEAD sem tocar no seu diretório de trabalho.

O Índice como Registo de Conflitos de Merge

Durante um conflito de merge, o índice armazena três versões de cada ficheiro em conflito simultaneamente (estágios 1, 2 e 3 — base, nossa versão, versão deles). É por isso que git diff --cached não mostra nada útil durante um conflito; precisa de git diff --cc ou de uma ferramenta de merge para inspecionar os três estágios.

O Diretório .git/: Anatomia do Armazenamento de Objetos

O diretório .git/ é o repositório. Tudo o resto — o diretório de trabalho, clones remotos — é derivado dele. Eliminar .git/ transforma um repositório num diretório simples sem histórico.

.git/
├── HEAD
├── config
├── description
├── index
├── COMMIT_EDITMSG
├── hooks/
├── info/
├── logs/
│   ├── HEAD
│   └── refs/
├── objects/
│   ├── info/
│   └── pack/
└── refs/
    ├── heads/
    ├── remotes/
    └── tags/

HEAD é um ficheiro de texto simples contendo uma ref simbólica (apontando para um branch) ou um hash SHA-1 bruto (estado HEAD desanexado).

cat .git/HEAD
# ref: refs/heads/main        <-- on a branch
# a3f1c9d...                  <-- detached HEAD

O HEAD desanexado não é um estado de erro — é intencional quando faz checkout de uma tag ou de um commit específico para inspeção. O perigo está em fazer commits com HEAD desanexado: esses commits são acessíveis apenas via reflog até os anexar a um branch.

git checkout -b rescue-branch   # Attach detached commits to a new branch

config

O ficheiro de configuração do repositório local. Substitui as definições globais (~/.gitconfig) e do sistema (/etc/gitconfig). Entradas comuns:

[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
[remote "origin"]
    url = git@github.com:user/repo.git
    fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
    remote = origin
    merge = refs/heads/main

Num servidor auto-hospedado, irá frequentemente editar este ficheiro diretamente ao rodar URLs remotos ou ao configurar uploadpack.allowReachableSHA1InWant para clones parciais.

refs/

O diretório refs/ contém ficheiros de texto simples, cada um contendo um único hash SHA-1. São os ponteiros nomeados que tornam o DAG do Git navegável.

Tipo de RefCaminhoDescrição
Branch localrefs/heads/<name>Aponta para o commit mais recente de um branch
Branch de rastreamento remotorefs/remotes/<remote>/<name>Cache local do commit mais recente de um branch remoto
Tag leverefs/tags/<name>Aponta diretamente para um objeto commit
Tag anotadarefs/tags/<name>Aponta para um objeto tag, que aponta para um commit
Stashrefs/stashAponta para o commit do stash

Por desempenho, o Git empacota refs em .git/packed-refs assim que um repositório acumula muitas delas. Verifique sempre ambas as localizações ao criar scripts que trabalhem com refs.

Objetos Git: O Núcleo Imutável

Tudo armazenado em .git/objects/ é endereçado por conteúdo: o nome do ficheiro é o hash SHA-1 (ou SHA-256 em versões mais recentes do Git) do conteúdo do objeto. Isto torna o Git inerentemente resistente a adulterações — alterar qualquer byte altera o hash, quebrando a cadeia.

Os Quatro Tipos de Objetos

Tipo de ObjetoO Que ArmazenaAponta Para
BlobConteúdo bruto do ficheiro (sem nome de ficheiro, sem permissões)Nada
TreeListagem de diretório: nomes de ficheiros, permissões, SHAs de blob/treeBlobs e outras trees
CommitAutor, committer, timestamp, mensagem, SHA(s) do(s) pai(s)Uma tree + zero ou mais commits pai
TagIdentidade do tagger, timestamp, mensagem, assinatura GPGNormalmente um commit

Inspecionar Objetos Diretamente

# Show the type of any object
git cat-file -t a3f1c9d

# Show the content of any object
git cat-file -p a3f1c9d

# Show the tree of the current HEAD commit
git ls-tree HEAD

# Show a specific blob's content
git show HEAD:src/main.py

Objetos Soltos vs. Ficheiros Pack

Inicialmente, cada objeto é armazenado como um ficheiro comprimido individual em .git/objects/<2-char-prefix>/<38-char-suffix>. Estes são objetos soltos. Com o tempo, o Git executa git gc (recolha de lixo) para agrupar objetos soltos em ficheiros pack (.git/objects/pack/*.pack) com um índice correspondente (.pack.idx).

Os ficheiros pack utilizam compressão delta — armazenando a diferença entre objetos semelhantes em vez de cópias completas. Um repositório com milhares de ficheiros de texto semelhantes pode encolher dramaticamente após o empacotamento. Num VPS com capacidade NVMe limitada, executar git gc --aggressive em repositórios grandes antes de arquivar é prática padrão.

git count-objects -vH    # Show loose object count and disk usage
git gc --aggressive      # Repack aggressively (CPU-intensive)
git verify-pack -v .git/objects/pack/*.idx | sort -k3 -n | tail -20
# Find the 20 largest objects in the pack

Histórico de Commits: O Grafo Acíclico Dirigido

Cada objeto commit contém exatamente um ponteiro para um objeto tree (o instantâneo do diretório raiz) e zero ou mais ponteiros para commits pai. Isto forma um DAG onde:

  • Zero pais = o commit inicial (commit raiz)
  • Um pai = um commit normal
  • Dois pais = um commit de merge
  • Três ou mais pais = um merge octopus (raro, usado para integrar muitos branches de funcionalidades simultaneamente)
git log --oneline --graph --all    # Visualize the full DAG
git log --format="%H %P"           # Show each commit's SHA and parent SHA(s)

Imutabilidade de Commits e Reescrita de Histórico

Como o SHA de um commit é derivado do seu conteúdo (incluindo SHAs dos pais), qualquer reescrita cria um novo commit com um novo SHA. Operações como git rebase, git commit --amend e git filter-repo não modificam o histórico — criam histórico paralelo. Os commits antigos permanecem no armazenamento de objetos até serem recolhidos pelo garbage collector.

É por isso que fazer force-push de histórico reescrito para um branch partilhado é destrutivo: os branches locais dos colaboradores ainda apontam para a cadeia de commits antiga.

Branches: Ponteiros Leves

Um branch não é mais do que um ficheiro de 41 bytes contendo um hash SHA-1. Criar um branch é instantâneo independentemente do tamanho do repositório porque o Git apenas escreve um pequeno ficheiro.

git branch feature/auth           # Create branch at current HEAD
git checkout -b feature/auth      # Create and switch in one step
git switch -c feature/auth        # Modern equivalent (Git 2.23+)
git branch -d feature/auth        # Delete (safe: refuses if unmerged)
git branch -D feature/auth        # Delete (force: regardless of merge status)

Internos dos Branches

cat .git/refs/heads/main
# a3f1c9d8e2b1f4c7d9e0a1b2c3d4e5f6a7b8c9d0

Quando faz commit num branch, o Git escreve o novo SHA do commit neste ficheiro. Isso é a totalidade de “avançar um ponteiro de branch.”

Branches de Rastreamento e Configuração Upstream

Uma relação de rastreamento diz ao Git qual branch remoto um branch local deve comparar para relatórios de divergência git status e comportamento de git pull.

git branch --set-upstream-to=origin/main main
git branch -vv    # Show tracking relationships and ahead/behind counts

Tags: Marcadores Permanentes no Histórico

As tags marcam commits específicos como significativos — tipicamente lançamentos de software. Ao contrário dos branches, as tags não são movidas por novos commits.

FuncionalidadeTag LeveTag Anotada
ArmazenamentoUm ficheiro ref apontando para um commitUm objeto tag no armazenamento de objetos
MetadadosNenhumNome do tagger, email, data, mensagem
Assinatura GPGNão possívelSuportado via git tag -s
Recomendado para lançamentosNãoSim
Transferência com git push --tagsSimSim
git tag v2.1.0                              # Lightweight tag at HEAD
git tag -a v2.1.0 -m "Release 2.1.0"       # Annotated tag
git tag -s v2.1.0 -m "Signed release"      # GPG-signed annotated tag
git push origin --tags                      # Push all tags to remote
git push origin v2.1.0                      # Push a specific tag

Armadilha crítica: git push não envia tags por padrão. As equipas frequentemente esquecem isto e publicam notas de lançamento referenciando uma tag que não existe no remoto.

Remotos: Colaboração Distribuída

Um remoto é um URL nomeado armazenado em .git/config. Os branches de rastreamento remoto (em refs/remotes/) são instantâneos locais de apenas leitura dos branches do remoto, atualizados apenas quando faz fetch explicitamente.

git remote add origin git@github.com:user/repo.git
git remote -v                          # List remotes with URLs
git remote set-url origin <new-url>    # Change a remote URL
git fetch origin                       # Update remote-tracking branches
git fetch --prune                      # Remove stale remote-tracking branches
git push origin main                   # Push local main to remote
git push -u origin feature/auth        # Push and set upstream tracking

Múltiplos Remotos

Um único repositório pode rastrear múltiplos remotos — comum quando se mantém um fork juntamente com o upstream:

git remote add upstream git@github.com:original/repo.git
git fetch upstream
git merge upstream/main

Quando auto-hospeda repositórios bare num servidor dedicado para a sua equipa, cada programador adiciona o servidor como remoto e usa autenticação por chave SSH para acesso de push.

Hooks: Aplicação Automatizada em Cada Evento Git

Os hooks são scripts executáveis em .git/hooks/. O Git chama-os em pontos definidos do fluxo de trabalho. Não são transferidos por git clone ou git push — cada programador (ou servidor) deve instalá-los independentemente. Esta é uma fonte frequente de confusão em ambientes de equipa.

Hooks do Lado do Cliente

HookGatilhoUso Comum
pre-commitAntes do prompt de mensagem de commitLinting, deteção de segredos, execução de testes
prepare-commit-msgApós a criação da mensagem padrãoInjetar nome do branch na mensagem
commit-msgApós o utilizador escrever a mensagemImpor formato de commit convencional
post-commitApós o commit ser registadoNotificações locais
pre-pushAntes de git push ser executadoExecutar suite de testes completa
pre-rebaseAntes do rebase iniciarImpedir rebase de branches publicados

Hooks do Lado do Servidor

HookGatilhoUso Comum
pre-receiveAntes das refs serem atualizadasImpor proteção de branch, rejeitar force-push
updatePor ref durante a receçãoAplicação de política por branch
post-receiveApós todas as refs atualizadasAcionar CI/CD, enviar notificações

Exemplo: Hook Pre-commit para Deteção de Segredos

#!/usr/bin/env bash
# .git/hooks/pre-commit

if git diff --cached --name-only | xargs grep -lE '(AKIA|passwords*=|api_keys*=)' 2>/dev/null; then
    echo "ERROR: Potential secret detected in staged files. Commit aborted."
    exit 1
fi
exit 0

Torná-lo executável:

chmod +x .git/hooks/pre-commit

Para distribuição de hooks em toda a equipa, use uma ferramenta como Husky (projetos Node.js) ou armazene hooks num diretório hooks/ na raiz do repositório e crie links simbólicos durante a configuração do projeto.

Reflog: A Rede de Segurança

O reflog regista cada movimento de HEAD e dos ponteiros de branch, incluindo operações que parecem destruir o histórico (reposições forçadas, rebases, commits alterados). É armazenado em .git/logs/.

git reflog                          # Show HEAD movement history
git reflog show main                # Show movement history for a specific branch
git checkout HEAD@{3}               # Check out the state HEAD was in 3 moves ago
git branch recovered HEAD@{5}       # Recover commits by branching from a reflog entry

As entradas do reflog expiram após 90 dias por padrão (gc.reflogExpire). Num servidor de produção, considere estender este prazo:

git config gc.reflogExpire 180
git config gc.reflogExpireUnreachable 30

Repositórios Bare: Hospedagem do Lado do Servidor

Um repositório bare não tem diretório de trabalho. Contém apenas o conteúdo de .git/ ao nível da raiz. Os repositórios bare são o formato correto para hospedagem centralizada — aceitam pushes sem as complicações de um branch com checkout.

git init --bare /srv/repos/myproject.git

Quando faz push para o GitHub, GitLab ou um servidor Git auto-hospedado, está a fazer push para um repositório bare. Se aloja o seu próprio servidor Git num VPS com cPanel ou num VPS Linux puro, repositórios bare em /srv/repos/ com acesso SSH são a arquitetura padrão.

Inicializar um Repositório Bare Partilhado

# On the server
git init --bare --shared=group /srv/repos/project.git
chown -R git:developers /srv/repos/project.git

# On a developer's machine
git remote add origin git@yourserver.com:/srv/repos/project.git
git push -u origin main

Armazenamento de Objetos Git: Tamanho, Integridade e Manutenção

Verificar a Saúde do Repositório

git fsck --full          # Verify object integrity (finds dangling and corrupt objects)
git fsck --lost-found    # Write dangling objects to .git/lost-found/

Encontrar e Remover Objetos Grandes

Ficheiros binários grandes acidentalmente confirmados são uma causa comum de repositórios sobredimensionados. Identifique-os antes de usar git filter-repo para os excluir:

# Find the 10 largest objects by compressed size
git verify-pack -v .git/objects/pack/*.idx 
  | sort -k3 -rn 
  | head -10 
  | awk '{print $1}' 
  | xargs -I{} git cat-file -p {}
# Remove a file from all history (requires git-filter-repo)
git filter-repo --path path/to/large-file.bin --invert-paths

Após a filtragem, todos os colaboradores devem re-clonar — os seus repositórios locais referenciam hashes SHA que já não existem no histórico reescrito.

Comparação: Conceitos Chave do Repositório Git

ConceitoTipoMutávelArmazenado EmTransferido por Push/Fetch
BlobObjetoNão.git/objects/Sim (quando acessível)
TreeObjetoNão.git/objects/Sim (quando acessível)
CommitObjetoNão.git/objects/Sim (quando acessível)
Tag AnotadaObjetoNão.git/objects/Apenas com --tags
BranchRefSim.git/refs/heads/Sim
Branch de rastreamento remotoRefSim (no fetch).git/refs/remotes/Não (cache local)
Tag LeveRefNão.git/refs/tags/Apenas com --tags
HEADSymref/hashSim.git/HEADNão
ÍndiceFicheiro binárioSim.git/indexNão
HooksScriptsSim.git/hooks/Não
ReflogLogSim (expira automaticamente).git/logs/Não

Matriz de Decisão Prática e Principais Conclusões

Use esta lista de verificação ao configurar ou auditar um repositório Git na sua infraestrutura:

Inicialização do repositório

  • Use git init --bare --shared=group para qualquer repositório que receberá pushes de múltiplos utilizadores.
  • Armazene repositórios bare fora de diretórios acessíveis pela web (nunca em /var/www/).

Saúde do armazenamento de objetos

  • Execute git fsck --full após qualquer incidente de armazenamento ou erro do sistema de ficheiros.
  • Agende git gc periodicamente em repositórios de longa duração; automatize-o via cron no seu servidor.
  • Monitorize o tamanho dos ficheiros pack com git count-objects -vH; investigue se a contagem de objetos soltos exceder 1.000.

Higiene de branches e refs

  • Elimine branches mesclados prontamente; refs obsoletas acumulam-se e abrandam as operações git fetch --prune.
  • Use git fetch --prune em pipelines CI para evitar agir sobre branches remotos eliminados.

Implementação de hooks

  • Nunca confie em .git/hooks/ para política em toda a equipa — os hooks não são clonados. Use hooks pre-receive do lado do servidor ou uma barreira CI em vez disso.
  • Audite os hooks do lado do servidor após cada atualização do servidor Git; os caminhos do interpretador de hooks podem mudar.

Segurança em servidores auto-hospedados

  • Restrinja o acesso SSH ao utilizador git com comandos forçados (command= em authorized_keys).
  • Use git-shell como shell de login para o utilizador git para impedir a execução de comandos arbitrários.
  • Associe o seu servidor de repositório a um certificado SSL válido se expuser qualquer interface web (Gitea, GitLab, cgit).

Reescrita de histórico

  • Nunca reescreva o histórico em branches partilhados com outros sem um plano de migração coordenado.
  • Após git filter-repo, todos os colaboradores devem re-clonar; atualize imediatamente os URLs remotos do CI/CD.

Recuperação de desastres

  • Estenda a expiração do reflog em servidores de produção (gc.reflogExpire = 180).
  • Mantenha um clone bare secundário num host separado como backup; um simples git fetch do primário é suficiente.

FAQ

Qual é a diferença entre um repositório Git bare e um não-bare?

Um repositório não-bare tem um diretório de trabalho onde os ficheiros são verificados, mais um subdiretório .git/ contendo o armazenamento de objetos. Um repositório bare contém apenas o armazenamento de objetos na sua raiz (sem diretório de trabalho) e é o formato correto para um servidor partilhado que recebe pushes.

Posso recuperar commits após executar git reset --hard?

Sim, desde que os commits não tenham sido recolhidos pelo garbage collector. Execute git reflog para encontrar o SHA do commit que pretende recuperar, depois git checkout -b recovery-branch <SHA> para o anexar a um novo branch. As entradas do reflog são retidas por 90 dias por padrão.

Por que é que git push não transfere as minhas tags?

Por design, git push apenas transfere commits acessíveis a partir das refs que envia explicitamente. As tags são refs separadas e devem ser enviadas com git push origin --tags (todas as tags) ou git push origin <tagname> (uma tag específica).

O que acontece ao índice durante um conflito de merge?

O índice armazena as três versões de cada ficheiro em conflito simultaneamente: estágio 1 (ancestral comum/base), estágio 2 (a sua versão) e estágio 3 (a versão deles). O git add normal apenas escreve o estágio 0 (resolvido). Até todos os conflitos serem resolvidos e colocados em staging, git commit recusará prosseguir.

Como diferem os hooks Git entre implementações do lado do cliente e do lado do servidor?

Os hooks do lado do cliente são executados na máquina do programador e não são aplicados centralmente — qualquer programador pode contorná-los eliminando o ficheiro de hook. Os hooks do lado do servidor (pre-receive, update, post-receive) são executados no servidor de hospedagem e não podem ser contornados pelo cliente, tornando-os o ponto de aplicação correto para políticas de proteção de branch, requisitos de revisão de código e acionadores CI/CD.

15%

Poupe 15% em todos os serviços

Teste as suas habilidades e obtenha Desconto em qualquer plano

Utilizar o código:

Skills
Começar a trabalhar