Bizu de Git: Git Flow e CI/CD no Forgejo
Este bizu mostra, passo a passo, como trabalhar com Git usando o fluxo Git Flow, mensagens no padrão Conventional Commits (em português) e um CI/CD no Forgejo Actions. Como exemplo, usamos um site de uma única página (HTML e CSS puros) chamado meu-site, hospedado em cosmos.simioni.dev.br, na organização projetos-web. Ajuste esses nomes para os seus.
Visão geral
| Branch | Para que serve | Quem escreve nela |
|---|---|---|
main |
Código estável. Cada versão publicada tem uma tag aqui. | Só recebe merge de release/* e hotfix/* |
develop |
Integração do trabalho em andamento. | Recebe merge de feature/* |
feature/* |
Uma tarefa ou funcionalidade. | Você, no dia a dia |
release/* |
Preparação de uma versão (changelog, ajustes finais). | Você, na hora de lançar |
hotfix/* |
Correção urgente, criada a partir da main. |
Você, quando algo quebra em produção |
deploy |
Espelho do que deve ir para o servidor. | Só avança com merge --ff-only a partir da main |
E o que o Forgejo faz em cada evento:
| Evento | O que o CI/CD executa |
|---|---|
Push em develop, feature/*, release/*, hotfix/* ou pull request |
Lint, testes e build |
Push de uma tag vX.Y.Z |
Lint, testes, build e pacote publicado no registry do Forgejo |
Push na branch deploy |
Deploy via SSH, que só roda se a variável DEPLOY_ENABLED for true (hoje fica desativado) |
O Git guarda o histórico e o Forgejo reage a ele. Quem comanda o pipeline é o que você envia: branches, pull requests e tags.
Configuração única (por máquina)
1. Instalar o Git no Windows
winget install --id Git.Git -e
Feche e abra o terminal depois da instalação e confirme com git --version.
2. Identificar-se e ajustar o básico
git config --global user.name "Seu Nome"
git config --global user.email "Este endereço de email está sendo protegido de spambots. Você precisa do JavaScript ativado para vê-lo. "
git config --global init.defaultBranch main
git config --global pull.rebase false
git config --global fetch.prune true
- Use no
user.emailo mesmo e-mail cadastrado no Forgejo, para os commits ficarem ligados ao seu usuário. fetch.pruneremove automaticamente as referências de branches que já foram apagadas no servidor.
3. Criar uma chave SSH
ssh-keygen -t ed25519 -C "Este endereço de email está sendo protegido de spambots. Você precisa do JavaScript ativado para vê-lo. "
type $env:USERPROFILE\.ssh\id_ed25519.pub
O segundo comando (PowerShell) mostra a chave pública. Copie o conteúdo e cadastre em Configurações → Chaves SSH/GPG no Forgejo. Teste a conexão:
ssh -T Este endereço de email está sendo protegido de spambots. Você precisa do JavaScript ativado para vê-lo.
Se o seu Forgejo usa uma porta SSH diferente da 22, use a URL no formato ssh://.
Criar o repositório de exemplo
1. Criar o repositório vazio no Forgejo
Crie projetos-web/meu-site sem README, sem .gitignore e sem licença. Ele precisa estar vazio para receber o primeiro push sem conflito.
2. Estrutura do projeto
meu-site/
├── .forgejo/
│ └── workflows/
│ ├── ci.yml
│ └── deploy.yml
├── scripts/
│ ├── build.sh
│ └── check.sh
├── src/
│ ├── index.html
│ └── css/
│ └── style.css
├── .gitattributes
├── .gitignore
├── .stylelintrc.json
├── CHANGELOG.md
└── README.md
3. Arquivos da página
src/index.html
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Meu Site</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<main>
<h1>Olá, mundo!</h1>
<p>Página publicada com Git e Forgejo.</p>
</main>
</body>
</html>
src/css/style.css
body {
font-family: system-ui, sans-serif;
margin: 0;
display: grid;
min-height: 100vh;
place-items: center;
}
h1 {
color: #1d4ed8;
}
4. Arquivos de apoio
.gitignore
dist/
node_modules/
*.tar.gz
.env
Thumbs.db
.gitattributes (evita que o Windows grave os scripts com final de linha CRLF, que quebra no Linux do runner)
* text=auto
*.sh text eol=lf
*.yml text eol=lf
.stylelintrc.json
{
"rules": {
"block-no-empty": true,
"color-no-invalid-hex": true,
"declaration-block-no-duplicate-properties": true,
"property-no-unknown": true,
"unit-no-unknown": true
}
}
scripts/check.sh (o "teste" da página: confere o essencial)
#!/usr/bin/env sh
set -eu
fail() {
echo "ERRO: $1" >&2
exit 1
}
[ -f src/index.html ] || fail "src/index.html não existe"
grep -q "<title>" src/index.html || fail "falta a tag <title>"
grep -q "<html lang=" src/index.html || fail "falta o atributo lang no <html>"
grep -q 'name="viewport"' src/index.html || fail "falta a meta viewport"
for arquivo in $(grep -o 'href="css/[^"]*"' src/index.html | cut -d'"' -f2); do
[ -f "src/$arquivo" ] || fail "arquivo referenciado não existe: $arquivo"
done
echo "Testes OK"
scripts/build.sh
#!/usr/bin/env sh
set -eu
rm -rf dist
mkdir -p dist
cp -r src/. dist/
printf '%s\n' "${BUILD_VERSION:-dev}" > dist/version.txt
echo "Build pronto em dist/"
Os workflows chamam os scripts com sh scripts/build.sh, então não é preciso dar permissão de execução (o que no Windows costuma dar dor de cabeça).
5. Primeiro commit e as branches fixas
git init -b main
git add .
git commit -m "chore: cria estrutura inicial do site"
git remote add origin Este endereço de email está sendo protegido de spambots. Você precisa do JavaScript ativado para vê-lo. :projetos-web/meu-site.git
git push -u origin main
git switch -c develop
git push -u origin develop
git switch -c deploy main
git push -u origin deploy
git switch develop
Depois disso o repositório tem as três branches permanentes: main, develop e deploy. Em Configurações → Branches do repositório, vale definir develop como branch padrão, para os pull requests já nascerem apontando para ela.
Padrão de commits (Conventional Commits em português)
O formato é tipo: descrição curta no imperativo. O tipo fica em inglês (é o padrão da convenção) e a descrição em português.
| Tipo | Quando usar | Exemplo |
|---|---|---|
feat |
Funcionalidade nova | feat: adiciona seção de contato |
fix |
Correção de erro | fix: corrige link quebrado do rodapé |
docs |
Só documentação | docs: explica como rodar o build |
style |
Formatação, sem mudar o comportamento | style: padroniza indentação do CSS |
refactor |
Reorganização sem mudar o resultado | refactor: separa estilos do cabeçalho |
test |
Testes | test: verifica meta viewport |
ci |
Pipeline e workflows | ci: inclui etapa de lint do CSS |
chore |
Tarefas de manutenção e releases | chore: prepara release 1.0.0 |
Para uma mudança que quebra compatibilidade, coloque ! depois do tipo (feat!: troca a estrutura de pastas) ou escreva BREAKING CHANGE: no rodapé da mensagem. Se quiser escopo, use feat(css): ....
O CI/CD no Forgejo Actions
Os workflows ficam em .forgejo/workflows/. O runner do servidor atende pelos labels ubuntu-latest e debian, e os exemplos usam ubuntu-latest.
Pipeline principal: .forgejo/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop, 'feature/**', 'release/**', 'hotfix/**']
tags: ['v*']
pull_request:
branches: [main, develop]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Lint do HTML
run: npx --yes htmlhint "src/**/*.html"
- name: Lint do CSS
run: npx --yes stylelint --config .stylelintrc.json "src/**/*.css"
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Testes
run: sh scripts/check.sh
build:
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- uses: actions/checkout@v4
- name: Build
run: sh scripts/build.sh
package:
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
needs: [build]
env:
VERSAO: ${{ github.ref_name }}
SERVIDOR: ${{ github.server_url }}
DONO: ${{ github.repository_owner }}
PACKAGE_TOKEN: ${{ secrets.PACKAGE_TOKEN }}
steps:
- uses: actions/checkout@v4
- name: Build com a versão da tag
run: BUILD_VERSION="${VERSAO#v}" sh scripts/build.sh
- name: Empacotar
run: tar -czf "meu-site-${VERSAO#v}.tar.gz" -C dist .
- name: Publicar no registry do Forgejo
run: |
curl --fail --silent --show-error \
-H "Authorization: token ${PACKAGE_TOKEN}" \
--upload-file "meu-site-${VERSAO#v}.tar.gz" \
"${SERVIDOR}/api/packages/${DONO}/generic/meu-site/${VERSAO#v}/meu-site-${VERSAO#v}.tar.gz"
- O job
packagesó roda quando o push é de uma tag que começa comv. - O pacote vai para o registry Generic da organização, em Pacotes, e fica com o nome
meu-site. - Crie o segredo
PACKAGE_TOKENem Configurações do repositório → Actions → Segredos. Ele deve ser um token de um usuário membro da organização, com leitura e escrita empackage, igual ao do registry Cargo.
Deploy desativado: .forgejo/workflows/deploy.yml
name: Deploy
on:
push:
branches: [deploy]
jobs:
deploy:
if: ${{ vars.DEPLOY_ENABLED == 'true' }}
runs-on: ubuntu-latest
env:
SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
KNOWN_HOSTS: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
DEPLOY_HOST: ${{ vars.DEPLOY_HOST }}
DEPLOY_PORT: ${{ vars.DEPLOY_PORT }}
DEPLOY_USER: ${{ vars.DEPLOY_USER }}
DEPLOY_PATH: ${{ vars.DEPLOY_PATH }}
steps:
- uses: actions/checkout@v4
- name: Build
run: BUILD_VERSION="$(git rev-parse --short HEAD)" sh scripts/build.sh
- name: Instalar rsync e ssh
run: |
if ! command -v rsync > /dev/null; then
apt-get update
apt-get install -y rsync openssh-client
fi
- name: Preparar o SSH
run: |
mkdir -p ~/.ssh
printf '%s\n' "$SSH_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
printf '%s\n' "$KNOWN_HOSTS" > ~/.ssh/known_hosts
- name: Enviar os arquivos
run: |
rsync -az --delete \
-e "ssh -p ${DEPLOY_PORT:-22}" \
dist/ "${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PATH}/"
Enquanto a variável DEPLOY_ENABLED não existir (ou for diferente de true), o job aparece como ignorado a cada push na deploy e nada é enviado. O arquivo já está pronto e versionado, então ligar o deploy depois não exige mexer no código.
Como ligar o deploy no futuro (só documentação)
- Gere uma chave exclusiva para o deploy, sem senha:
ssh-keygen -t ed25519 -f deploy_key -C "forgejo-deploy". - Coloque o conteúdo de
deploy_key.pubno~/.ssh/authorized_keysdo usuário de deploy no servidor. - Pegue a impressão digital do servidor com
ssh-keyscan -p 22 seu-servidor.com.br. - Em Configurações do repositório → Actions → Segredos, crie
DEPLOY_SSH_KEY(conteúdo da chave privada) eDEPLOY_KNOWN_HOSTS(saída dossh-keyscan). - Em Actions → Variáveis, crie
DEPLOY_HOST,DEPLOY_PORT,DEPLOY_USEReDEPLOY_PATH(pasta do site no servidor). - Por último, crie a variável
DEPLOY_ENABLEDcom o valortrue. - Apague os dois arquivos da chave da sua máquina:
deploy_keyedeploy_key.pub.
Para desligar de novo, basta apagar a variável DEPLOY_ENABLED ou mudar o valor para false.
Fluxo do dia a dia
1. Criar uma feature
git switch develop
git pull
git switch -c feature/secao-contato
Trabalhe, faça commits pequenos e frequentes e envie a branch:
git add .
git commit -m "feat: adiciona seção de contato"
git push -u origin feature/secao-contato
O CI roda lint, testes e build. Abra um pull request de feature/secao-contato para develop, espere o CI ficar verde e faça o merge (pode usar a opção de merge commit, que mantém a história do Git Flow). Depois limpe:
git switch develop
git pull
git branch -d feature/secao-contato
Se a branch remota não foi apagada pelo Forgejo, use git push origin --delete feature/secao-contato.
2. Lançar uma versão (release)
git switch develop
git pull
git switch -c release/1.0.0
Atualize o CHANGELOG.md e faça o commit:
git add CHANGELOG.md
git commit -m "chore: prepara release 1.0.0"
git push -u origin release/1.0.0
Com o CI verde, leve a release para a main, crie a tag e envie tudo:
git switch main
git pull
git merge --no-ff release/1.0.0 -m "chore: release 1.0.0"
git tag -a v1.0.0 -m "Versão 1.0.0"
git push origin main v1.0.0
O push da tag v1.0.0 dispara o pipeline completo e publica o pacote meu-site 1.0.0 no registry. Agora traga as mudanças de volta para a develop e limpe a branch:
git switch develop
git merge --no-ff release/1.0.0
git push origin develop
git branch -d release/1.0.0
git push origin --delete release/1.0.0
Se a main estiver protegida contra push direto, faça o merge da release por pull request e crie a tag depois, já com a main atualizada: git switch main, git pull, git tag -a v1.0.0 -m "Versão 1.0.0", git push origin v1.0.0.
3. Corrigir algo urgente (hotfix)
git switch main
git pull
git switch -c hotfix/1.0.1
Corrija, faça o commit (fix: corrige link quebrado do rodapé) e depois:
git switch main
git merge --no-ff hotfix/1.0.1 -m "chore: hotfix 1.0.1"
git tag -a v1.0.1 -m "Versão 1.0.1"
git push origin main v1.0.1
git switch develop
git merge --no-ff hotfix/1.0.1
git push origin develop
git branch -d hotfix/1.0.1
4. Colocar no ar (branch deploy)
git switch deploy
git pull
git merge --ff-only main
git push origin deploy
O --ff-only garante que a deploy seja sempre uma cópia exata da main, sem commits próprios. Se o merge recusar, algo foi escrito direto na deploy e precisa ser investigado antes de continuar. Com o deploy desativado, esse push só gera a execução "ignorada" no Forgejo.
Comandos que salvam o dia
| Comando | Para que serve |
|---|---|
git status |
Ver o que mudou e em qual branch você está |
git log --oneline --graph --decorate --all |
Ver o histórico e as branches em forma de árvore |
git diff e git diff --staged |
Ver o que mudou antes de adicionar e antes de commitar |
git restore arquivo |
Descartar alterações ainda não commitadas de um arquivo |
git restore --staged arquivo |
Tirar um arquivo da área de preparo (git add) |
git commit --amend |
Corrigir o último commit, só se ele ainda não foi enviado |
git stash e git stash pop |
Guardar o trabalho pela metade e recuperar depois |
git switch - |
Voltar para a branch anterior |
git tag -n |
Listar as tags com suas mensagens |
git reflog |
Achar commits "perdidos" depois de um erro |
Proteger as branches
Em Configurações do repositório → Branches, crie regras de proteção:
mainedevelop: exigir pull request e exigir que o CI esteja verde antes do merge.deploy: permitir push apenas para você (ou para quem pode publicar), já que essa branch é a que vai para o servidor.- Para a
main, se você prefere fazer o merge da release pela linha de comando, inclua o seu usuário na lista de quem pode dar push.
Problemas comuns
| Sintoma | Causa e solução |
|---|---|
Permission denied (publickey) |
A chave pública não está cadastrada no Forgejo, ou o SSH está usando outra chave. Teste com ssh -T . |
rejected ... non-fast-forward |
Alguém (ou você, em outra máquina) enviou commits antes. Rode git pull, resolva conflitos se houver e envie de novo. |
| A tag não dispara o CI | O git push comum não envia tags. Use git push origin v1.0.0 (ou inclua a tag no push da main). |
| O workflow não aparece no Forgejo | O arquivo precisa estar em .forgejo/workflows/ com extensão .yml, e o Actions precisa estar ativo no repositório. |
| O job fica eternamente em "Aguardando" | O runs-on não bate com os labels do runner (ubuntu-latest ou debian), ou o runner está offline. |
bad interpreter: ^M ao rodar um script |
O arquivo foi salvo com CRLF no Windows. Confira o .gitattributes e regrave o script com final de linha LF. |
| Erro 401 ou 403 ao publicar o pacote | O segredo PACKAGE_TOKEN está errado, sem permissão de escrita em package, ou o usuário não é membro da organização. |
| Erro 409 ao publicar o pacote | Essa versão já existe no registry. Corrija e lance uma nova versão (por exemplo v1.0.1). |
| O job de deploy aparece como "ignorado" | É o esperado enquanto DEPLOY_ENABLED não for true. |
npx ou node não encontrado no lint |
Confira se o passo actions/setup-node vem antes do lint. |
Segurança
- Nunca versione tokens, chaves privadas ou arquivos
.env. Eles ficam somente em Segredos do Forgejo. Se algo vazar, revogue e gere outro. - Use uma chave SSH dedicada ao deploy, diferente da sua chave pessoal, e apague a cópia local depois de cadastrá-la.
- Guarde o
known_hostsdo servidor como segredo, em vez de aceitar qualquer host automaticamente. - Proteja
main,developedeploypara que nada chegue a elas sem passar pelo CI. - Não use
git push --forceem branches compartilhadas.
Resumo do ciclo
feature/*sai dadevelope volta para ela por pull request.release/*sai dadevelop, vai para amaincom uma tagvX.Y.Ze o pacote é publicado.- A
mainavança adeploycom--ff-onlye, quando o deploy estiver ligado, o site é atualizado. hotfix/*sai damaine volta para amaine para adevelop.