Repositórios git ‘bare’

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.

Repositórios '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.

Como isso resolve os meus problemas

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

Gerenciamento de páginas na web

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.

Criação de um repositório 'bare'

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 em PROJETO.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

Exemplo: projeto 'meu-projeto' - Cenário #1

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.

Clonando o repositório

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

Atualizando o repositório 'bare'

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!

Extração da árvore de trabalho (checkout)

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.

Automatizando o 'checkout' (hooks) - Cenário #2

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.

Dica final (ainda sobre o Cenário #1)

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.

Conclusão

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.

Músico, programador, designer e apaixonado pelos ideais do Software Livre.

1 Response

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Post comment