Git Workflow
Introdução
O Git possibilita uma variedade de estratégias de uso, o que pode tornar o processo de desenvolvimento complicado. Visando trazer produtividade e simplificar o uso do Git, bem como viabilizar integração contínua via ferramentas de CI/CD, propõe-se neste capítulo um conjunto claramente definido de boas práticas e fluxos de trabalho, projetado para suportar uma grande quantidade de desenvolvedores atuando concorrentemente nos projetos.
Pré-requisitos
Para leitura deste capítulo, são pré-requisitos conhecimentos básicos de Git: o que são branches e as operações comuns commit, pull, merge e push.
Diretrizes e Convenções
-
Deverá existir apenas 1 branch de longa duração, o tronco, que contém o código estável, sempre apto para publicação em produção.
-
A implementação de mudança, seja evolutiva ou corretiva, emergencial ou eletiva, deverá ser realizada em branch temporário, criado a partir do tronco.
-
Uma vez que a mudança foi aprovada pelo responsável técnico e/ou negocial, seu branch deverá ser reintegrado no tronco e então removido.
-
O branch de mudança deverá ser atualizado no repositório local, para recebimento das novidades do código estável, conforme a política:
-
Frequentemente, com vistas a minimizar esforço de adaptações causadas por impacto de outras demandas (conflitos).
-
Tipicamente antes da disponibilização para teste ou homologação.
-
Necessariamente antes da reintegração no tronco.
-
-
A atualização local de branch de mudança deverá ocorrer por
merge
, podendo ser com fast forward. -
A reintegração do branch de mudança no tronco deverá ocorrer por
merge
ou Merge Request, sem fast forward. -
Não utilizar as operações
rebase
esquash
. -
Evitar alterar commit message, pois o Git refaz os commits subsequentes por rebase, podendo “danificar” o histórico.
-
Um branch temporário de nome
release
poderá ser criado caso o código-fonte estável do tronco requeira ajuste de configuração para fins de deployment. Esse release branch representa uma janela técnica de preparação para produção. -
Enquanto existir o release branch, demandas de mudança não deverão ser reintegradas no tronco.
-
Tags para teste ou homologação de determinada mudança são criadas a partir do seu respectivo branch, enquanto tags de produção são criadas a partir do tronco ou do release branch, se aplicado.
-
Nome sugerido para o branch tronco:
master
(oumain
oustable
). -
Nome sugerido para branch de mudança: “dev” + <número da demanda>, ex:
dev77
.-
Quando relativo a um grupo de demandas, o nome poderá ser composto dos números das demandas separados por hífen, ex:
dev105-98-62
. -
Se relativo a uma demanda sem registro, sugere-se limitar-se aos caracteres a-z (minúsculos), números e hífen:
negativacao-cartorio
.
-
-
Nome sugerido para tag: <nome do branch> + “.” + <sequencial>, ex:
dev77.1
,dev77.2
,master.1
. -
Os nomes dos pacotes de aplicação, como builds, imagens Docker, etc, terão os mesmos nomes das respectivas tags de origem. Ex: tag
dev77.1
gera imagem Docker de nomedev77.1
. -
No caso de aplicações legadas cujo profile (ex: homologação ou produção) é definido durante o build, os nomes dos pacotes serão sufixados com o profile:
-
Tag
dev77.1
gerar imagem Dockerdev77.1.homologacao
. -
Tag
master.1
gerar imagens Dockermaster.1.homologacao
emaster.1.producao
. -
O script de empacotamento, como pipeline CI/CD, deverá tratar cada cenário, gerando somente os profiles aplicáveis para o tipo de branch.
-
Diagrama
O diagrama abaixo exemplifica uma sequência temporal do histórico de um repositório:
-
Nesse exemplo, a demanda
dev50
teve sua primeira tentativa de homologaçãodev50.1
reprovada. Após ajuste, a versãodev50.2
foi aprovada e então reintegrada no tronco. -
A demanda
dev77
foi aprovada conforme tagdev77.1
e sua reintegração ficou pendente durante um tempo (curto), enquanto ocorria uma janela de preparação para produção, tagmaster.5
.
Próximos Passos
IDEs como IntelliJ e VSC oferecem opções gráficas para uso do Git.
Observação: O IntelliJ não tem suporte gráfico a tags (2024), sendo necessária a utilização de comandos via terminal somente neste caso. As tags podem ser criadas pela interface web do GitLab ou GitHub.
O anexo a seguir traz exemplos de como realizar os fluxos de trabalho por linha de comando, em conformidade com as diretrizes e convenções acima dispostas. Entretanto, dificilmente será necessário utilizar comandos, salvo se for sua preferência.
Importante considerar as dicas constantes em “Configurando o repositório” do anexo “Fluxos de Trabalho”.
Anexos
I - Fluxos de Trabalho
A seguir são exemplificados fluxos do processo de trabalho por linha de comando. Verifique o anexo “Cartilha de Comandos” ou a documentação oficial do Git para mais detalhes sobre cada comando.
Obtendo o repositório:
git clone <url>
Configurando o repositório:
git config push.followTags true # Faz o Git enviar as tags junto com o push.
git config pull.rebase false # Desativa o rebase durante o pull.
git config core.autocrlf false # Evita que scripts .sh sejam alterados de LF para CRLF (impede execução no Mac)
Se na raiz do repositório existir o arquivo
config.sh
(Linux/Mac) ouconfig.bat
(Windows), recomenda-se execulá-lo para ativar extensões do Git (vide anexo “Git Config”).É possível também, opcionalmente, adicionar um
commit hook
que complementa automaticamente o comentário de commits adicionando o nome da demanda como prefixo da mensagem (vide anexo “Git Hooks”).
Começando nova demanda:
git checkout master
git pull
git checkout -b dev50
git push -u origin HEAD
Participando de uma demanda existente:
git fetch
git checkout dev70
Recebendo novidades do repositório remoto relativas à demanda:
git pull
Recebendo novidades do tronco (código estável):
git fetch
git merge master
Salvando as alterações locais:
git add -A
git commit -m "Implementação de ..."
Enviando a demanda para o repositório remoto:
git pull # Recebe novidades relativas à demanda
git push # Envia
Enviando a demanda para teste ou homologação:
git pull # Recebe novidades relativas à demanda
git merge master # Recebe novidades do tronco
git tag -l "dev50.*" # Lista as tags existentes
git tag -a dev50.1 -m "Versão dev50.1" # Cria a próxima tag (o sequencial inicia em 1)
git push # Envia
Finalizando uma demanda aprovada (via Merge Request):
Acesse a interface web do Git e crie um Merge Request do branch da demanda. O branch local poderá ser removido quando o Merge Request for aplicado. Certique-se de que a tag do branch foi aprovada pelo responsável técnico e/ou negocial e que não houve novos commits no branch após a aprovação.
Finalizando uma demanda aprovada (manual):
Certique-se de que a tag do branch foi aprovada pelo responsável técnico e/ou negocial e que não houve novos commits no branch após a aprovação.
git checkout master # Acessa o tronco local
git pull # Atualiza o tronco
git merge --no-ff -m "Merge de dev50" origin/dev50 # Faz o merge da demanda
git tag -l "master.*" # Lista as tags existentes
git tag -a master.1 -m "Versão master.1" # Cria a próxima tag (o sequencial inicia em 1)
git push # Envia
git branch -d dev50 # Remove o branch local
git push origin :dev50 # Remove o branch remoto
Finalizando uma versão de produção:
git checkout master # Acessa o tronco local
git pull # Atualiza o tronco
git checkout -b release # Cria o branch de release
git push -u origin HEAD # Envia branch de release para sinalizar a janela de mudança
# Agora ajuste os arquivos de configuração necessários
git add -A # Prepara os arquivos de configuração para commit
git commit -m "Preparação para produção" # Salva as mudanças
git tag -l "master.*" # Lista as tags existentes
git tag -a master.1 -m "Versão master.1" # Cria a próxima tag (o sequencial inicia em 1)
git checkout master # Retorna para o tronco
git merge --no-ff -m "Versão master.1" release # Faz o merge do release
git push # Envia
git branch -d release # Remove o branch local
git push origin :release # Remove o branch remoto
Nota: Caso o projeto não requeira ajustes de configurações, apenas crie a tag de produção diretamente a partir de tronco:
git checkout master # Acessa o tronco local
git pull # Atualiza o tronco
git tag -l "master.*" # Lista as tags existentes
git tag -a master.1 -m "Versão master.1" # Cria a próxima tag (o sequencial inicia em 1)
git push # Envia
II - Tarefas de Apoio
Este anexo contém tarefas úteis para manutenção do repositório local. IDEs oferecem opções gráficas para a maioria dessas tarefas.
Limpando branches locais que foram removidos do remoto:
git checkout master
git fetch origin --prune
git branch -a
Limpando um branch local que não tem referência remota:
git branch -d dev50
Salvando todas as mudanças:
git add -A
git commit -m "<Mensagem aqui>"
Analisando quais mudanças serão inclusas no commit:
O próximo commit incluirá as mudanças marcadas como staged. O comando abaixo mostra o conteúdo da stage area:
git status # Mostra o conteúdo da stage area
git reset # Limpa a stage area
Revertendo as mudanças:
Esse comando volta o projeto para o estado do último commit. Essa operação não pode ser desfeita. Semelhante ao revert do SVN. Tecnicamente é feita limpeza da stage area e do workdir.
git reset --hard
Revertendo o último commit:
Esse comando desfaz o último commit completamente. Essa operação não pode ser desfeita. Tecnicamente o ponteiro do branch volta para o commit anterior ao commit atual, sendo feita limpeza da stage area e do workdir.
git reset --hard HEAD~1
Cancelando o último commit:
Esse comando desfaz o último commit, porém mantém os arquivos em stage e mantém no workdir.
Se executar git commit
retornamos exatamente ao estado imediatamente anterior ao reset.
Útil caso queira ajustar o último commit.
git reset --soft HEAD~1
Opcionalmente pode-se fazer conforme abaixo. Neste caso, além do retorno do ponteiro, as mudanças ficarão unstaged. O workdir é mantido.
git reset HEAD~1
Revertendo para a última versão submetida:
Esse comando volta o branch para o último commit que teve push. Tudo será desfeito localmente. Essa operação não pode ser desfeita.
git fetch
git reset --hard origin/dev50
Revertendo um arquivo específico:
git checkout -- <arquivo>
III - Cartilha de Comandos
A seguir apresenta-se uma cartilha dos comandos Git mais importantes e seus principais parâmetros.
Clone:
git clone <url>
Faz o download do repositório remoto para o diretório local.
url
endereço HTTP do repositório remoto
Config:
git config [--global] <name> <value>
Configura o comportamento do Git.
--global
aplica a configuração no profile global~/.gitconfig
em vez de aplicar no próprio repositório.git/config
name
nome da configuração, vide documentação oficialvalue
valor da configuração, vide documentação oficial
Branch:
git branch [-a]
git branch -d <name>
Lista os branches do repositório local ou remove um branch local.
-a
ao listar os branches, inclui os remotosname
nome do branch local a ser removido
Add:
git add -A
Adiciona todos os arquivos novos, alterados e removidos para a lista de itens que são inclusos no próximo commit. Essa lista é chamada de stage area.
Commit:
git commit -m "<comment>"
Salva as alterações feitas no repositório local. O commit cria um snapshot (fotografia ou versão) dos arquivos, identificado por um hash único que o torna rastreável e referenciável.
comment
comentário
Fetch:
git fetch [remote] [--prune] [--prune-tags]
Baixa os novos commits, branches e tags do repositório remoto para o local.
remote
identificação do repositório remoto, por padrãoorigin
--prune
remove referências locais de branches remotos que não mais existem no repositório remoto--prune-tags
remove tags locais que inexistem no remoto
Checkout:
git checkout <name>
git checkout -b <new> [src]
Alterna para determinado branch ou cria um novo branch local.
name
branch para o qual ocorrerá o switch-b
cria um novo branchnew
nome do novo branch a ser criado a partir desrc
src
por padrão o branch atual
Se existir branch remoto com mesmo nome new
, o branch local terá tracking para ele.
Caso não existir, o tracking poderá ser definido no momento do push.
Não use src
referenciando um remoto (ex: origin/master).
Merge:
git merge [--no-ff] -m "<comment>" <src>
Faz o merge de determinado branch sobre o branch atual.
--no-ff
forção geração de novo commit mesmo quando fast forward for possívelcomment
informe no padrão “Merge de <src>”src
nome do branch que será aplicado sobre o branch atual
Esse comando considera a versão do branch que existe no repositório local. Normalmente é conveniente executar o fetch antes.
Pull:
git pull [remote]
Faz fetch seguido de merge: primeiro atualiza o repositório local e em seguida faz o merge do branch remoto sobre o branch local.
remote
identificação do repositório remoto, por padrãoorigin
Tag:
git tag [-l "<exp>"]
git tag -a -m "<comment>" <name> [src]
Lista ou cria tags.
-l
lista as tags conforme expressãoexp
(wilcards aceitos: *, ?, [abc], [!abc] outros conforme fnmatch)-a
cria uma tag do tipo Anotadacomment
comentário obrigatórioname
nome da tag a ser criadasrc
commit de origem para o qual a tag será referenciada (pode ser nome de um branch ou hash de um commit)
Push:
git push [--set-upstream origin <name>] [--follow-tags]
git push origin :<remove>
Envia o branch atual do repositório local para o remoto. O segundo comando remove um branch do repositório remoto.
--set-upstream
configura o tracking como sendo o branch remoto de nomename
--follow-tags
inclui as tags no pushremove
nome do branch a ser removido do repositório remoto
IV - Git Config
Este anexo contém scripts que estendem os comandos do Git.
Os seguintes comandos são adicionados:
git revert
: Volta o código-fonte para o commit anterior.git uncommit
: Cancela o último commit, porém mantém os arquivos alterados e a stage area. Útil quando se deseja fazer novas alterações no código-fonte e regerar o commit.git unstage
: Cancela o último commit e limpa a stage area, mantendo os arquivos alterados. Útil quando se deseja especificar quais alterações entrarão no commit.git rlog
: Imprime o log seguindo o percurso raiz dos merges. Ou seja, quando existe um merge, orlog
exibe apenas o commit aplicado no branch, sem exibir o histórico de commits do branch que foi reintegrado. Útil para visualizar as alterações no branch principal (master
).git prunetags
: Remove as tags locais que não existem no repositório remoto.
Arquivo /.gitconfig
:
[alias]
unstage = reset HEAD
revert = reset --hard HEAD
uncommit = reset --soft HEAD~1
rlog = log --oneline --decorate --graph --first-parent --author-date-order --pretty=format:'%C(yellow)%h|%Cred%ad|%Cblue%<(15,trunc)%an|%Cgreen%d %Creset%s' --date=short
tagref = rev-list -n 1
Arquivo /config.sh
:
#!/bin/sh
git config include.path ../.gitconfig
Arquivo /config.bat
:
git config include.path ../.gitconfig
V - Git Hooks
Este anexo contém script que adiciona um commit hook
responsável por incluir
automaticamente o nome do branch nos comentários de commit. Requer Git versão 2.9+.
Arquivo /.gitconfig
:
[core]
hooksPath = .githooks
Arquivo /.githooks/prepare-commit-msg
:
#!/bin/sh
COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
SHA1=$3
# Gets branch name
BRANCH_NAME=$(git branch | grep '*' | sed 's/* //')
PREFIX="[${BRANCH_NAME}]"
# Gets merged branch info
#if [ "$COMMIT_SOURCE" = "merge" ]
#then
# # Retrieve merged branch name from an env var GITHEAD
# # We cannot use a sym ref of MERGE_HEAD, as it doesn't yet exist
# HEAD=$(env | grep GITHEAD) # e.g. GITHEAD_<sha>=dev77
#
# # Cut out everything up to the last "=" sign
# SOURCE="${HEAD##*=}"
#
# MSG="${MSG} Merge from '${SOURCE}'"
#fi
# Removes lines starting with #
# printf %s "$(grep -s -v '^ *#' "$COMMIT_MSG_FILE")" > "$COMMIT_MSG_FILE"
# Get first character of the message
FIRST=$(head -c 1 "$COMMIT_MSG_FILE")
# Adds a separator between prefix and the message
if [ "$FIRST" = "#" ] || [ "$FIRST" = "\n" ]
then
PREFIX="${PREFIX}\n"
else
# If other chars (not empty)
if [ ${#FIRST} -ge 1 ]
then
PREFIX="${PREFIX} "
fi
fi
# Writes the prefix into the message file
printf %s "$PREFIX$(cat "$COMMIT_MSG_FILE")" > "$COMMIT_MSG_FILE"
Arquivo /config.sh
:
#!/bin/sh
git config include.path ../.gitconfig
Para testar o hook, simule uma demanda e faça um commit:
git checkout -b dev01
touch arquivo
git add arquivo
git commit -m "Primeiro arquivo"
git log -1 --pretty=%B
Deverá aparecer “[dev01] Primeiro arquivo”.
Agora o teste:
git checkout master
git branch -d dev01
Referências
-
Patterns for Managing Source Code Branches (Martin Fowler, 2020)
https://martinfowler.com/articles/branching-patterns.html -
4 branching workflows for Git (Patrick Porto, 2018)
https://medium.com/@patrickporto/4-branching-workflows-for-git-30d0aaee7bf -
The Dymitruk Model (Adam Dymitruk, 2012)
http://dymitruk.com/blog/2012/02/05/branch-per-feature -
Git Flow - A successful Git branching model (Vincent Driessen, 2020) [descontinuado]
https://nvie.com/posts/a-successful-git-branching-model -
GitHub Flow (Scott Chacon, 2011)
http://scottchacon.com/2011/08/31/github-flow.html -
GitLab Flow (Sytse Sijbrandij, 2014)
https://about.gitlab.com/blog/2014/09/29/gitlab-flow -
One Flow – a Git branching model and workflow (Adam Ruka, 2017)
https://www.endoflineblog.com/oneflow-a-git-branching-model-and-workflow -
GitHub Docs (2020)
https://docs.github.com/pt -
Git SCM (2020)
https://git-scm.com