Desenvolvimento

27 jul, 2012

Dica Git da Semana: adição interativa

Publicidade

A dica desta semana é sobre a adição interativa. Até agora, sempre que você adicionou um arquivo com git add, ele tomou todo o arquivo e o acrescentou no índice.

No entanto, é possível adicionar apenas partes de um arquivo para o índice; na terminologia Git, são chamados de hunks. Um hunk é apenas um conjunto de alterações em um arquivo, envolvendo linhas adicionadas (aquelas com prefixo +) e as linhas removidas (aquelas prefixadas com -). Essa é uma forma textual de mostrar as diferenças, quando se trata de pacotes de arquivos (sobre os quais já falamos anteriormente), ele usa esses hunks para armazenar várias versões do mesmo conteúdo – ao mesmo tempo – usando menos espaço.

Há um menu interativo que pode ser criado com git add –interactive, ou git add-i. No entanto, a maioria das opções aqui não é muito útil, o que você achará com mais frequência é o comando [p]atch. Há um caminho mais curto para resolver isso, com o comando git add –patch, ou apenas git add-p.

Para que serve? Bem, ele permite que você escolha de forma seletiva quais diffs serão adicionados ao índice. Embora possa não parecer muito útil, ele permite que você divida as alterações de um arquivo em várias pequenas mudanças, desde que você faça o commit deles toda vez. Vejamos um exemplo com uma classe Person, a qual estamos adicionando um nome e sobrenome:

(master) $ git show Person.java | tail -4
@@ -0,0 +1,3 @@
+public class Person {
+
+}
(master) $ git diff
@@ -1,2 +1,17 @@
public class Person {
+ private String firstName;
+ public String getFirstName() {
+ return firstName;
+ }
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }

+ private String lastName;
+ public String getLastName() {
+ return lastName;
+ }
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
}

Normalmente, se fizermos um git add, ele vai colocar a mudança tanto para o firstName, quanto para o lastName dentro do índice. E se quisermos separá-los? Bem, poderíamos apenas ter uma cópia do arquivo; editar uma mudança, adicionar, copiar o arquivo de volta, e depois adicionar a segunda mudança. Mas podemos usar Git para nos ajudar, com o git add-p:

@@ -1,2 +1,17 @@
public class Person {
+ private String firstName;
+ public String getFirstName() {
+ return firstName;
+ }
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }

+ private String lastName;
+ public String getLastName() {
+ return lastName;
+ }
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
}
Stage this hunk [y,n,q,a,d,/,e,?]?

O ‘stage this hunk’ está perguntando o que nós queremos fazer. Se quiséssemos adicioná-lo, diríamos ‘y’ ou ‘a’. Se não o fizéssemos, poderíamos dizer ‘n’ ou ‘d’. (O primeiro é “apenas um presente” enquanto o último é “todo o resto “).

E se a gente quiser adicionar aos poucos? Bem, há um comando [s]plit que divide este hunk em outros menores:

Stage this hunk [y,n,q,a,d,/,e,?]? s
Split into 2 hunks.
@@ -1,2 +1,9 @@
public class Person {
+ private String firstName;
+ public String getFirstName() {
+ return firstName;
+ }
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]? y

@@ -2,2 +9,9 @@

+ private String lastName;
+ public String getLastName() {
+ return lastName;
+ }
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
}
Stage this hunk [y,n,q,a,d,/,K,g,e,?]? n

O que nós fizemos foi adicionar as primeiras sete linhas da mudança no índice git, deixando de fora o último sete. Se fizermos um git diff, vamos ver a diferença:

(master) $ git diff
diff --git a/Person.java b/Person.java
index a6d00fd..23dd325 100644
--- a/Person.java
+++ b/Person.java
@@ -7,4 +7,11 @@ public class Person {
this.firstName = firstName;
}

+ private String lastName;
+ public String getLastName() {
+ return lastName;
+ }
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
}

Podemos ver nos tatus do Git que temos uma mudança de staged e uma mudança de unstaged:

(master) $ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD ..." to unstage)
#
# modified: Person.java
#
# Changes not staged for commit:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# modified: Person.java
#

Normalmente, você ficaria preocupado se visse isso – como se tivesse se esquecido de acrescentar algo. No entanto, é exatamente o efeito desejado. Neste caso, nós adicionamos parte da nossa mudança e temos parte da mudança ainda para continuar. A partir daqui, podemos fazer um commit, como de costume:

(master) $ git commit -m "Added firstname"
[master 2e0d5f8] Added firstname
1 files changed, 7 insertions(+), 0 deletions(-)
(master) $ git add Person.java
(master) $ git commit -m "Added lastname"
[master 1d09387] Added lastname
1 files changed, 7 insertions(+), 0 deletions(-)

Se olharmos para o arquivo, podemos ver que sempre fizemos o commit das alterações como commits separados:

(master) $ git blame Person.java | cut -c 1-9,50-80
391f64ef 1) public class Person {
2e0d5f8b 2) private String firstName;
2e0d5f8b 3) public String getFirstNam
2e0d5f8b 4) return firstName;
2e0d5f8b 5) }
2e0d5f8b 6) public void setFirstName(
2e0d5f8b 7) this.firstName = firstN
2e0d5f8b 8) }
391f64ef 9)
1d093875 10) private String lastName;
1d093875 11) public String getLastName
1d093875 12) return lastName;
1d093875 13) }
1d093875 14) public void setLastName(S
1d093875 15) this.lastName = lastNam
1d093875 16) }
391f64ef 17) }

Ser capaz de fazer alterações em partes, ao invés de em sua totalidade, é uma técnica útil quando você tem subconjuntos de alterações que foram feitas em um arquivo sem commits entre eles.

***

Artigo original disponível em: http://alblue.bandlem.com/2011/10/git-tip-of-week-interactive-adding.html