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

Posted by Alberto Leal on May 23rd, 2009

Dando sequência aos posts sobre GIT, aí vai mais uma dica: Como quebrar um commit e cancelar algumas alterações.

Assim como os posts anteriores, esse post foi inspirado em um problema que o meu time enfrentou semana passada.

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 das 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:

picture-22

picture-23

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Alberto:ProjectX Alberto$ git branch
* Defect123
  master
Alberto:ProjectX Alberto$ git status
# On branch master
nothing to commit (working directory clean)
 
Alberto:ProjectX Alberto$ git log
commit ea4f2953c305b0453fd20f27b3043ebf96d541df
Author: Alberto Leal <albertonb@gmail.com>
Date:   Sat May 23 15:42:16 2009 -0300
 
    Adding new fields
 
commit 656236111360762d92f9efacb05e27b5cd067a04
Author: Alberto Leal <albertonb@gmail.com>
Date:   Sat May 23 15:41:35 2009 -0300
 
    Some new changes
 
commit 131519b37c1d1415e962d6fd23d4058c93438870
Author: Alberto Leal <albertonb@gmail.com>
Date:   Sat May 23 15:40:40 2009 -0300
 
    Initial Commit
Alberto:ProjectX Alberto$ git format-patch master
0001-Some-new-changes.patch
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 as 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 modicaçõ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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Alberto:ProjectX Alberto$ git checkout master
Switched to branch "master"
Alberto:ProjectX Alberto$ git checkout -b Defect123_Changes
Switched to a new branch "Defect123_Changes"
Alberto:ProjectX Alberto$ git apply 0001-Some-new-changes.patch
Alberto:ProjectX Alberto$ git apply 0002-Adding-new-fields.patch
Alberto:ProjectX Alberto$ git log
commit 131519b37c1d1415e962d6fd23d4058c93438870
Author: Alberto Leal &lt;albertonb@gmail.com&gt;
Date:   Sat May 23 15:40:40 2009 -0300
 
    Initial Commit
Alberto:ProjectX Alberto$ git status
# On branch Defect123_Changes
# Changed but not updated:
#   (use "git add &lt;file&gt;..." to update what will be committed)
#
#       modified:   config.txt
#
# Untracked files:
#   (use "git add &lt;file&gt;..." to include in what will be committed)
#
#       0001-Some-new-changes.patch
#       0002-Adding-new-fields.patch
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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Alberto:ProjectX Alberto$ git add -p config.txt
diff --git a/config.txt b/config.txt
index ac82771..b36405c 100644
--- a/config.txt
+++ b/config.txt
@@ -1,6 +1,8 @@
 Nome: Alberto Leal
-email: albertonb@gmail.com
-idade: 24
+Email: albertonb@gmail.com
+Idade: 2423434
 Empresa: IBM
+Telefone: 12-1234-5678
+País: Brasil
 Sexo: Masculino
 Estado civil: solteiro
Stage this hunk [y/n/a/d/s/?]? s
Split into 2 hunks.
@@ -1,4 +1,4 @@
 Nome: Alberto Leal
-email: albertonb@gmail.com
-idade: 24
+Email: albertonb@gmail.com
+Idade: 2423434
 Empresa: IBM
Stage this hunk [y/n/a/d/j/J/?]? n
@@ -4,3 +4,5 @@
 Empresa: IBM
+Telefone: 12-1234-5678
+País: Brasil
 Sexo: Masculino
 Estado civil: solteiro
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). Após, 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 mundanças:

1
2
3
4
5
6
7
8
9
10
11
12
13
Alberto:ProjectX Alberto$ git commit -m 'Cutting branch'
Created commit 9e365a2: Cutting branch
 1 files changed, 2 insertions(+), 0 deletions(-)
Alberto:ProjectX Alberto$ git checkout config.txt
Alberto:ProjectX Alberto$ cat config.txt
Nome: Alberto Leal
email: albertonb@gmail.com
idade: 24
Empresa: IBM
Telefone: 12-1234-5678
País: Brasil
Sexo: Masculino
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 comitadass. 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.

picture-24

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

1
2
3
4
5
Alberto:ProjectX Alberto$ git checkout master
Switched to branch "master"
Alberto:ProjectX Alberto$ git rebase Defect123_Changes
First, rewinding head to replay your work on top of it...
Fast-forwarded master to Defect123_Changes.

picture-25

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

Abraços!

Juntando vários commits no Git

Posted by Alberto Leal on May 21st, 2009

Foi no ano passado que comecei a utilizar o GIT como VCS padrão. Naquela ocasião, não tinha  muitos problemas de conflitos, já que o “time” era pequeno - só eu =)

Hoje, faço parte de um time de 8 desenvolvedores. Já dá para imaginar que problemas na hora de fazer o rebase/merge já começam a aparecer com uma certa frequência. É gente comitando o dia inteiro!

Quando alguém no time cria um branch remoto, e trabalha nele o dia inteiro, por exemplo, ao final do dia é possível existir uma quantidade de commits consideráveis. Mas, na hora de enviar essas alterações para o repositório - git push - pode não ser necessário enviar todo aquele histórico de commits, apenas o resultado final interessa. Mas, dá para fazer isso? A resposta é: Sim, dá!

Mas atenção, somente faça isso em commits que ainda não foram enviados para o repositório externo. Pois se isso já tiver acontecido, outro desenvolvedor pode ter feito outras alterações e os conflitos aparecerão para lhe trazer dor de cabeça.

A idéia é utilizar git rebase –interactive, ou simplesmente git rebase -i.

Imagine o seguinte cenário: Você precisa trabalhar em um defect, e para isso você cria um branch para trabalhar nele. Ao longo do seu trabalho, você vai fazendo vários commits para manter o status do seu trabalho, já que de vez enquando você precisa pular para outro branch para resolver outra coisa, ou simplesmente testar alguma funcionalidade em outro branch que seu chefe lhe pediu (E, dependendo da situação, utilizar git stash não é a solução). Na hora de fazer o push, você só precisa enviar o defect resolvido. Se você desejar juntar todos os commits em um único commit que represente aquele seu defect, siga os passos abaixo:

picture-8

Veja que temos 4 commits em nosso repositório. E desejamos unir os três últimos commits. Para isso, vamos utilizar o comando que foi citado anteriormente:

picture-14

git rebase -i master~3

O comando acima diz para fazer o rebase interativo de 3 commits a partir do master. Feito isso, será aberto um editor para que você faça as devidas alterações:

picture-16

Marque os últimos commits que você deseja juntar com o primeiro com a palavra squash. Feito isso, salve o documento e um novo editor será aberto para que você informe a mensagem para esse novo commit.  No exemplo abaixo, eu deixei as mensagens de todos os commits, mas é permitido que você informe a mensagem que desejar.

picture-18Observe que, agora temos apenas 2 commits, ao invés de 4:

picture-191

Bastante útil. Mas, use apenas se você realmente não precisar do histórico desses commits.

UPDATE 1: Execute git rebase –continue para consolidar as alterações.

Alterando a mensagem de um commit no GIT

Posted by Alberto Leal on May 20th, 2009

Se você, assim como eu, já precisou alterar a mensagem de um commit no git, aí vai uma dica:

Antes, uma observação: se você já fez o push do commit que você deseja alterar a mensagem, não faça nenhuma alteração! Isso pode causar uma série de conflitos com os demais abaixo do seu commit.

Alterando a mensagem

Primeiro, é necessário escolher um commit abaixo daquele que será alterado. Para isso, dê um git log para listar os commits:

git log

Feito isso, escolha o commit, o qual a partir dele os commits acima poderão ser alterados e dê um git rebase -i <commit>:

picture-7git rebase -i 24eb338

Com o editor aberto, marque com a palavra EDIT os commits que você deseja alterar. Salve o arquivo e feche o editor:

picture-10

Agora, para abrir o editor e alterar os commits que você marcou como EDIT, execute git commit –amend:

picture-11

picture-13

Altere a mensagem do commit e está pronto!

Observe que o commit agora tem outro hash - dê git log para visualizar a alteração-, pois, essa tarefa de alterar a mensagem de um commit faz com que o GIT gere um novo commit.

Falando em Java 2009: eu também vou!

Posted by Alberto Leal on May 8th, 2009

logo_fj2009Acontecerá no próximo dia 24 o Falando em Java 2009. O evento já está em sua terceira edição. Infelizmente, não pude participar dos eventos anteriores. Mas, neste ano, estarei presente.

O evento contará com boas palestras, e promete ser bem interessante. Além de que, será uma ótima oportunidade de conhecer, pessoalmente, uma galera que já venho conversando pela internet - twitter, gtalk, msn e fóruns.

Então, nos vemos lá!

Dica: CouchDB no Mac OS X

Posted by Alberto Leal on December 11th, 2008

Para aqueles que estiverem tentando instalar o CouchDB no Mac OS X, aí vai uma dica.

Eu instalei utilizando o MacPorts:

sudo port install couchdb

Após a instalação, execute “sudo couchdb” no seu terminal para iniciar o serviço. No meu caso, a saída foi a seguinte:

Apache CouchDB 0.8.1-incubating (LogLevel=info)

Apache CouchDB is starting.

Config Info /opt/local/etc/couchdb/couch.ini:

CurrentWorkingDir=/Users/Alberto

DbRootDir=/opt/local/var/lib/couchdb

BindAddress=”127.0.0.1″

Port=”5984″

DocumentRoot=/opt/local/share/couchdb/www

LogFile=/opt/local/var/log/couchdb/couch.log

UtilDriverDir=/opt/local/lib/couchdb/erlang/lib/couch-0.8.1-incubating/priv/lib

DbUpdateNotificationProcesses=

FullTextSearchQueryServer=

javascript=/opt/local/bin/couchjs /opt/local/share/couchdb/server/main.js

Se você tentar acessar a pasta de log e  couchdb, verá que elas não foram criadas. Com isso, o serviço não é inicializado corretamente. Crie as pastas (sudo mkdir -p /opt/local/var/log/couchdb/ e sudo mkdir -p /opt/local/bin/couchdb ) e tente iniciar o serviço novamente.

UPDATE 1: Recebi uma dica do Marcos Tapajós falando para não instalar o CouchDB usando o ports, já que ele instala uma versão ultrapassada e cheia de bugs. Se quiser instalar, pegue direto do trunk do projeto.

UPDATE 2: Para desinstalar o couch instalado com o MacPorts, execute no seu terminal:

sudo port uninstall couchdb @0.8.1_0

Feito isso, agora é hora de pegar e compilar o projeto que está no trunk:

$ svn co http://svn.apache.org/repos/asf/incubator/couchdb/trunk couchdb
$ cd ~
$ cd couchdb
$ ./bootstrap
$ ./configure
$ make
$ sudo make install

E, isso é tudo! Execute “couchdb -V” e veja se tudo saiu como o esperado. Se você notar, verá que realmente a versão do trunk é mais nova!Para aqueles que estiverem tentando instalar o CouchDB no Mac OS X, aí vai uma dica.

Eu instalei utilizando o MacPorts:

sudo port install couchdb

Após a instalação, execute “sudo couchdb” no seu terminal para iniciar o serviço. No meu caso, a saída foi a seguinte:

Apache CouchDB 0.8.1-incubating (LogLevel=info)

Apache CouchDB is starting.

Config Info /opt/local/etc/couchdb/couch.ini:

CurrentWorkingDir=/Users/Alberto

DbRootDir=/opt/local/var/lib/couchdb

BindAddress=”127.0.0.1″

Port=”5984″

DocumentRoot=/opt/local/share/couchdb/www

LogFile=/opt/local/var/log/couchdb/couch.log

UtilDriverDir=/opt/local/lib/couchdb/erlang/lib/couch-0.8.1-incubating/priv/lib

DbUpdateNotificationProcesses=

FullTextSearchQueryServer=

javascript=/opt/local/bin/couchjs /opt/local/share/couchdb/server/main.js

Se você tentar acessar a pasta de log e  couchdb, verá que elas não foram criadas. Com isso, o serviço não é inicializado corretamente. Crie as pastas (sudo mkdir -p /opt/local/var/log/couchdb/ e sudo mkdir -p /opt/local/bin/couchdb ) e tente iniciar o serviço novamente.

UPDATE 1: Recebi uma dica do Marcos Tapajós falando para não instalar o CouchDB usando o ports, já que ele instala uma versão ultrapassada e cheia de bugs. Se quiser instalar, pegue direto do trunk do projeto.

UPDATE 2: Para desinstalar o couch instalado com o MacPorts, execute no seu terminal:

sudo port uninstall couchdb @0.8.1_0

Feito isso, agora é hora de pegar e compilar o projeto que está no trunk:

$ svn co http://svn.apache.org/repos/asf/incubator/couchdb/trunk couchdb
$ cd ~
$ cd couchdb
$ ./bootstrap
$ ./configure
$ make
$ sudo make install

E, isso é tudo! Execute “couchdb -V” e veja se tudo saiu como o esperado. Se você notar, verá que realmente a versão do trunk é mais nova!

Prazer, Ruby

Posted by Alberto Leal on December 8th, 2008

Pessoal,

nos últimos meses intensifiquei os meus estudos em Ruby e Rails. Como podem perceber, meus últimos posts foram emcima dessas tecnologias. E, ao longo da última semana, resolvi pegar alguns dos meus resumos e dar uns “tapas” na formatação e no conteúdo, tentando transformá-los em uma espécie de apostila para aqueles que estão começando com a linguagem e preferem material em português.

Gostaria de convidar a comunidade para contribuir com o trabalho. O projeto está no Github,  portanto, aqueles que quiserem contribuir, basta acessar o link do projeto no Github

 

Abraços

Trabalhando com Ranges

Posted by Alberto Leal on December 8th, 2008

É bastante comum trabalharmos com números sequênciais. E, para isso, não é necessário criarmos uma estrutura de iteração para incrementar um contador, por exemplo.

A melhor forma de se ter número sequênciais, em ruby, é utilizar os ranges

 

Criando um range

Exitem duas maneiras de se criar um range: a primeira é utilizando 2(dois) pontos (..) e a segunda utilizando 3(três) pontos (…) . A diferença entre eles é que quando utilizamos “..” estamos incluindo o último número informado, já com “…” estamos excluindo o último número informado.

numeros = 1..10
numeros2 = 1...10

 

Iterando um range

(1..10).each{|num| puts num}

(1.0..5.0).each{|num| puts num}#error

 

Embora seja permitido se criar um range de Float, não é permitido iterá-lo, visto que a classe Float não possui um método succ

Convertendo para Array

num_array = numeros.to_a #[1,2,3,4,5,6,7,8,9,10]
num2_array = numeros2.to_a #[1,2,3,4,5,6,7,8,9]

Conversão implícita e explícita de Numéricos

Posted by Alberto Leal on December 4th, 2008

Falei sobre conversão implícita/explícita quando estamos trabalhando com String. Agora, chegou a vez de falar um pouco sobre conversões com classes numéricas.

A mesma confusão que encontramos quando falamos sobre os métodos to_s e to_str, encontramos quando nos deparamos com to_i, to_int, to_f, to_flt. Vamos valer da mesma regra que foi apresentada para a classe String: Conversão explícita é para os métodos curtos e implícita para os métodos longos.

Assim como o método to_str, muitas classes não definem os métodos implícitos to_int e to_flt.

Vamos ver um exemplo:

class MeuNumero
  def to_i
    5
  end

  def to_int
    10
  end
end

meu_numero = MeuNumero.new
puts meu_numero.to_i

 

O código acima imprime “5″, visto que chamamos o método explicitamente. Agora, quando o meu método de conversão implícita é executado? Vejamos outro exemplo:

meu_numero = MeuNumero.new
array_meu_numero = Array.new(meu_numero)

 
Ao executarmos o código acima, constatamos que foi criado um array com 10 posições. Isso porque quando passamos o nosso objeto (meu_numero) ele foi implicitamente convertido para um valor numérico para ser passado como parâmetro para a criação de um Array.


Copyright © 2007 Alberto Leal. All rights reserved.