Desenvolvimento

17 mai, 2012

Dica Git da semana: Rebasing

Publicidade

A dica Git desta semana é sobre reescrever o histórico.

Reescrevendo o histórico

Uma das diferenças filosóficas entre Git e Mercurial é se o histórico deve ser autorizado a ser reescrito ou não. Quando um commit é feito, o hash de commit representa um ponto nesse histórico – e os commits subsequentes então confiam naquele hash para a integridade e a representação dos links parentais.

Reescrever o histórico pode ser perigoso; se você alterar um commit no passado, isso invalida o hash de commit atual. Em vez disso, você precisa recomitar a alteração em curso contra o novo commit para obter um novo valor de hash.

No entanto, perigoso é relativo. Nem sempre é o caso de mudanças no histórico serem um mau negócio – se aquele histórico for local, e não tiver sido visto por mais ninguém, então a única pessoa que ele afeta é você. Contanto que você saiba o que está fazendo (e quem você afetará), então alterar o histórico local não é diferente de anular as suas alterações locais em um editor de texto e salvar novamente.

(Nota: o Mercurial tem o conceito de ‘patch queues’, que são o equivalente do histórico mutável local – mas você acaba com dois conceitos de repositórios separados, em vez de um único conceito do histórico como no Git.)

Então, a questão não é tanto como se o histórico reescrito fosse perigoso, como a compreensão dos efeitos caso essas mudanças estejam expostas a outros (por exemplo, via pushing para o GitHub). Às vezes, é necessário interromper publicamente os hashes de commit – por exemplo, alguém acidentalmente fez o commit de um grande binário, ou copiou código protegido por de direitos autorais que não deveria estar presente (ou mesmo uma senha que não tenha sido comitada) – mas, nesses casos, fazer a mudança geralmente envolve uma notificação pública para avisar os outros.

Rebasing

Então, o que é rebasing? Bem, rebasing é o conceito do Git de mudar (recente) o histórico local. Em essência, é uma opção de desfazer/repetir que você pode usar para fazer alterações como se tivessem sido feitas no passado.

O rebase Git libera o histórico para um determinado ponto (tipicamente especificado como HEAD~n, onde n é o número pequeno de commits prévios no passado) e, em seguida, repete as mesmas alterações no topo do código. Se as mudanças estão inalteradas, então o commit resultante será o mesmo de antes.

No entanto, é mais normal querer ajustar o (s) commit (s) de alguma forma, por exemplo:

  • Reword – alterar a mensagem de commit para outra coisa (por exemplo, para adicionar uma referência de bug)
  • Edit – para fazer alterações no commit (por exemplo, corrigir um erro de digitação no código)
  • Pick – para incluir aquele commit no histórico
  • Squash – para condensar aquele commit com o anterior e torná-los um (e entrada de log concatenar)
  • Fixup – para condensar aquele commit com o anterior e torná-los um (e descartar entrada de log)

Assim como essas opções, também é possível reordená-las simplesmente reordenando a lista de mudanças.

Exemplo

Vamos construir um repositório com algumas mudanças que gostaríamos de fazer:

$ git init example
Initialized empty Git repository in example/.git
$ cd example
$ git commit --allow-empty -m "Initial Commit"
$ echo Helo World > README.txt
$ git add README.txt
$ git commit -m "Typo" README.txt
$ echo Second > Second.txt
$ git add Second.txt
$ git commit -m "Second" Second.txt
$ echo Frst > First.txt
$ git add First.txt
$ git commit -m "First" First.txt
$ echo First > First.txt
$ git add First.txt
$ git commit -m "First" First.txt
$ git log
07e9061 First
756281e First
13aba60 Second
7b49271 Typo
82f9a21 Initial Commit

O que nós gostaríamos de fazer é corrigir o erro de digitação feito no primeiro commit, junte os dois primeiros commits em um, e reordene o segundo (Second) para que ele venha em segundo lugar na lista. Para fazer isso, dê início a um rebase interativo, o que nos dará um editor:

$ git rebase -i 82f9a21
@ pick 7bf9271 Typo
@ pick 13aba60 Second
@ pick 756281e First
@ pick 07e9061 First

O que isso está dizendo é uma sequência de pontos-chave para reproduzir o histórico com as mudanças específicas listadas. Podemos voltar a requisitá-los para reproduzir o histórico de uma maneira diferente:

@ edit 7bf9271 Typo
@ pick 756281e First
@ fixup 07e9061 First
@ pick 13aba60 Second

O Git, então, retrocede o histórico para o pai do commit Typo, e nos envia para dentro um shell, que nos permite fazer alterações:

$ echo Hello World > README.txt
$ git add README.txt
$ git commit -m "Readme" README.txt
$ git rebase --continue

Aqui, nós paramos a edição por um tempo e continuamos através da operação de rebase. Nós poderíamos inserir mais commits se quiséssemos, mas acabamos fazendo um commit do estado atual como está. Você também pode ver:

error: could not apply 07e9061... First
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' and run 'git rebase --continue'
Could not apply 07e9061... First

Isso é causado porque estamos mudando a mesma linha no mesmo arquivo, e o Git está perguntando se está tudo OK. Podemos simplesmente adicionar esse arquivo e continuar, ou (dado que 07e9061 é um substituto completo para 756281e) simplesmente não tê-lo feito em primeiro lugar:

$ git rebase --abort
$ git rebase -i 82f9a21
...
@ edit 7bf9271 Typo
@ pick 07e9061 First
@ pick 13aba60 Second

Ao remover o commit da lista, é como se aquele commit nunca tivesse acontecido. Ele deve ser executado e permitir que você faça um commit de todas as mudanças sem ter quaisquer conflitos.

Resumo

Rebasing permite que você reescreva o histórico de maneira automatizada, em vez de ter que relaxar e repetir as mudanças manualmente. É muito utilizado com git rebase -i HEAD~5 (ou algum outro número pequeno) para corrigir alterações no seu histórico local antes de fazer merge ou push para um repositório central.

Rebasing também permite a transplantação de seções inteiras de uma tree, sobre as quais falaremos outra hora.

Finalmente, lembre-se de que Git nunca perde os dados do commit. Se você está trabalhando contra um branch, você tem reflogs do branch para recorrer; o que Git permite que você reconstrua facilmente essas novas commit trees (embora mantendo as velhas ao redor em seu cache local) até que você esteja feliz com o resultado.

?

Texto original disponível em http://alblue.bandlem.com/2011/06/git-tip-of-week-rebasing.html