Repositórios git ‘bare’
Vídeo associado a este artigo.
Recentemente, eu me vi diante de dois problemas aparentemente sem relação: o primeiro era encontrar uma forma simples, segura e padronizada de trabalhar nos meus projetos utilizando, pelo menos, duas máquinas diferentes na rede; o segundo, automatizar o gerenciamento e a publicação das páginas da minha wiki sobre shell (textos em markdown) sem ser obrigado a utilizar a interface web da wiki ou abrir uma sessão de SSH. O inusitado é que ambos os problemas acabaram passando pela mesma solução: os repositórios git bare.
Quando clonamos um repositório git, ele possui duas estruturas básicas: os arquivos do nosso projeto e um diretório oculto com a estrutura do repositório propriamente dito. Os arquivos do projeto compõem aquilo que é chamado de work tree (árvore de trabalho), enquanto a estrutura do repositório, que fica na pasta oculta .git
, é onde encontramos configurações e outras informações que o Git utiliza para controlar o versionamento do projeto.
Um repositório bare, por sua vez, não possui uma árvore de trabalho exposta. Em vez disso, ele possui apenas o equivalente ao conteúdo do diretório .git
, ou seja, a estrutura de dados e controle. No meio dessas estruturas, encontram-se os conteúdos do projeto, mas eles só podem ser acessados através de mecanismos do próprio Git, por exemplo, pela clonagem do repositório em outro local ou pelo mecanismo do checkout.
Imagine o cenário onde você tem um PC, mas precisa (ou quer) alternar o trabalho nos mesmos projetos utilizando um notebook. Até então, eu resolvia o problema abrindo uma sessão de SSH no notebook para trabalhar diretamente com os arquivos do PC. Isso funciona muito bem, mas carece de alguns recursos que, para mim, começaram a ficar importantes:
- Controle de versionamento;
- Padronização dos procedimentos;
- Mecanismos para lidar com erros;
- Compartilhamento com outras máquinas na rede.
A maior parte desses requisitos poderia ser atendida com SSH, mas não de formas tão simples e seguras quanto aquelas que o Git oferece. Para ser justo, o SSH é apenas o que seu nome diz: um shell seguro, enquanto o Git é uma ferramenta especializada em versionamento e metodologias de trabalhos colaborativos.
Sendo assim, esta seria uma possível solução deste primeiro cenário:
# Cenário #1 - Projeto compartilhado na rede
- Máquina 1 (PC):
· ~/meu-projeto.git (repositório 'bare')
· ~/meu-projeto (repositório clonado)
- Outras máquinas:
· ~/meu-projeto (repositório clonado)
Tendo um repositório bare do projeto, eu só preciso cloná-lo em todas as máquinas em que quiser trabalhar. Depois disso, o fluxo de trabalho nos repositórios clonados em qualquer uma das máquinas, será sempre:
atualizar (pull) → alterar arquivos → add → commit → push
Se pensarmos que o meu servidor de hospedagem é apenas outra máquina em uma rede a que eu tenho acesso (inclusive por SSH), parte da solução não difere em nada do que eu implementei na minha rede local: tendo acesso ssh e o Git instalado no servidor, eu posso criar um repositório bare para os textos das páginas da minha wiki e cloná-lo em todas as minhas máquinas.
Mas isso só resolve o lado do gerenciamento dos arquivos das páginas, já que um repositório bare não expõe os arquivos da árvore de trabalho (work tree). Eu até poderia clonar ou fazer o checkout do repositório no próprio servidor para utilizar os arquivos, mas seria um procedimento manual, lento e nada prático.
Felizmente, o Git possui mecanismos para automatizar tarefas disparando a execução de scripts a partir de vários eventos: os hooks (ganchos). Se é assim, tudo que eu tenho que fazer é criar um script que extraia a última versão dos meus arquivos em uma pasta separada no meu servidor.
Mais adiante, nós veremos que script é esse e como criá-lo. No momento, eu só quero que você observe a solução deste segundo cenário:
# Cenário #2 - Gerenciamento de arquivos na web
- Servidor (hospedagem):
· ~/minhas-paginas.git (repositório 'bare')
· ~/minhas-paginas (diretório extraído do repositório)
- Máquinas locais:
· ~/minhas-paginas (repositório clonado)
Diferente do primeiro cenário, aqui nós temos um diretório para receber os arquivos extraídos do repositório bare em vez de um clone do mesmo.
Agora que sabemos aonde queremos chegar, vamos aos procedimentos.
Seja qual for o cenário, teremos que criar um repositório bare, o que é feito da seguinte forma:
git init --bare CAMINHO/PROJETO.git -b RAMO
Onde:
-
git init
é a operação para criar ou fazer com que uma pasta se torne um repositório Git; -
--bare
é a flag que informa que o repositório será bare; -
CAMINHO/PROJETO.git
é o diretório do repositório que será criado; -
-b RAMO
é o nome do ramo (branch) padrão.
Nomear o diretório com a terminação
.git
(como emPROJETO.git
) nos ajuda a identificar repositórios bare.
A definição do nome do ramo é opcional. Se omitido, o nome do ramo será o que estiver definido nas configurações globais do Git (geralmente, master
) e poderá ser mudado após a criação do repositório, com o comando:
git branch -m NOVO_NOME
Dica extra: para definir a configuração global do nome do ramo padrão, nós podemos executar o comando:
git config --global init.defaultBranch NOME
Para efeito de demonstração, nós criaremos o repositório bare para o projeto chamado meu-projeto
na pasta ~/projetos
:
:~$ cd projetos
:~/projetos$ git init --bare meu-projeto.git -b main
Initialized empty Git repository in /home/blau/projetos/meu-projeto.git
Isso cria a seguinte estrutura (basicamente uma pasta .git
exposta):
:~/projetos$ cd meu-projeto.git
:~/projetos/meu-projeto.git$ ls
branches/ hooks/ info/ objects/ refs/ config description HEAD index
No arquivo de configuração (config
), nós temos:
:~/projetos/meu-projeto.git$ cat config
[core]
repositoryformatversion = 0
filemode = true
bare = true
Repare que o valor do campo
bare
étrue
, verdadeiro.
O repositório meu-projeto.git
não pode receber arquivos diretamente: nós não temos uma árvore de trabalho! Para utilizá-lo, nós precisamos cloná-lo em outro local, como outra máquina na rede. O processo não é diferente de clonar qualquer outro repositório Git via SSH.
Ainda como demonstração, digamos que eu tenha criado o repositório no meu PC e queira cloná-lo na minha pasta pessoal de um notebook que está na rede:
# No notebook...
:~$ git clone blau@192.168.15.2:projetos/meu-projeto.git
Cloning into 'meu-projeto'...
warning: You appear to have cloned an empty repository.
Importante! Para que isso seja possível, o serviço de SSH deve estar disponível e ativo em ambas as máquinas!
Neste comando:
-
git clone
é o comando para clonar repositórios; -
blau@192.168.15.2
é o nome do usuário e o IP da máquina de onde eu estou clonando o projeto; -
:projetos/meu-projeto.git
é o caminho relativo do repositório a partir da minha pasta pessoal (/home/blau/
).
A clonagem de um repositório Git bare vazio resulta em um repositório non-bare vazio, ou seja, a estrutura do Git está na pasta .git
e nós temos uma árvore de trabalho vazia onde podemos incluir arquivos.
# No notebook...
:~$ cd meu-projeto
:~/meu-projeto$ ls -a
. .. .git/
Nós podemos testar a inclusão de novos arquivos:
# No notebook...
:~/meu-projeto$ echo 'Olá, git!' > novo-arquivo.txt
:~/meu-projeto$ ls -a
. .. .git/ novo-arquivo.txt
Para atualizar o repositório 'bare' remoto, o procedimento é o mesmo de qualquer fluxo de trabalho com Git:
# No notebook...
:~/meu-projeto$ git add .
:~/meu-projeto$ git commit -m 'primeiro commit'
[main (root-commit) 68d2c5b] primeiro commit
(um monte de mensagens porque não configuramos o usuário)
1 file changed, 1 insertion(+)
create mode 100644 novo-arquivo.txt
:~/meu-projeto$ git push origin main
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 224 bytes | 224.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To 192.168.15.2:projetos/meu-projeto.git
* [new branch] main -> main
:~/meu-projeto$
Pronto! Isso significa que já podemos trabalhar nos mesmos projetos utilizando qualquer máquina que esteja na rede, inclusive o PC onde o meu repositório foi criado: basta clonar o repositório bare e partir para o abraço!
No repositório bare (remoto), novo-arquivo.txt
não estará exposto (não há uma árvore de trabalho, lembra?), mas a alteração será registrada, o que podemos verificar com um log
:
# Na máquina do repositório 'bare'
:~/projetos/meu-projeto.git$ git log main -p
commit 68d2c5b818be3278f6e4f3a0797729fbe9f5c842 (HEAD -> main)
Author: Blau Araujo <blau@dell>
Date: Wed Oct 13 11:22:39 2021 -0300
primeiro commit
diff --git a/novo-arquivo.txt b/novo-arquivo.txt
new file mode 100644
index 0000000..71a93cc
--- /dev/null
+++ b/novo-arquivo.txt
@@ -0,0 +1 @@
+Olá, git!
Nota: para clonar repositórios bare na mesma máquina, é só informar o caminho local da origem em vez de um
usuário@IP:caminho
.
Mas é possível extrair uma árvore de trabalho a partir do repositório bare através de uma operação chamada de checkout.
Por exemplo, para extrair a árvore de trabalho na minha pasta pessoal:
# Na máquina do repositório 'bare'
:~$ git --work-tree=/home/blau/meu-projeto --git-dir=/home/blau/projetos/meu-projeto.git checkout -f
Onde:
-
--work-tree=
é o caminho absoluto da pasta de destino; -
--git-dir=
é o caminho absoluto do repositório bare; -
checkout
é o comando para recriar a árvore de trabalho; -
-f
(force) indica que queremos ignorar alterações que ainda não foram aplicadas ao projeto (unmerged).
Como resultado...
# Na máquina do repositório 'bare'
:~/projetos/meu-projeto.git$ ls -a ~/meu-projeto
. .. novo-arquivo.txt
Repare que não há um diretório .git
, apenas os arquivos incluídos na árvore de trabalho: o que era de se esperar, visto que nós não clonamos o repositório, nós apenas reconstruímos a árvore de trabalho em outro caminho.
Um dos possíveis usos para o checkout é o gerenciamento de páginas e outros documentos na web através do Git. Tendo acesso SSH, nós poderíamos entrar no servidor e executar o comando do checkout manualmente, mas isso não faria muito sentido prático. A outra opção é criar um script que será executado toda vez que o repositório receber atualizações. Isso é feito através do mecanismo de disparo do Git chamado de hook (gancho).
Tudo que temos que fazer é criar um script na pasta hooks
do nosso repositório bare. Se o nome do script for post-receive
, ele será executado sempre que fizermos um push no nosso repositório local. O conteúdo do script é basicamente o mesmo do comando que executamos manualmente:
#!/bin/sh
git --work-tree=/home/USUARIO/CAMINHO/DESTINO --git-dir=/home/USUARIO/CAMINHO/PROJETO.git checkout -f
Obviamente, para ser executado, o script post-receive
tem que receber permissões de execução:
:~/CAMINHO/PROJETO.git/hooks$ chmod +x post-receive
Nota: este procedimento só faz sentido em circunstâncias onde precisamos apenas dos arquivos do projeto, e não de um repositório completo. Para trabalhar efetivamente no projeto de forma colaborativa (cenário #1), nós precisamos de um repositório clonado; para servir arquivos e páginas na web (deploy), não nos interessa um repositório Git clonado.
Pode ser que os arquivos do nosso projeto já existam na máquina onde o repositório bare foi criado: só não estão em um repositório Git. Neste caso, o procedimento mais fácil é clonar o repositório, como vimos antes, mover todos os arquivos existentes para este clone e fazer um commit.
Alternativamente, nós podemos transformar a pasta com os arquivos existentes em um repositório Git, mas será necessário configurar uma origem, ou seja, indicar onde está o repositório bare que receberá as nossas atualizações.
O procedimento geral é:
# Passo 1: iniciar o repositório:
:~$ cd CAMINHO/PROJETO_EXISTENTE
:~/CAMINHO/PROJETO_EXISTENTE$ git init
Initialized empty Git repository in /home/USER/CAMINHO/PROJETO_EXISTENTE/.git/
# Passo 2: configurar a origem (se em outro ponto na rede):
:~/CAMINHO/PROJETO_EXISTENTE$ git remote add origin USUÁRIO@IP:CAMINHO/REPOSITÓRIO.git
# Passo 2: configurar a origem (se na mesma máquina):
:~/CAMINHO/PROJETO_EXISTENTE$ git remote add origin ~/CAMINHO/REPOSITÓRIO.git
Contudo, eu insisto: é muito mais prático e simples apenas copiar os arquivos existentes para a pasta do repositório recém-clonado.
Eu não sou muito fluente no Git, estou habituado apenas com os procedimentos mais simples relacionados à criação e gerenciamento de projetos tendo como origem os repositórios criados em plataformas como o GitLab e o Codeberg, por exemplo. Por isso, demorou algum tempo até que eu tomasse ciência da possibilidade de criar meus próprios repositórios bare e vislumbrasse as vantagens que eles trazem para a minha computação em geral, não apenas no desenvolvimento de software.
Mas o nosso objetivo aqui não é esgotar o assunto. Na verdade, nós mal arranhamos a superfície do Git e eu nem sei dizer, neste momento, se estou fazendo as coisas da forma mais adequada. Para o aprimoramento dessas dicas, portanto, eu conto com a colaboração de todos: revisando, corrigindo e melhorando os conceitos e exemplos aqui apresentados.
parabens pelo material, foi muito útil !