Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Ambiente de Desenvolvimento

Imagine a seguinte cena: um grupo de 4 alunos precisa entregar um projeto de software para uma disciplina da faculdade. Cada aluno ficou responsável por uma parte do código. O prazo está apertado. Eles combinaram de trocar as partes pelo WhatsApp.

O primeiro aluno manda o arquivo projeto.zip. O segundo baixa, faz suas alterações, e manda de volta projeto_v2.zip. O terceiro, que não viu a mensagem do segundo, fez suas alterações na versão do primeiro e manda projeto_atualizado.zip. Agora ninguém sabe qual é a versão mais recente. O quarto aluno tenta juntar tudo à mão, num domingo à noite, e estraga uma função que já estava funcionando. Resultado: projeto_final_v3_AGORA_VAI.zip.

Se você já passou por algo parecido, sabe que o problema não é competência. É a falta de uma ferramenta adequada para coordenar o trabalho de várias pessoas sobre os mesmos arquivos. Essa ferramenta existe e se chama controle de versão.

Esse capítulo apresenta os conceitos e práticas de controle de versão utilizando o Git, a ferramenta mais utilizada no mundo para esse fim. Depois, veremos como organizar um projeto com vários componentes (backend, frontend web, frontend mobile) dentro de um único repositório.

Controle de Versão

Um sistema de controle de versão (Version Control System ou VCS) é um software que registra as alterações feitas em arquivos ao longo do tempo. Ele resolve três problemas fundamentais:

  1. Histórico. Você consegue ver exatamente o que mudou, quando mudou e quem mudou. Se algo quebrar, é possível voltar a uma versão anterior.
  2. Colaboração. Várias pessoas podem trabalhar nos mesmos arquivos ao mesmo tempo sem sobrescrever o trabalho umas das outras.
  3. Rastreabilidade. Cada conjunto de alterações vem acompanhado de uma mensagem explicando o motivo da mudança. Meses depois, é possível entender por que aquele trecho de código existe.

Existem vários sistemas de controle de versão: CVS, Subversion (SVN), Mercurial e Git. Hoje, o Git é o padrão da indústria. Foi criado em 2005 por Linus Torvalds (o mesmo criador do Linux) para gerenciar o código-fonte do kernel Linux, que já naquela época era um projeto com milhares de contribuidores.

Instalando e Configurando o Git

Antes de tudo, instale o Git:

  • Linux (Debian/Ubuntu):
    sudo apt install git
    
  • macOS:
    brew install git
    
  • Windows: Instale o Git for Windows. Ele inclui o Git Bash, um terminal que emula o ambiente Linux. Se você usa o WSL (recomendado neste livro), instale normalmente via apt dentro do WSL.

Após a instalação, configure seu nome e e-mail. Essas informações ficam gravadas em cada alteração que você registrar:

git config --global user.name "Maria Silva"
git config --global user.email "maria@example.com"

Você pode verificar a configuração com:

$ git config --global --list
user.name=Maria Silva
user.email=maria@example.com

Repositório

Um repositório (ou repo) é um diretório cujo histórico de alterações é gerenciado pelo Git. Para transformar um diretório qualquer em um repositório Git, usamos o comando git init:

$ mkdir meu-projeto
$ cd meu-projeto
$ git init
Initialized empty Git repository in /home/maria/meu-projeto/.git/

A partir desse momento, o Git passa a monitorar esse diretório. Ele cria uma pasta oculta chamada .git/ que armazena todo o histórico. Você nunca precisa mexer diretamente nessa pasta.

O Ciclo Básico: Staging e Commit

O Git não registra alterações automaticamente. Você decide o que salvar e quando. O fluxo básico tem dois passos:

  1. Staging (preparação): você marca quais alterações deseja incluir no próximo registro.
  2. Commit (registro): você grava essas alterações no histórico, junto com uma mensagem descritiva.

Vamos ver na prática. Suponha que criamos um arquivo index.html dentro do nosso repositório:

<!DOCTYPE html>
<html>
<head>
    <title>Meu Site</title>
</head>
<body>
    <h1>Olá, mundo!</h1>
</body>
</html>

Se rodarmos git status, o Git nos diz que existe um arquivo novo que ele ainda não está rastreando:

$ git status
On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	index.html

nothing added to commit but untracked files present (use "git add" to track)

Para incluir esse arquivo no próximo commit, usamos git add:

$ git add index.html

Agora o arquivo está na área de staging (staging area). Podemos confirmar com git status:

$ git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
	new file:   index.html

O Git está dizendo: “quando você fizer o próximo commit, vou incluir a criação do arquivo index.html”. Para gravar de fato, usamos git commit com uma mensagem que descreve a alteração:

$ git commit -m "Criar página inicial com título e saudação"
[main (root-commit) a1b2c3d] Criar página inicial com título e saudação
 1 file changed, 9 insertions(+)
 create mode 100644 index.html

Pronto. A alteração está registrada no histórico. O identificador a1b2c3d é um hash (resumo criptográfico) que identifica esse commit de forma única. Na prática, o hash completo tem 40 caracteres hexadecimais (ex: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0), mas o Git permite usar apenas os primeiros caracteres, desde que não haja ambiguidade.

Vários Arquivos, Um Commit

Não é necessário fazer um commit por arquivo. Você pode adicionar vários arquivos à área de staging e registrá-los todos de uma vez. O importante é que cada commit represente uma unidade lógica de mudança: uma alteração coerente, com um propósito claro.

Por exemplo, se você criou um arquivo style.css e modificou o index.html para referenciá-lo, faz sentido que ambas as alterações entrem no mesmo commit, pois juntas elas formam uma unidade: “adicionar estilização à página inicial”.

$ git add index.html style.css
$ git commit -m "Adicionar estilização à página inicial"
[main e4f5a6b] Adicionar estilização à página inicial
 2 files changed, 18 insertions(+), 1 deletion(-)
 create mode 100644 style.css

Visualizando o Histórico

O comando git log mostra o histórico de commits, do mais recente ao mais antigo:

$ git log
commit e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3 (HEAD -> main)
Author: Maria Silva <maria@example.com>
Date:   Mon Aug 11 14:32:10 2026 -0300

    Adicionar estilização à página inicial

commit a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
Author: Maria Silva <maria@example.com>
Date:   Mon Aug 11 14:15:42 2026 -0300

    Criar página inicial com título e saudação

Cada entrada mostra o hash completo, o autor, a data e a mensagem. Para uma versão mais compacta, use git log --oneline:

$ git log --oneline
e4f5a6b Adicionar estilização à página inicial
a1b2c3d Criar página inicial com título e saudação

Vendo o que Mudou

Antes de fazer um commit, é boa prática verificar exatamente o que mudou. O comando git diff mostra as alterações que ainda não foram adicionadas à área de staging:

$ git diff
diff --git a/index.html b/index.html
index 3b18e51..f3c4a12 100644
--- a/index.html
+++ b/index.html
@@ -5,6 +5,7 @@
 </head>
 <body>
     <h1>Olá, mundo!</h1>
+    <p>Bem-vindo ao meu site.</p>
 </body>
 </html>

As linhas com + foram adicionadas; linhas com - foram removidas. Nesse caso, adicionamos um parágrafo de boas-vindas.

Branches

Até agora trabalhamos apenas em uma linha reta de commits. Mas imagine que você quer experimentar uma nova funcionalidade sem arriscar quebrar o que já está funcionando. Ou que dois colegas querem trabalhar em funcionalidades diferentes ao mesmo tempo. É para isso que existem os branches (ramos).

Uma branch é uma linha independente de desenvolvimento. Quando você cria uma branch, está criando um espaço isolado onde pode fazer commits sem afetar a branch principal.

A branch padrão geralmente se chama main1. Vamos criar uma branch nova para adicionar uma página “sobre”:

$ git branch pagina-sobre

Esse comando criou a branch, mas ainda estamos na main. Para mudar para a nova branch, usamos git checkout:

$ git checkout pagina-sobre
Switched to branch 'pagina-sobre'

Existe um atalho que cria a branch e muda para ela de uma vez:

$ git checkout -b pagina-sobre
Switched to a new branch 'pagina-sobre'

Agora, qualquer commit que fizermos será registrado na branch pagina-sobre, sem afetar a main. Vamos criar o arquivo sobre.html e fazer um commit:

$ git add sobre.html
$ git commit -m "Adicionar página sobre com informações do projeto"
[pagina-sobre 7d8e9f0] Adicionar página sobre com informações do projeto
 1 file changed, 12 insertions(+)
 create mode 100644 sobre.html

Se voltarmos para a main, o arquivo sobre.html não existe:

$ git checkout main
Switched to branch 'main'
$ ls
index.html  style.css

O arquivo só existe na branch pagina-sobre. As duas branches têm históricos diferentes a partir do ponto onde divergiram.

Merge

Quando a funcionalidade na branch está pronta e testada, queremos incorporá-la à branch principal. Esse processo se chama merge (mesclagem).

Para mesclar a branch pagina-sobre na main, primeiro vamos para a main e depois executamos o merge:

$ git checkout main
Switched to branch 'main'
$ git merge pagina-sobre
Updating e4f5a6b..7d8e9f0
Fast-forward
 sobre.html | 12 ++++++++++++
 1 file changed, 12 insertions(+)
 create mode 100644 sobre.html

Agora a main contém tudo o que estava em pagina-sobre. O Git disse “Fast-forward” porque não houve alterações na main enquanto trabalhávamos na outra branch. Ele simplesmente avançou o ponteiro.

Após o merge, a branch pagina-sobre pode ser apagada:

$ git branch -d pagina-sobre
Deleted branch pagina-sobre (was 7d8e9f0).

Conflitos

Nem todo merge é tão simples. Quando duas branches alteram a mesma parte do mesmo arquivo, o Git não consegue decidir qual versão manter. Isso é um conflito.

Suponha que dois desenvolvedores, Maria e João, criaram branches separadas a partir da main. Maria alterou a saudação no index.html para “Olá, pessoal!” na branch saudacao-maria. João alterou a mesma linha para “Bem-vindos ao site!” na branch saudacao-joao.

Maria faz o merge primeiro. Tudo corre bem. Agora João tenta fazer o merge da sua branch:

$ git checkout main
$ git merge saudacao-joao
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

O Git nos avisa que houve um conflito. Se abrirmos o index.html, veremos algo assim:

<!DOCTYPE html>
<html>
<head>
    <title>Meu Site</title>
</head>
<body>
<<<<<<< HEAD
    <h1>Olá, pessoal!</h1>
=======
    <h1>Bem-vindos ao site!</h1>
>>>>>>> saudacao-joao
    <p>Bem-vindo ao meu site.</p>
</body>
</html>

O trecho entre <<<<<<< HEAD e ======= é a versão que já está no main (a alteração da Maria). O trecho entre ======= e >>>>>>> saudacao-joao é a versão da branch do João. Para resolver o conflito, editamos o arquivo e escolhemos a versão final. Podemos manter uma, a outra, ou combinar as duas:

    <h1>Olá, pessoal! Bem-vindos ao site!</h1>

Depois de editar, removemos os marcadores de conflito (<<<<<<<, =======, >>>>>>>), adicionamos o arquivo à staging area e fazemos o commit:

$ git add index.html
$ git commit -m "Resolver conflito: combinar saudações de Maria e João"
[main b3c4d5e] Resolver conflito: combinar saudações de Maria e João

Conflitos são normais. Sempre que duas pessoas mexem no mesmo trecho, o Git não tenta adivinhar qual versão é a correta. Ele mostra as duas e deixa a decisão com você.

Colaboração com Repositórios Remotos

Até agora, tudo aconteceu localmente, na sua máquina. Mas para colaborar com outras pessoas, é preciso que o repositório exista em algum lugar acessível por todos. Esse lugar é um repositório remoto (remote).

O serviço mais popular para hospedar repositórios remotos é o GitHub. Existem outros, como GitLab e Bitbucket, mas o GitHub é de longe o mais usado. Ao criar um repositório no GitHub, você obtém uma URL como https://github.com/maria/meu-projeto.git.

Clone

Para obter uma cópia local de um repositório que já existe no GitHub, usamos git clone:

$ git clone https://github.com/maria/meu-projeto.git
Cloning into 'meu-projeto'...
remote: Enumerating objects: 18, done.
remote: Total 18 (delta 4), reused 18 (delta 4), pack-reused 0
Receiving objects: 100% (18/18), done.
Resolving deltas: 100% (4/4), done.

Isso cria um diretório meu-projeto/ com todo o histórico de commits. O repositório clonado já vem configurado com um remote chamado origin, apontando para a URL de onde foi clonado.

Push e Pull

Depois de fazer commits localmente, você envia suas alterações para o remoto com git push:

$ git push origin main
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 312 bytes | 312.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
To https://github.com/maria/meu-projeto.git
   e4f5a6b..b3c4d5e  main -> main

O comando git push origin main envia os commits da branch main local para o remote origin.

Para trazer as alterações que outros membros da equipe enviaram, usamos git pull:

$ git pull origin main
remote: Enumerating objects: 5, done.
remote: Total 5 (delta 2), reused 5 (delta 2), pack-reused 0
Unpacking objects: 100% (5/5), done.
From https://github.com/maria/meu-projeto.git
   b3c4d5e..f6a7b8c  main -> origin/main
Updating b3c4d5e..f6a7b8c
Fast-forward
 contato.html | 8 ++++++++
 1 file changed, 8 insertions(+)
 create mode 100644 contato.html

O git pull é equivalente a buscar as alterações do remoto (git fetch) e mesclá-las na branch local (git merge). Se houver conflitos, o processo de resolução é o mesmo que vimos antes.

O Fluxo de Trabalho Baseado em Branches

Na prática, equipes raramente fazem commits diretamente na main. O fluxo de trabalho mais comum segue estes passos:

  1. O desenvolvedor cria uma branch a partir da main para trabalhar numa funcionalidade específica (ex: adicionar-pagina-contato).
  2. Ele faz seus commits nessa branch.
  3. Quando a funcionalidade está pronta, ele envia a branch para o remoto (git push origin adicionar-pagina-contato).
  4. Ele abre um pull request (PR) no GitHub, que é uma proposta de incorporar as alterações dessa branch na main.
  5. Outros membros da equipe revisam o código, fazem comentários e sugerem mudanças.
  6. Após aprovação, o pull request é mesclado no main.

Esse fluxo tem duas vantagens importantes. Primeiro, a main nunca recebe código que não foi revisado por outra pessoa. Segundo, cada funcionalidade é desenvolvida de forma isolada, reduzindo a chance de conflitos.

.gitignore

Nem todo arquivo do seu projeto deve ser rastreado pelo Git. Arquivos gerados automaticamente (como node_modules/, que contém dependências baixadas), arquivos de configuração local do editor (como .vscode/settings.json) e, principalmente, arquivos com dados sensíveis (como senhas e chaves de API) não devem ir para o repositório.

Para dizer ao Git quais arquivos ignorar, criamos um arquivo chamado .gitignore na raiz do repositório:

node_modules/
dist/
.env
*.log

Cada linha define um padrão. Nesse exemplo:

  • node_modules/ ignora o diretório inteiro de dependências.
  • dist/ ignora a pasta de arquivos gerados pelo build.
  • .env ignora o arquivo de variáveis de ambiente (que costuma conter senhas).
  • *.log ignora qualquer arquivo com extensão .log.

O .gitignore em si deve ser commitado, pois todos os membros da equipe precisam ignorar os mesmos arquivos.

Organizando o Projeto: Monorepo

Em Introdução ao Desenvolvimento de Aplicações, vimos que uma aplicação é composta de vários componentes: backend, frontend web, frontend mobile, banco de dados. Cada um desses componentes tem seu próprio código, suas próprias dependências e seus próprios scripts de execução.

Uma pergunta natural é: cada componente deve ter seu próprio repositório, ou todos devem ficar juntos em um único repositório?

Monorepo vs. Multi-repo

Existem duas abordagens principais:

  • Multi-repo: cada componente vive em um repositório separado. O backend fica em github.com/equipe/backend, o frontend web em github.com/equipe/web, o mobile em github.com/equipe/mobile.
  • Monorepo: todos os componentes vivem no mesmo repositório, em diretórios separados.

A abordagem multi-repo parece mais organizada à primeira vista, mas na prática ela cria problemas de coordenação. Quando uma mudança no backend exige uma mudança correspondente no frontend (por exemplo, renomear um campo da API), você precisa abrir dois pull requests em repositórios diferentes, e garantir que os dois sejam mesclados juntos. Versões ficam dessincronizadas. Configurações são duplicadas.

A abordagem monorepo simplifica isso: uma única alteração que corta o backend e o frontend fica em um único commit, um único pull request, uma única revisão. A equipe inteira vê o projeto como um todo, não como peças separadas.

A estrutura típica de um monorepo para uma aplicação com backend, web e mobile é:

meu-projeto/
├── backend/
│   ├── src/
│   ├── package.json
│   └── tsconfig.json
├── web/
│   ├── src/
│   ├── package.json
│   └── tsconfig.json
├── mobile/
│   ├── src/
│   ├── package.json
│   └── app.json
├── .gitignore
└── README.md

Cada diretório (backend/, web/, mobile/) é um componente independente, com suas próprias dependências e configuração. Mas todos vivem sob o mesmo repositório Git, compartilhando o mesmo histórico e o mesmo processo de revisão.

É essa a estrutura que o TamoJunto seguirá. Quando chegarmos ao próximo capítulo, vamos montar o repositório com esses três pontos de entrada.

Quando usar multi-repo

Monorepo não é sempre a melhor escolha. À medida que o projeto cresce e equipes diferentes ficam responsáveis por componentes diferentes, um monorepo pode se tornar lento (muitos arquivos, histórico grande) e ruidoso (notificações sobre partes do código que não te interessam). Grandes empresas como Google e Meta usam monorepos com ferramentas especializadas para lidar com essa escala. Projetos menores, como o que construiremos neste curso, se beneficiam da simplicidade do monorepo sem sofrer essas desvantagens.

A regra é simples: se a equipe é pequena e os componentes mudam juntos, comece com monorepo. Se os componentes forem verdadeiramente independentes (equipes diferentes, ciclos de deploy diferentes, nenhuma interface compartilhada), multi-repo faz mais sentido.

Conclusão

Com o Git, o cenário do projeto_final_v3_AGORA_VAI.zip deixa de existir. Cada alteração é rastreada, cada decisão fica registrada, e duas pessoas podem trabalhar no mesmo arquivo sem medo. O fluxo de branches e pull requests garante que código é revisado antes de entrar na branch principal.

A partir do próximo capítulo, vamos colocar isso em prática: criaremos o repositório do TamoJunto como um monorepo com backend, web e mobile.

Exercícios

  1. Instale o Git na sua máquina e configure seu nome e e-mail usando git config. Verifique com git config --global --list que as informações estão corretas.

  2. Crie um diretório chamado exercicio-git, inicialize um repositório Git nele (git init) e crie um arquivo notas.txt com qualquer conteúdo. Faça um commit com a mensagem “Adicionar arquivo de notas”. Verifique o histórico com git log --oneline.

  3. No mesmo repositório, crie uma branch chamada experimento. Nela, modifique o arquivo notas.txt e faça um commit. Depois, volte à main e faça uma modificação diferente na mesma linha de notas.txt. Tente fazer o merge. Resolva o conflito que surgir.

  4. Crie um repositório no GitHub (ou GitLab). Conecte seu repositório local a ele com git remote add origin <URL>. Envie seus commits com git push. Verifique no site que os arquivos aparecem.

  5. O arquivo .gitignore abaixo tem um problema. Identifique qual é e explique o risco:

    node_modules/
    dist/
    *.log
    

Referências

Para um estudo mais aprofundado sobre Git, recomendo o livro Pro Git, de Scott Chacon e Ben Straub, disponível gratuitamente em português em https://git-scm.com/book/pt-br/v2.


  1. Historicamente, a branch padrão se chamava master. Em 2020, o GitHub e outras plataformas mudaram o padrão para main. Ambos os nomes funcionam da mesma forma. A escolha é apenas de convenção.