cd ..
CI/CD

Understanding GitFlow, Pull Requests, and Branch Protection

When working alone on a small project, it’s common to make all changes directly to the main branch.

The flow typically looks like this:

git add .
git commit -m "adiciona nova funcionalidade"
git push origin main

This process can work for personal projects, but it starts to cause problems when multiple people work on the same repository.

Imagine a team where:

If everyone pushes changes directly to main, the chances of conflicts, failures, and unreviewed code reaching production increase.

To organize this process, we can use:

What is GitFlow?

GitFlow is a branch organization model in Git.

It defines a standardized way to separate code that is in production, code that is under development, and features that are still being built.

In the traditional model, the main branches are:

main
develop
feature
release
hotfix

Each one has a different responsibility.

The main Branch

The main branch represents the stable code of the project.

Typically, it contains the version that is ready for production or already published.

main
└── stable version of the project

For this reason, it is not recommended to develop features directly on it.

main should only receive changes that have already been:

The develop Branch

The develop branch gathers features being prepared for the next version.

develop
└── next version of the project

The different feature branches are integrated into develop.

When the next version is stable, it can proceed to a release branch or be integrated into main, depending on the project’s adopted workflow.

feature Branches

feature branches are used to develop new functionalities.

For example:

feature/login
feature/login-google
feature/cadastro-usuario

Each feature is developed in isolation.

This allows one person to work on the login screen while another develops the registration, without one change directly interfering with the other.

A common flow would be:

develop
   └── feature/login

Once the feature is ready, it returns to develop via a Pull Request.

Creating a Feature Branch

First, we access the development branch:

git checkout develop

We update the local code:

git pull origin develop

Then, we create the new branch:

git checkout -b feature/login

We can also use the more recent command:

git switch -c feature/login

After developing the feature, we add the files:

git add .

We create the commit:

git commit -m "feat: adiciona tela de login"

And push the branch to the remote repository:

git push -u origin feature/login

Now the branch will be available on GitHub.

release Branches

release branches are used to prepare a new version of the project.

Example:

release/1.2.0

At this stage, major features are usually not added.

The branch is used for:

When the version is ready, it can be integrated into main.

hotfix Branches

hotfix branches are used to fix urgent problems that are already in production.

Example:

hotfix/fix-login

Since the problem is occurring in the current version of the system, the branch is usually created from main.

git checkout main
git pull origin main
git checkout -b hotfix/corrigir-login

After the fix:

git add .
git commit -m "fix: corrige erro no login"
git push -u origin hotfix/corrigir-login

The fix can then be integrated back into main and also into develop, preventing the problem from reappearing in the next version.

GitFlow Overview

The flow can be represented as follows:

flowchart TD
    A[main] --> B[develop]
    B --> C[feature/login]
    B --> D[feature/cadastro]
    C --> B
    D --> B
    B --> E[release/1.0.0]
    E --> A
    A --> F[hotfix/corrigir-login]
    F --> A
    F --> B

The main idea is to separate each type of change.

main
└── stable code

develop
└── next version

feature/*
└── new features

release/*
└── version preparation

hotfix/*
└── urgent fixes

What is a Pull Request?

A Pull Request, also known as a PR, is a proposal to integrate changes from one branch into another.

For example:

feature/login → develop

In this case, we are proposing that the code developed in the feature/login branch be integrated into the develop branch.

In projects that do not use the develop branch, the flow might be:

feature/login → main

The Pull Request creates a review step before the merge.

Other people can:

Creating a Pull Request

After pushing the branch:

git push -u origin feature/login

We can open a Pull Request on GitHub.

In this Pull Request, we choose:

base: develop
compare: feature/login

This means:

target branch: develop
source branch: feature/login

The title could be:

feat: add login screen

And the description could explain:

## O que foi desenvolvido?

Foi criada a tela de login da aplicação.

## Alterações

- criação do formulário;
- validação dos campos;
- integração com a API;
- tratamento de mensagens de erro.

## Como testar?

1. Execute a aplicação.
2. Acesse `/login`.
3. Informe um usuário válido.
4. Verifique o redirecionamento.

A good description greatly facilitates the work of those who will review the code.

Pull Request Flow

flowchart TD
    A[Desenvolver funcionalidade] --> B[Criar commits]
    B --> C[Enviar branch para o GitHub]
    C --> D[Abrir Pull Request]
    D --> E[Revisar o código]
    E --> F{Alterações necessárias?}
    F -- Sim --> G[Corrigir o código]
    G --> B
    F -- Não --> H[Aprovar Pull Request]
    H --> I[Realizar merge]

A Pull Request is not just a button to merge branches.

It also serves as a:

What is Branch Protection?

Branch protection is a set of rules used to prevent dangerous changes on important branches.

Typically, we protect branches such as:

main
develop

Without a protection rule, someone could execute:

git push origin main

This would allow pushing code directly to the main branch, without review and without tests.

In a team, this type of change can break the application for everyone.

Branch Protection Rules

Protection rules can require that:

A protected workflow might look like this:

feature/login

Pull Request

Code Review

Automated Tests

Approval

Merge to main

Why Protect main?

The main branch represents the most important version of the repository.

If anyone can change it directly, problems such as:

Branch protection turns main into a controlled area.

No one enters through the window. Everyone goes through the Pull Request.

Example Rule for main

A common configuration might require:

Require Pull Request before merging
Require at least one approval
Require tests to pass
Require comments to be resolved
Block direct pushes
Prevent branch deletion

With this, even if someone tries to execute:

git push origin main

GitHub can block the operation.

What is CODEOWNERS?

The CODEOWNERS file allows you to define owners for different parts of the project.

It tells GitHub who should review a change depending on the modified files.

We can understand its function as follows:

When someone changes this part of the project, request a review from these people.

The file can be created in:

.github/CODEOWNERS

CODEOWNERS Example

Imagine a project with this structure:

frontend/
backend/
infra/
docs/
.github/

The file could be:

*                     @cesarsantos96
/frontend/            @dev-frontend
/backend/             @dev-backend
/infra/               @dev-infra
/docs/                 @cesarsantos96
/.github/              @cesarsantos96

In this example:

Using Teams in CODEOWNERS

In a GitHub organization, we can also use teams:

/frontend/     @empresa/time-frontend
/backend/      @empresa/time-backend
/infra/        @empresa/time-devops
/.github/      @empresa/time-devops

Thus, when someone changes a file within backend, GitHub can automatically request a review from the responsible team.

CODEOWNERS and Branch Protection

CODEOWNERS becomes even more useful when used in conjunction with protection rules.

The rule can require approval from the owners defined in the file.

The flow then works as follows:

flowchart TD
    A[Alteração no backend] --> B[Pull Request criado]
    B --> C[GitHub lê o CODEOWNERS]
    C --> D[Responsável pelo backend é solicitado]
    D --> E[Revisão do código]
    E --> F{Aprovado?}
    F -- Não --> G[Solicitar correções]
    F -- Sim --> H[Merge liberado]

Complete Flow

Using GitFlow, Pull Requests, branch protection, and CODEOWNERS, the process can look like this:

flowchart TD
    A[Atualizar develop] --> B[Criar feature/login]
    B --> C[Desenvolver funcionalidade]
    C --> D[Criar commits]
    D --> E[Enviar branch para o GitHub]
    E --> F[Abrir Pull Request]
    F --> G[CODEOWNERS solicita revisores]
    F --> H[Testes automatizados]
    G --> I{Revisão aprovada?}
    H --> J{Testes passaram?}
    I -- Não --> K[Realizar correções]
    J -- Não --> K
    K --> D
    I -- Sim --> L[Validação final]
    J -- Sim --> L
    L --> M[Merge na branch protegida]

Each tool has a responsibility:

GitFlow
└── organizes branches

Pull Request
└── organizes review and integration

Branch Protection
└── defines mandatory rules

CODEOWNERS
└── defines who should review

Conclusion

GitFlow helps organize development through branches with different responsibilities.

feature branches isolate new functionalities. release branches prepare new versions, while hotfix branches fix urgent problems in production.

Pull Requests create a review step before code integration.

Protection rules prevent direct changes to important branches, and the CODEOWNERS file helps route each change to the correct owners.

When these tools are used together, development becomes more organized, secure, and predictable.

The code stops following this path:

change → main

And starts following a controlled process:

branch → commit → Pull Request → review → tests → approval → merge

References

What did you think?