Screencast: Como criar um branch vazio no Git

Posted by Alberto Leal on March 22nd, 2010

Olá Pessoal,

Neste último final de semana eu gravei um screencast de como criar um branch vazio no Git.

Espero que vocês gostem:

Creating an empty branch on Git from Alberto Leal on Vimeo.

Abraços.

Git: Localizando a origem de um bug através de busca binária entre os commits

Posted by Alberto Leal on January 5th, 2010

No artigo de hoje, vamos abordar uma funcionalidade muito interessante do Git, a qual nos permite fazer uma busca binária no projeto procurando o commit exato onde o bug foi introduzido no projeto.

Cenário: Você não é o único desenvolvedor que trabalha no projeto. Enquanto você trabalha em um branch, outras pessoas trabalham em outros branches, e no final todos fazem o merge no branch master. O merge é feito várias vezes por semana. Em um belo dia, uma funcionalidade que antes funcionava perfeitamente para de funcionar, mas ninguém sabe dizer quem foi o responsável por quebrar aquela parte da aplicação, e como não existia uma suíte de testes cobrindo tal funcionalidade, o bug foi commitado.

Problema: Onde o erro foi commitado?
Solução: Utilizar o comando git bisect.

O projeto possui os seguintes commits:

commit 5c370fe0c059566a88e41699bfb7fea0abf4da0c
Author: Alberto Leal <albertonb@gmail.com>
Date:   Sat Dec 12 11:05:04 2009 -0200
 
    CBR 98575 - Cert verification
 
commit bd9e83fe1def11a38436d159b1ce721920fb9565
Author: Alberto Leal <albertonb@gmail.com>
Date:   Sat Dec 12 11:04:25 2009 -0200
 
    CBR 26345 - Regression
 
commit 61de4d21518855196426986ee0798c1e69f05ae0
Author: Alberto Leal <albertonb@gmail.com>
Date:   Sat Dec 12 11:03:37 2009 -0200
 
    bug 123 fixed
 
commit 32c8ce277a7db30aea67f7397a06596b1fc8bfdf
Author: Alberto Leal <albertonb@gmail.com>
Date:   Sat Dec 12 11:02:58 2009 -0200
 
    sessionTimeout features added
 
commit 1973d569ac052697c6e35e7e26b4e7b03ba09616
Author: Alberto Leal <albertonb@gmail.com>
Date:   Sat Dec 12 11:01:58 2009 -0200
 
    slogan on the header
 
commit 294d5cac2d9b400b7d244d71c6db5849f61fad6a
Author: Alberto Leal <albertonb@gmail.com>
Date:   Sat Dec 12 11:01:01 2009 -0200
 
    initial commit

**Para efeito de didática, já sabemos que o bug está no commit:
bd9e83fe1def11a38436d159b1ce721920fb9565

Antes de iniciar a busca binária, deve-se saber pelo menos um commit onde não existia o bug. Neste caso, sabemos que até o segundo commit tudo funcionava perfeitamente. Mas, para ter certeza disso, execute o comando abaixo, rode a aplicação e teste:

git checkout 1973d569ac052697c6e35e7e26b4e7b03ba09616

Observação: Estamos utilizando poucos commits, mas no “mundo real” os commits são muitos.

Após constatar que não existe o bug no segundo commit, volte para o último commit da mesma maneira:

git checkout 5c370fe0c059566a88e41699bfb7fea0abf4da0c

Agora chegou a vez de utilizar o comando git bisect. A idéia é marcar os commits como bad(ruim) e good(bom), e deixar que o Git faça uma busca binária entre os commits.

Alberto:repo Alberto$ git bisect start
Alberto:repo Alberto$ git bisect bad
Alberto:repo Alberto$ git bisect good 1973d569ac052697c6e35e7e26b4e7b03ba09616
Bisecting: 1 revisions left to test after this (roughly 1 steps)
[61de4d21518855196426986ee0798c1e69f05ae0] bug 123 fixed

Antes de mais nada, é necessário informar ao Git que vamos iniciar uma busca binária, para isso execute git bisect start. Repare que após iniciar o bisect, marcamos o último commit como bad e o segundo commit como good.

Em seguida o Git fez uma busca binária e encontrou o commit “bug 123 fixed”. Neste momento você deve executar a sua aplicação para ver se o bug está lá.

Não, o bug não está lá, portanto, marcamos o commit como good:

Alberto:repo Alberto$ git bisect good
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[bd9e83fe1def11a38436d159b1ce721920fb9565] CBR 26345 - Regression

Novamente, o Git faz uma busca binária e encontra mais um commit para ser analisado. Após rodar a aplicação, constata-se que o erro está no commit atual que o bisect encontrou “CBR 26345 - Regression”:

Alberto:repo Alberto$ git bisect bad
bd9e83fe1def11a38436d159b1ce721920fb9565 is first bad commit
commit bd9e83fe1def11a38436d159b1ce721920fb9565
Author: Alberto Leal <albertonb@gmail.com>
Date:   Sat Dec 12 11:04:25 2009 -0200
 
    CBR 26345 - Regression
 
:100644 100644 9309150acfad8f21cb460ee17663fcbeaf212aa0 172b5ae1734b7bd1e1ac46b5cade3e61cbfddff1 M	imasters.txt

Pronto! Agora já sabemos em qual commit o bug foi introduzido. E, para finalizar, da mesma forma que informamos ao Git que desejávamos fazer uma busca binária, devemos informá-lo que já terminamos, executando o comando abaixo:

Alberto:repo Alberto$ git bisect reset
Previous HEAD position was bd9e83f... CBR 26345 - Regression
Switched to branch 'master'

Muita gente acha que quando se está trabalhando sozinho em um projeto de software não é necessário utilizar um sistema de controle de versões. O que é um falácia. Utilizar um SCM é indispensável e indiferente à quantidade de desenvolvedores que está trabalhando no projeto.

Git: Recuperando os nomes dos arquivos modificados

Posted by Alberto Leal on June 4th, 2009

Cenário: Seu cliente utiliza um outro SCM, por exemplo, o Harvest, no projeto o qual você trabalha. Mas, você e sua equipe resolvem utilizar o Git. Eis que você faz uma série de scripts de automação para pegar o seu release no Git e entregá-lo ao Harvest. Bacana, não? Para que isso funcione perfeitamente, você deve passar para o sistema de automação o nome dos arquivos que foram alterados, desse jeito, não será necessário enviar arquivos que sequer sofreram modificações, o que é bem interessante, diga-se de passagem.

Legal, existe o comando git whatchanged. Você pode passa como parâmetro o total de commits que você deseja analisar, utilizando a opção -n. Se você omití-lo, todas as modificações serão exibidas. (Se você informar o parâmetro -p, serão exibidas as modificações dentro dos arquivos).

Vamos dar uma olhada na saída desse comando:

$ git whatchanged -n 3
commit 8d726556aec75e1d6ef5ae477532fcc11770ac01
Author: Alberto Leal
Date:   Wed Jun 3 16:04:23 2009 -0400
 
Message 3
 
:100755 000000 0615380... 0000000... D  workspace/xxx/yyy/cfgXPTO.jnlp
:100755 000000 37076cc... 0000000... D  workspace/xxx/yyy/packageXYZ.jar
 
commit 3ab3a5e95fa031d8eb93154d161bfba228bc5f86
Author: Alberto Leal
Date:   Wed Jun 3 15:38:00 2009 -0400
 
Message 2
 
:100755 100755 849dc69... 1e7ef9d... M  workspace/xxx/src/com/xxx/yyy/ModelXYZ.java
:100755 100755 9828364... d85e5ee... M  workspace/xxx/WebContent/WEB-INF/jsp/viewXYZ.jsp
:000000 100755 0000000... d034c28... A  workspace/xxx/WebContent/htmlXPTO.html
 
commit 7e076b5130340f28e57526cf71ce96d7cc279538
Author: Alberto Leal
Date:   Wed Jun 3 10:04:56 2009 -0400
 
Message 1
 
:100755 100755 9c322de... 9828364... M  workspace/xxx/WebContent/WEB-INF/jsp/anotherXTPO.jsp

Repare que se você quiser pegar os arquivos modificados, você terá que copiar um a um. Trabalhoso, não? Existem diversas maneiras de se recuperar apenas os nomes dos arquivos. Uma sugestão seria:

def files_changed(commits, last_commit)
  File.open(commits, "r") do |infile|
  infile.each_line do |line|
    if line.include? last_commit
      break
    else
      er = line.match(/workspace(.*)/)
      puts er unless er.nil?
    end
  end
  end
end
 
files_changed(ARGV[0], ARGV[1])

Execute o seguinte comando no seu terminal para obter o resultado desejado:

$ git whatchanged > files.txt && ruby files_changed.rb files.txt 7e076b513

Se você quiser, simplesmente, pegar todos os arquivos modificado, você pode executar o comando abaixo:

$ git whatchanged | grep workspace | awk '{print($6)}'

Fica aí mais uma dica. Afinal, seria bem chato ter que copiar arquivo por arquivo =)

TortoiseGit: Você já conhece?

Posted by Alberto Leal on June 4th, 2009

Olá Pessoal,

Quando você utilizava o SVN ou CVS, provavelmente, você já utilizou ou ouviu falar do Tortoise. Agora, você já pode utilizá-lo junto com o Git.


O projeto está no Google, acesse: http://code.google.com/p/tortoisegit/
Vejo muitas pessoas reclamando de utilizar Git pelo fato de não existir algo visual, por não ser user friendly. Para essas pessoas, agora existe o projeto TortoiseGit.
Se você, assim como eu, gosta de terminal, linhas de comando, esse projeto não será tão útil.
Fica aí a dica para aqueles que não curtem terminal e linhas de comando.

Git: Total de commits de cada commiter no projeto

Posted by Alberto Leal on June 3rd, 2009

Sua equipe utiliza Git na empresa? Você quer ver quem anda comitando mais?

Algumas pessoas me perguntaram como fazer para obter tal resultado. Há algum tempo eu twittei sobre isso.

Para aqueles que acompanham o meu blog, mas não me acompanham no twitter, aí vai a dica. A maneira de ver o total de commits de cada commiter em um projeto que utiliza o Git, como controlador de versão, é:

1
git shortlog -s -n --all

(O tweet está aqui.)

Ok. Mas agora você quer ver o total de commits apenas nos repositórios remotos? O comando é:

1
git rev-list --remotes --pretty=short

(O tweet está aqui.)

GIT: Visualizando as modificações nos commits

Posted by Alberto Leal on June 2nd, 2009

Cenário: Você trabalhou durante dias em um branch. Fez vários commits nele. E…., após algumas discussões, ficou decidido que algumas das suas modificações não entrarão na próxima build. A idéia das mensagens ao se fazer um commit é ajudar na identificação daquele commit específico.  Você pode analisar estas mensagens e remover alguns commits com base em tais mensagens. Mas, às vezes, só isso não basta para você. Você quer analisar antes alguns commits para ter certeza de que somente as alterações desejáveis, sejam adicionadas na build. Comando: git whatchanged Ps: As mensagens foram alteradas propositalmente. Utilize mensagens significativas em seus commits.

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
34
35
36
37
38
39
40
41
commit d9a472455d9f91c75465af9778f5271baf532813
Author: Alberto Alvine &lt;albertonb@gmail.com&gt;
Date:   Thu May 28 14:14:39 2009 -0400
 
    Message 7
 
commit 6251f4b532d0c602e41742ab6399b637b14596c8
Author: Alberto Alvine &lt;albertonb@gmail.com&gt;
Date:   Wed May 27 15:51:56 2009 -0400
 
    Message 6
 
commit e71160c9fc1f676e94c4fe732f169e153731bf4c
Author: Alberto Alvine &lt;albertonb@gmail.com&gt;
Date:   Tue May 26 13:18:03 2009 -0400
 
    Message 5
 
commit e91d5a2171f8a4edcb60004967bad847339046d8
Author: Alberto Alvine &lt;albertonb@gmail.com&gt;
Date:   Thu May 21 15:31:49 2009 -0400
 
    Message 4
 
commit b514e967f01f579faab27f84017897d017810038
Author: Alberto Alvine &lt;albertonb@gmail.com&gt;
Date:   Thu May 21 10:23:50 2009 -0400
 
    Message 3
 
commit c59d02c18bfeff297f91fc2f06247e82ff77b741
Author: Alberto Alvine &lt;albertonb@gmail.com&gt;
Date:   Tue May 19 09:18:37 2009 -0400
 
    Message 2
 
commit 48b5d388ad78dbc260a3afd164c6b46cca7b9e51
Author: Alberto Alvine &lt;albertonb@gmail.com&gt;
Date:   Wed May 13 09:46:28 2009 -0400
 
    Message 1

Para visualizar quais foram as alterações em todos commits, execute git whatchanged -p. Esse comando mostrará os diffs de todos os arquivos que foram alterados. Se for desejável apenas visualizar os arquivos que sofreram alterações, execute apenas git whatchanged, sem parâmetro. Este comando foi útil para mim. Fica aí mais uma dica =) UPDATE 1: Esqueci de mencionar: Na hora de executar o git revert, você deve executá-lo de forma inversa, isto é,  vamos imaginar que você deseja desfazer os 2 últimos commits, o primeiro revert vai ser para o último commit, e o segundo revert para o penúltimo commit.

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.


Copyright © 2007 Alberto Leal. All rights reserved.