Conceitos básicos: branching, merging, rebasing e pull requests
Version Control System (Sistema de Controle de Versão) é um sistema que registra as alterações feitas em um arquivo ou conjunto de arquivos ao longo do tempo, armazenando as modificações junto com a assinatura e a descrição da alteração realizada pelo autor[1]. Dessa forma, ele permite reverter determinados arquivos ou até um projeto inteiro para um estado anterior, possibilitando a recuperação de arquivos perdidos ou a correção de defeitos no projeto. A partir disso, surgiram três tipos de VCS: Sistemas Locais de Controle de Versão, que gerenciam versões de arquivos em um único computador; Sistemas Centralizados de Controle de Versão, que utilizam um repositório central onde todos os arquivos e versões são armazenados; e Sistemas Distribuídos de Controle de Versão, nos quais cada usuário tem uma cópia completa do repositório, incluindo o histórico de versões.
Neste tutorial, serão apresentados alguns conceitos utilizados para gerenciar um projeto desenvolvido e hospedado em um Sistema Distribuído de Controle de Versão, e serão utilizados os comandos do Git[2] — o sistema mais popular desse tipo — como exemplo.
Branching (Ramificação)[3]
[editar | editar código-fonte]Um branch é uma ramificação do código-fonte que permite trabalhar de forma isolada, sem afetar a versão principal do projeto. Isso permite que diferentes funcionalidades, correções ou experimentos sejam desenvolvidos paralelamente, de forma independente.
Exemplo
[editar | editar código-fonte]A figura a seguir ilustra um exemplo de repositório, com os commits representados como círculos, os branches como retângulos e o branch atual destacado com cor verde. Nesse caso, o repositório possui 3 commits, 1 branch, e o branch atual é o master
.
Criação do Novo Branch
[editar | editar código-fonte]Para criar um novo branch, utiliza-se o seguinte comando descrito abaixo. Quando o branch de origem é omitido, o novo branch é criado a partir do branch em que você está trabalhando no momento.
$ git branch <nome-do-novo-branch> <branch-de-origem>
Mudar Branch de trabalho
[editar | editar código-fonte]Para mudar o branch de trabalho, utiliza-se o seguinte comando. Note que isso não pode ser feito caso o branch atual em que você está trabalhando possua alterações não commitadas:
$ git checkout <nome-do-branch>
Merge (Mesclagem)[3]
[editar | editar código-fonte]Quando o trabalho é concluído, as modificações do branch <nome-do-branch> poderão ser integradas ao branch de trabalho atual por meio de um merge, utilizando o seguinte comando no Git:
$ git merge <nome-do-branch>
Exemplo
[editar | editar código-fonte]Considere a seguinte situação, que apresenta um histórico de commits mais complexo, envolvendo múltiplos branches e modificações nos arquivos, destacadas pelas cores diferentes no arquivo:
Fast-Forward Merge
[editar | editar código-fonte]No exemplo mostrado acima, quando é chamado o comando git merge Branch1
, como o commit atual do Branch1
pode ser alcançado a partir do commit atual do Master
, o Git simplesmente move o ponteiro para a frente, porque não há alterações divergentes para mesclar — isso é conhecido como um merge "Fast-Forward".
Deletar um Branch desnecessário
[editar | editar código-fonte]
Como não precisamos mais do Branch1
, podemos chamar o seguinte comando para deletá-lo. Dessa forma, deixaremos o histórico mais limpo e facilitaremos a visualização e manutenção do projeto posteriormente. Este passo será omitido nas próximas partes do tutorial para facilitar a leitura. Note que isso não quer dizer que, ao realizar o comando de merge, o Git deleta automaticamente o branch mesclado.
$ git branch -d <nome-do-branch>
Three-way Merge
[editar | editar código-fonte]Quando tentamos chamar agora o comando git merge Branch2
, note que o commit atual do Master
e do Branch2
estão em ramificações distintas, ou seja, não é possível acessar diretamente o commit do Branch2
a partir do commit do Master
. Nesse caso, ocorre um processo conhecido como "Three-Way Merge", no qual o Git acessará os seguintes três commits: o commit ancestral comum do Master
e Branch2
, o commit atual do Master
e o commit atual do Branch2
. Em seguida, o Git compara essas três versões e tenta combinar automaticamente as mudanças feitas em cada branch desde o ponto comum. Se não houver conflitos, o Git mescla as alterações e cria um commit de merge especial, que tem mais de um ancestral, como o exemplo mostrado abaixo.
Por fim, ao chamar o comando git merge Branch3
, note que o arquivo F1
foi modificado em ambos os commits. Nesse caso, o Git reportaria um conflito de merge e solicitaria ao usuário resolver o conflito:
CONFLICT (content): Merge conflict in F1.cpp
Automatic merge failed; fix conflicts and then commit the result.
Para verificar os arquivos em conflito, também pode ser utilizado o comando git status
, que retornaria uma mensagem semelhante à seguinte:
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: F1.cpp
no changes added to commit (use "git add" and/or "git commit -a")
Abrindo o arquivo F1
para edição, você encontrará seções no seguinte formato, que indicam um conflito:
<<<<<<< HEAD
cout << "Este é um tutorial feito no curso MAC0332 - Engenharia de Software (2024)";
=======
cout << "O curso MAC0332 é ministrada pelo professor Pedro Henrique Dias Valle no IME-USP";
>>>>>>> Branch3
Todas as linhas acima de =======
e abaixo de >>>>>>> HEAD
pertencem ao arquivo que está atualmente no seu branch de trabalho (no nosso caso, o master
), enquanto tudo que estiver abaixo de =======
e acima de >>>>>>> Branch3
pertence ao Branch3
. Para solucionar o conflito, basta escolher um dos lados ou escrever um conteúdo à sua escolha, substituindo o bloco completo anterior:
cout << "Autor deste tutorial é Fernando Yang";
Depois de resolver todos os conflitos, é necessário usar o comando git add
para adicionar os arquivos que estavam em conflito ao staging, e usar o comando git commit
para finalizar o merge.
Rebasing[4] [5]
[editar | editar código-fonte]Assim como o merge, o rebase é outra maneira de integrar as mudanças de um branch para outro. Embora os arquivos resultantes sejam semelhantes, o histórico de commits criado por esses métodos é diferente. Para utilizar o rebase para unir as modificações dos branches, utiliza-se o seguinte comando:
git rebase <nome-do-branch>
Exemplo
[editar | editar código-fonte]Considere a seguinte situação, que apresenta um histórico de commits com dois branches não lineares, com alguns commits marcados com seus respectivos nomes:
Histórico de Commit utilizando Merge
[editar | editar código-fonte]Quando utilizamos o merge para integrar o conteúdo do Branch
no Master
, o Git cria um commit de merge C3
que une as mudanças dos dois branches, preservando o histórico original de ambos. Nesse caso, o histórico se torna não linear, com uma bifurcação visível, indicando que os branches foram unidos.
Histórico de Commit utilizando Rebase
[editar | editar código-fonte]Agora, quando utilizamos o rebase para integrar o conteúdo do Branch
no Master
, o Git pega todas as alterações que foram confirmadas no Master e as reaplica no branch atual. Nesse caso, o histórico resultante é linear, pois o Git reescreve a sequência de commits de forma que os commits do Master
parecem ter sido feitos após os commits do Branch
, sem criar um commit de merge. No entanto, isso reescreve o histórico, o que também altera o timestamp dos commits e isso pode ser problemático caso o branch for público.
Conflito
[editar | editar código-fonte]Assim como no merge, ao utilizar o rebase para integrar dois branches, pode ocorrer um conflito nas modificações realizadas nos arquivos. Para resolver isso, o processo é semelhante ao procedimento descrito na seção sobre merge. No entanto, após resolver todos os conflitos, em vez de usar git commit
, você deve usar o comando git rebase --continue
para prosseguir com o rebase.
Pull Requests[6] [7]
[editar | editar código-fonte]Diferente dos conceitos anteriores, o Git, por si só, não possui o conceito de Pull Request. Esse é um mecanismo de colaboração amplamente utilizado em plataformas de hospedagem de código, como GitHub[8] e GitLab[9], que facilita a colaboração de uma comunidade de desenvolvedores em um mesmo projeto.
A ideia central do Pull Request (PR) é que, em vez de permitir que um desenvolvedor integre diretamente suas modificações — geralmente em um branch de desenvolvimento ou branch de feature — sejam integradas a outro branch, ele precisa fazer uma solicitação para que isso aconteça. No processo de PR, os mantenedores do projeto podem revisar e discutir o conjunto de alterações propostas. Eles podem, durante essa revisão, solicitar ajustes no código ou até recusar a solicitação. Esse processo de revisão ajuda a garantir a qualidade do código, evitando bugs indesejáveis e manter uma boa organização do repositório.
Observações
[editar | editar código-fonte]Este tutorial cobre apenas o básico de cada um dos conceitos de branch, merge, rebase e pull requests. Para mais detalhes, consulte a página de ajuda de cada comando e as páginas de referência usadas neste tutorial.
Referências
[editar | editar código-fonte]- ↑ Começando - Sobre Controle de Versão
- ↑ Git (Site Oficial)
- ↑ 3,0 3,1 Branches no Git - O básico de Ramificação (Branch) e Mesclagem (Merge)
- ↑ Branches no Git - Rebase
- ↑ Merging vs. rebasing
- ↑ GitHub - Contribuindo em um projeto
- ↑ Sobre solicitação de pull
- ↑ GitHub (Site Oficial)
- ↑ GitLab (Site Oficial)