Dev (Back & Front)ARTIGO

GIT: quebrando um commit e cancelando algumas alterações

Cenário: Foi feito o push de um commit contendo
várias alterações no código. Porém, após ter sido feito o code review
foi detectado que algumas mudanças não seriam mais necessárias, e/ou
deveriam ser implementadas de outra forma. Claro que seria possível ir
no código fonte e ir apagando todas as alterações que foram feitas.
Mas, será que essa “limpeza” sairia às mil maravilhas? Será que o
código voltaria a ficar da mesma forma que antes de as alterações terem
sido feitas? Acho difícil!

Elaborei um pequeno exemplo, apenas para tentar ilustrar o cenário acima.

Temos os seguintes commits em nosso Projeto:

Repare que foi criado um branch para resolver um defeito, no nosso
caso, foi adicionar um novo campo, País. Mas por algum motivo o
registro de idade foi alterado acidentalmente.

No caso acima, é fácil ir ao arquivo e desfazer as alteração
incorreta no campo “idade” antes de fazer o rebase/merge, eu sei. Mas,
tente imaginar códigos reais, complexos, onde voltar ao código fonte e
desfazer as alterações não seria a melhor solução =)

Uma solução para trazer todas as alterações e escolher o que vai ser
efetivado para o merge/rebase é trazer o commit de volta para o stage
do git para trabalhar nas alterações. Para isso, vamos utilizar o
comando git format-patch para recuperar os commits.
Em seguida, criaremos um novo branch para trabalharmos nas modificações
e finalmente fazer o rebase com o branch master:

Alberto:ProjectX Alberto$ git branch<br />* Defect123<br />  master<br />Alberto:ProjectX Alberto$ git status<br /># On branch master<br />nothing to commit (working directory clean)<br /> <br />Alberto:ProjectX Alberto$ git log<br />commit ea4f2953c305b0453fd20f27b3043ebf96d541df<br />Author: Alberto Leal <albertonb@gmail.com><br />Date:   Sat May 23 15:42:16 2009 -0300<br /> <br />    Adding new fields<br /> <br />commit 656236111360762d92f9efacb05e27b5cd067a04<br />Author: Alberto Leal <albertonb@gmail.com><br />Date:   Sat May 23 15:41:35 2009 -0300<br /> <br />    Some new changes<br /> <br />commit 131519b37c1d1415e962d6fd23d4058c93438870<br />Author: Alberto Leal <albertonb@gmail.com><br />Date:   Sat May 23 15:40:40 2009 -0300<br /> <br />    Initial Commit<br />Alberto:ProjectX Alberto$ git format-patch master<br />0001-Some-new-changes.patch<br />0002-Adding-new-fields.patch

Repare que os patches foram gerados a partir do Defect123 em
relação ao branch master, ou seja, somente as diferenças entre eles.

Para escolher quais modificações vão entrar para fazer o
merge/rebase com o master, é necessário que se crie um novo branch a
partir do master – limpo, sem as modificações de Defect123 e use o
comando git apply para aplicar os patches no stage. 
Agora que os patches com os últimos commits já foram gerados, chegou a
hora de jogá-los novamente no stage:

Alberto:ProjectX Alberto$ git checkout master<br />Switched to branch "master"<br />Alberto:ProjectX Alberto$ git checkout -b Defect123_Changes<br />Switched to a new branch "Defect123_Changes"<br />Alberto:ProjectX Alberto$ git apply 0001-Some-new-changes.patch<br />Alberto:ProjectX Alberto$ git apply 0002-Adding-new-fields.patch<br />Alberto:ProjectX Alberto$ git log<br />commit 131519b37c1d1415e962d6fd23d4058c93438870<br />Author: Alberto Leal &lt;albertonb@gmail.com&gt;<br />Date:   Sat May 23 15:40:40 2009 -0300<br /> <br />    Initial Commit<br />Alberto:ProjectX Alberto$ git status<br /># On branch Defect123_Changes<br /># Changed but not updated:<br />#   (use "git add &lt;file&gt;..." to update what will be committed)<br />#<br />#       modified:   config.txt<br />#<br /># Untracked files:<br />#   (use "git add &lt;file&gt;..." to include in what will be committed)<br />#<br />#       0001-Some-new-changes.patch<br />#       0002-Adding-new-fields.patch<br />no changes added to commit (use "git add" and/or "git commit -a")

Veja que o arquivo config.txt foi alterado. Agora, vamos adicionar
as alterações para serem comitadas, mas para isso vamos utilizar o
comando git add -p (similar ao add interativo):

Alberto:ProjectX Alberto$ git add -p config.txt<br />diff --git a/config.txt b/config.txt<br />index ac82771..b36405c 100644<br />--- a/config.txt<br />+++ b/config.txt<br />@@ -1,6 +1,8 @@<br /> Nome: Alberto Leal<br />-email: albertonb@gmail.com<br />-idade: 24<br />+Email: albertonb@gmail.com<br />+Idade: 2423434<br /> Empresa: IBM<br />+Telefone: 12-1234-5678<br />+País: Brasil<br /> Sexo: Masculino<br /> Estado civil: solteiro<br />Stage this hunk [y/n/a/d/s/?]? s<br />Split into 2 hunks.<br />@@ -1,4 +1,4 @@<br /> Nome: Alberto Leal<br />-email: albertonb@gmail.com<br />-idade: 24<br />+Email: albertonb@gmail.com<br />+Idade: 2423434<br /> Empresa: IBM<br />Stage this hunk [y/n/a/d/j/J/?]? n<br />@@ -4,3 +4,5 @@<br /> Empresa: IBM<br />+Telefone: 12-1234-5678<br />+País: Brasil<br /> Sexo: Masculino<br /> Estado civil: solteiro<br />Stage this hunk [y/n/a/d/K/?]? y

Utilizando o comando acima, podemos quebrar as alterações feitas no
arquivo utilizando a opção “s” (split). Depois você informa se deseja
adicionar os fragmentos ao stage, utilizando as opções “y”- yes e “n”-
no. Todos os fragmentos que você desejar adicionar no stage para
comitá-lo, utilize a opção “y”. No pequeno exemplo acima, apenas foi
mantido no commit a adição dos novos campos: Telefone e País. A
alteração equivocada no campo “idade”foi eliminada do commit. Só nos
resta efetivar as alterações comitando as mudanças:

Alberto:ProjectX Alberto$ git commit -m 'Cutting branch'<br />Created commit 9e365a2: Cutting branch<br /> 1 files changed, 2 insertions(+), 0 deletions(-)<br />Alberto:ProjectX Alberto$ git checkout config.txt<br />Alberto:ProjectX Alberto$ cat config.txt<br />Nome: Alberto Leal<br />email: albertonb@gmail.com<br />idade: 24<br />Empresa: IBM<br />Telefone: 12-1234-5678<br />País: Brasil<br />Sexo: Masculino<br />Estado civil: solteiro

Cuidado para não adicionar o arquivo novamente ao stage (git add
config.txt). As alterações que você fez já estão lá, prontas para serem
comitadas. Se você se esquecer e adicionar o arquivo novamente ao
stage, você perderá todas as mudanças que você fez no add interativo. É
aconselhável que, logo após o comando git commit, você execute git checkout <file>, como mostrado acima. Desse jeito, você pegará o arquivo que está no repositório atualizado.

Agora, só nos resta fazer o rebase com o branch principal:

Alberto:ProjectX Alberto$ git checkout master<br />Switched to branch "master"<br />Alberto:ProjectX Alberto$ git rebase Defect123_Changes<br />First, rewinding head to replay your work on top of it...<br />Fast-forwarded master to Defect123_Changes.

É isso, pessoal! Espero que a técnica apresentada nesse artigo lhe seja útil algum dia, assim como foi para mim.

Abraços!

é Bacharel em Sistemas de Informação e pós graduando Business Intelligence. Trabalha em projetos internacionais JEE na IBM, como Analista Programador Java/IT Specialist.

Ver perfil