Saltar para o conteúdo

Git Avançado

Fonte: Wikiversidade

Nesta página você encontrará definições de alguns comandos avançados sobre a ferramenta de controle de versão git. Inicialmente você encontrará uma explicação com exemplos dos comandos rebase, cherry-pick e stash. Caso deseje ler antes sobre os comandos básicos do git, pode-se visitar a seguinte página: git básico

O comando rebase pode ser descrito como um forma de atualizar o conteúdo de um branch por meio de outro branch, ou seja, é uma forma de pegar as mudanças que foram commitadas em um branch e aplicar tais mudanças a outros branch. Apesar do comando merge também permitir tal funcionalide, o rebase faz isso de forma diferente e para fazer um rebase, é necessário usar o seguinte comando:

git rebase <base>

Onde base pode ser desde qualquer tipo de referência a um commit, como o nome de um branch ou até mesmo o próprio id de um commit.

Para melhor visualizar como funciona o rebase, o exemplo abaixo mostra os passos básicos para se fazer um rebase.

#O usuário cria uma nova branch
git checkout -b nova_branch

#O usuário cria alguns novos arquivos e os edita.
git commit -m "Criando novos arquivos"

#Entretanto, algum outro contribuidor do projeto também fez uma alteração na branch principal do projeto, aqui chamada de master.
git rebase master

#Por fim, o usuário pode assim voltar a branch principal do projeto e fazer merge chamado de limpo
git checkout master
git merge nova_branch

Conforme foi visto no exemplo, o rebase está sendo usado para puxar os novos commits da branch master para a branch nova_branch. Quando se usa o rebase, os commits da branch passada como base são colocados abaixo dos commits realizados pelo usuário na branch atual. Dessa forma, usando o rebase, têm-se a sensação de que o histórico de commits é linear.

Vale ressaltar que ao usar o rebase, conflitos podem ser gerados. Por exemplo, caso um usuário tenha editado um arquivo e commitado o arquivo na branch master, e na branch atual, este mesmo arquivo foi modificado na mesma linha que o arquivo editado na branch master, o comando rebase irá gerar um conflito, pois o git não saberá como manter ambas as alterações. Quando um conflito acontece no rebase, o rebase irá ser pausado e o usuário irá ser pedido para solucionar o conflito. Para solucionar o conflito, o usuário deve abrir o arquivo que sofreu o conflito e editar as linhas que mostrão o conflito. Após fazer tal alteração, o usuário deve adicionar tal arquivo a lista de arquivos acompanhados via o comando add e usar o seguinte comando para continuar com o rebase:

git rebase --continue

Caso o usuário queira pular ou ignorar tal conflito, ou seja, não trazer o commit que gera o conflito para a nova branch, o mesmo pode usar o seguinte comando:

git rebase --skip

Por fim, caso o usuário queira abortar o rebase, o seguinte comando pode ser usado:

git rebase --abort


Rebase interativo

[editar | editar código-fonte]

Outra opção que pode ser usado junto com o rebase é a opção -i ou --interactive. Tal opção é usada para permitir um maior controle sobre como o rebase irá acontecer. Dessa forma, para executar o rebase, é necessário usar o comando:

git rebase -i <base>

Quando se usa tal comando, o editor de texto escolhido como padrão irá mostrar os commits que serão movidos:

pick 33d5b7a Mensagem do commit #1
pick 9480b3d Mensagem do commit #2
pick 5c67e61 Mensagem do commit #3

É nessa hora que o modo interativo do rebase mostra seu real valor. Dentro do editor o usuário pode aplicar os seguintes comandos:

  • pick: Seleciona o commit para ser incluído no novo histórico criado pelo rebase. Vale ressaltar que o usuário pode mudar a ordem dos commits usando o comando pick. Para isso basta mudar a ordem que os commits aparecem no editor de texto junto com o comando pick. Por exemplo:
 
#Antes                                                #Depois
pick 33d5b7a Mensagem do commit #1                    pick 9480b3d Mensagem do commit #2 
pick 9480b3d Mensagem do commit #2                    pick 33d5b7a Mensagem do commit #1
pick 5c67e61 Mensagem do commit #3                    pick 5c67e61 Mensagem do commit #3

Caso o usuário queira retirar um commit do novo histórico, basta apagar a linha associada a tal commit.

  • reword: Similar ao comando pick, entretanto, quando o rebase estiver acontecendo e encontrar um commit marcado com a opção reword, o processo será pausado e um editor será aberto para que o usuário possa mudar a mensagem do commit. Uma vez que o usuário tenha terminado tal etapa e saído do editor de texto, o processo de rebase irá continuar normalmente.
  • edit: Essa opção permite editar o commit marcado. Por editar, pode-se desde adicionar novos arquivos ao commit como também quebrar o commit marcado em commits menores. De forma similar a opção reword, quando o processo de rebase encontrar essa opção, ele irá pausar e irá permitir que o usuário faça as edições que ache necessário. Uma vez prontas, o usuário pode então executar o comando mostrado a seguir para continuar o processo de rebase:
git rebase --continue
  • squash: Permite que um ou mais commits sejam combinados. No exemplo mostrado a seguir, o segundo e o terceiro commit serão anexados ao primeiro commit. Vale lembrar que a opção squash faz com que o commit seja enexado ao primeiro commit acima. Por fim, o usuário também será requisitado para fornecer uma nova mensagem para a combinação de commits realizada.
pick 33d5b7a Mensagem do commit #1
squash 9480b3d Mensagem do commit #2
squash 5c67e61 Mensagem do commit #3
  • fixup: Similar ao comando squash, entretanto a mensagem do commit não será alterado. Ou seja, os commits serão combinados, mas a mensagem do commit original do commit superior será usada para descrever o novo commit. No exemplo a seguir, a mensagem do novo commit será "Mensagem do commit #1", ou seja, o usuário não será requisitado para fornecer uma nova mensagem ao commit gerado.
pick 33d5b7a Mensagem do commit #1
fixup 9480b3d Mensagem do commit #2
fixup 5c67e61 Mensagem do commit #3
  • exec: Permite a execução de comandos durante o rebase. No exemplo mostrado a seguir, após o rebase executar um squash, o comando exec irá permitir a execução do comando rake para rodar os testes baseado na nova alteração. Se o comando executado por exec não retorna 0, o rebase será pausado, permitindo assim que o usuário conserte os problemas encontrados.
pick 33d5b7a Mensagem do commit #1
fixup 9480b3d Mensagem do commit #2
fixup 5c67e61 Mensagem do commit #3
exec rake

É sempre bom lembrar que o rebase pode ser abortado caso o usuário encontre problema em algum passo. Para isso, o mesmo deve executar o comando:

git rebase --abort

A história do projeto será então mantida conforme estava antes da execução do rebase.

Ao usar o rebase interativo, o usuário tem total controle sobre a história do projeto. Uma vantagem clara é que o mesmo não precisa se preocupar tanto com a granularidade dos commits, pois com tal comando o mesmo pode fazer isso depois de concluir uma feature. Vale lembrar que o rebase não necessariamente necessita receber uma branch como parâmetro. O rebase pode ser executado na mesma branch, para mudar o histórico local. O exemplo mostrado a seguir tem como parâmetro o terceiro commit anterior ao commit atual:

git rebase -i HEAD~3

Dessa forma, o rebase interativo irá ocorrer apenas sobre os últimos três commits.

Outra vantagem clara é criar um histórico de commits bem mais linear, pois usando o rebase, pode-se criar um fluxo de commits bem mais simples claro de ler, o que facilitia tanto para a equipe que está desenvolvendo uma aplicação quanto para usuários que pretendem contribuir para um projeto. Essa vantagem também é clara quando se está contribuindo para um projeto e um rebase é usado para deixar a a feature em o usuário esta desenvolvendo consistente com o branch principal do projeto. Ao fazer um rebase na feature, o mantenedor que receber o pull request dessa feature poderá fazer um merge dessa feature sem problemas de conflitos ou outras dores de cabeça desnecessárias, fazendo assim com que o mantenedor tenha que se preocupar apenas com o conteúdo da feature e não com o seu histórico de commits.

Merge vs Rebase

[editar | editar código-fonte]

Apesar de ambos comandos fazerem o mesmo papel, de pegar mudanças feitas em uma branch e replicá-las em outra, os comandos fazem isso de forma diferente.

O comando merge irá realmente fazer uma junção entre as duas branchs. Dessa forma, nenhum commit será de fato perdido. Entretanto, o merge irá gerar um commit extra mostrado que o merge entre branches foi realizado. Este commit extra pode vir a ser útil caso o time queira enteder perfeitamente quando novas alterações em uma branch foram usadas em outra. Entretanto, caso tal acompanhamento não seja necessário, esse commit extra será usado apenas para poluir o histórico de commits.

Uma forma de evitar isso é usando o rebase, que diferente do merge, não faz junção de histórico de commits, ele cria novos commits baseados nos commits da possível branch passada como parâmetro e reaplica esses commits na branch atual. Dessa forma, o commit de merge não será criado, criando assim um histórico de commits mais limpo. Entretanto, vale ressaltar que o comando rebase não deve ser usado em certo contexto, que seria aplicar um rebase em branches públicas. Se uma branch for pública, pode-se imaginar que diferentes desenvolvedores podem estar trabalhando em cima dela. Ao realizar um rebase em umbranch pública e subir tal mudança ao repositório irá fazer com que o histórico dos outros desenvolvedores que estão usando esta branch mude também, gerando confusão entre os desenvolvedores e desincronização do trabalho de outros desenvolvedores com a branch.

Dessa forma, o rebase pode ser aplicado para qualquer caso, exceto para branches públicas. Quando uma branch for pública e seu conteúdo precisa ser atualizado, o certo a se fazer é usar o merge, pois a história da branch não será alterada.

Caso queira saber mais sobre o comando rebase, as seguintes páginas podem ser usadas como referência:

documentação do github sobre rebase [Inglês]

Comparação entre merge e rebase [Inglês]

Pode-se também usar o próprio comando man do terminal passando como parâmetros git rebase. Tal comando irá mostrar o manual do rebase junto com todas as opções que o comando aceita. Vale lembrar que apenas as principais opções foram tratadas neste tutorial, mas existem muitas outras que podem atender a necessidades especifícas não tratadas aqui.

Cereja é uma fruta pequena mas bastante saborosa. Pelo fato de ser pequena é preciso que saiamos literalmente catando a fruta no pé, para que possamos come-las. Na grande parte das vezes, não comemos todas as cerejas do pé, principalmente porque algumas podem ainda estar verdes, ou não tão apetitosas ao nossos olhos. O comando git cherry-pick <commit> faz uma alusão a essa cena imaginaria(nós catando cerejas no Pé), mas ao invés de estarmos catando cerejas, estaremos catando commits. Como dito, muitas vezes deixamos de comer algumas cerejas pelos motivos já falados, e assim também ocorre com os commits. Por meio do cherry-pick podemos selecionar exatamente quais commits queremos recuperar de uma branch A e aplicar em nossa branch atual.

  • Quando precisarmos de alguma alteração feita em uma branch que não seja a atual em que estamos trabalhando, mas não queremos realizar rebases e merges que, dependendo do status de nossas branchs, possam vir a ser muito trabalhosos. Com o cherry-pick é possível rearranjar os commits de uma branch A em outra branch B, bastando  apenas definir a sequência com que esses commits serão aplicados a branch B.
  • Versões mais novas do cherry-pick permitem aplicar um intervalo de commits a branch atual.
  • Obviamente, o processo de aplicar commits de uma branch em outra, podem vir a gerar certos conflitos. Assim como no rebase, esses conflitos deverão ser arrumados para que o cherry-pick ocorra corretamente.

Como funciona?

[editar | editar código-fonte]

Supondo que tenhamos o seguinte contexto. Em uma das builds geradas diariamente pela ferramenta de integração contínua, percebemos que um teste na branch master está quebrando. O desenvolvedor rapidamente sobe três commits para a master arrumando esse bug.Para arrumar esse bug crítico da aplicação o desenvolvedor fez três commits. O primeiro commit já resolvia o bug adicionando algumas verificações na classe mapeada , o segundo commit implementava os testes, e o ultimo commit é uma pequena refatoração na classe em questão. O desenvolvimento na master continua e uma série de novos commits pertencentes a outras features são adicionadas. Infelizmente, duas sprints após o bug ter sido resolvido na master, verificou-se que havia o mesmo bug na branch stable, e que seria necessário trazer para a stable, apenas esses três commits feitos pelo desenvolvedor, é nesse ponto que entra o comando cherry-pick. A próxima seção irá descrever todo o processo de utilizar o cherry-pick, dando tanto a visão do desenvolvedor que arrumou o bug, quanto do responsável por atualizar a branch stable com os novos commits.

[[File:Gcs cherry pick.png|thumb|Simple image]]

Comandos Básicos

[editar | editar código-fonte]
Comandos do desenvolvedor
[editar | editar código-fonte]
  • O desenvolvedor verifica se está na branch correta;
 git branch

O desenvolvedor arruma o bug na classe project, adiciona a classe e realiza o commit.

vim project.rb
git add project.rb
git commit -m "Arruma o bug reportado na issue #45"
  • O desenvolvedor cria os testes unitarios, adiciona os arquivos e realiza o commit.
    vim test_project.rb
    git add test_project.rb
    git commit -m "teste unitário para reproduzir bug"
    
  • Por fim o desenvolvedor realiza melhorias no código, e faz um terceiro commit
vim project.rb
git add project.rb
git commit -m "Arruma o bug reportado na issue #45"
  • O desenvolvedor sobe as alterações para o repositório remoto.
git push origin master

Nesse momento o bug foi concertado, e a branch master continuou a evoluir. A imagem a seguir descreve o status atual do repositório.

As esferas L,M e N foram os commits realizados pelo desenvolvedor, as esferas O,P,Q são novas features.

Comandos do responsável por atualizar a branch stable

[editar | editar código-fonte]
  • Primeiro o responsável muda para a branch correta;
git checkout stable

O cherry-pick sempre irá ocorrer entre duas branchs diferentes, mas que pertencem ao mesmo repositório. Dessa forma mudamos para a branch para qual iremos trazer os commits que queremos aplicar. No contexto dado, nossos commits estão na master, e queremos aplica-los na branch stable. Ainda falando do contexto dado iremos realizar dois procedimentos que são possíveis ao utiliza o cherry-pick.

  1. Aplicar os commits ta branch master na branch stable
  2. Reordenar os commits de modo que as práticas do TDD sejam atendidas.

Para aplicarmos os commits da branch master na branch stable o cherry-pick provê as seguintes formas de "catarmos" nossos commits;

git cherry-pick master       # commit Q
git cherry-pick master^      # commit P
git cherry-pick master~2     # commit O
git cherry-pick master~3     # commit N

Ao utilizamos o cherry-pick, os commits que estão vindo da outra branch, serão colocados acima dos commits da branch atual.Como queremos deixar os commits em um formato TDD, os testes deverão vir antes da correção do bug. Para fazermos isso, basta definirmos a ordem com que queremos organizar nosso commits. No caso do nosso contexto fariamos.

git cherry-pick master~4     # commit M 
git cherry-pick master~5     # commit L
git cherry-pick master~3     # commit N

Nosso esquema de branchs agora está da seguinte maneira:

É muito possível que em algum momento que estivermos utilizando o cherry-pick, conflitos terão de ser resolvidos. Para isso o cherry-pick possui subcomandos iguais aos do rebase, que permitem arrumar esses conflitos antes de enviar o commit para o repositório local.

git cherry-pick --continue
git cherry-pick --abort
git cherry-pick --quit

Algumas flags

[editar | editar código-fonte]
  • Percebemos que foram aplicados na branch stable os commits L', M' e N', provenientes da branch master. Caso quiséssemos alterar a descrição de algum dos commits bastaria fazer:
git cherry-pick <commit> -e
  • Caso queiramos manter um histórico dos commits que foram aplicados utilizando cherry-pick, podemos utilizar a flag -x.
git cherry-pick <commit> -x

Isso irá gerar:

(cherry picked from commit d805a8da83464c54697595dde7d3fc59747e4ab0)

Para mais informações sobre o cherry-pick acesse:

http://git-scm.com/docs/git-cherry-pick

Caso use alguma distribuição linux, basta executar no terminal o comando man git-cherry-pick ou info git-cherry-pick..

O comando stash é normalmente usado quando se deseja mudar de uma branch para outra, porém os arquivos que estão na branch atual ainda não estão em um estado adequado para serem commitados. O comando stash irá basicamente pegar as alterações no respositório atual e irá colocá-las em uma pilha. Isso pode ser feito com o seguinte comando:

git stash

Uma vez usado tal comando, pode-se visualizar assim a lista de stashes criados pelo usuário usando o comando:

git stash list

Um exemplo da saída deste comando pode ser visualizada a seguir:

stash@{0}: WIP on master: 049d078 adicionando README.md
stash@{1}: WIP on master: c264051 modificando README.md
stash@{2}: WIP on master: 21d80a5 criando .gitignore

Vale ressaltar que a mensagem que aparece na lista de stashes é referente ao último commit realizado.

Um possível problema em usar o git stash sem argumentos é que podemos esquecer o que aquele stash realmente fiz, uma vez que sem argumentos, a mensagem que será guardada na pilha é a que está no ultimo commit da branch. Uma forma de revertermos isso é salvando uma mensagem específica para as alterações que estamos fazendo.Dessa forma, quando voltarmos para a branch, saberemos exatamente em que estávamos trabalhando antes do checkout.

git stash save "altera arquivos de configuração do banco de dados"
stash@{0}: On master: altera arquivos de configuração do banco de dados

Ainda falando do git stash save também é possível adicionar a pilha, os arquivos não versionados pelo git(untracked files), bastando apenas passar a flag --include-untracked. Com essa flag o git irá adicionar a pilha, e limpar a árvore de trabalho.

Ao usar o stash, pode-se perceber que o diretório principal ficará limpo. Ou seja, os arquivos acompanhados não irão mais aparecer na lista. Vale ressaltar que se o usuário deseja adicionar tantos os arquivos acompanhados como não acompanhados ao stash, deve usar o seguinte comando:

git stash --all

Após realizar o stash, o usuário pode mudar de branch sem problemas de perder suas modificações locais. Caso necessite voltar a sua branch de origem e pegar de volta os arquivos na pilha do stash, o usuário deve rodar o comando:

git stash apply numero_da_stash

Se nenhuma número for passado como parâmetro para o comando stash, o git irá assumir que o usuário deseja recuperar o último stash salvo na pilha.O git stash apply aplica a ultima alteração da pilha no topo da branch atual, mas mantem o historico do stash.

O seguinte comando irá aplicar as alterações, e remover da pilha o stash salvo. Caso ao aplicar o commit houver algum conflito, o estado não será removido da pilha, e teremos que resolver os conflitos identificados pelo git, antes de aplicar o novo estado da stash escolhida.

git stash pop numero_da_stash

Caso queiramos criar uma branch, a partir do nosso ultimo stash feito, basta utilizar o seguinte comando:

git stash branch new_branch

Com isso, criamos a branch e em seguida mudamos para a branch recém criada.Isso é útil quando estamos trabalhando em uma branch que mudou bastante desde o útimo stash feito, e ao tentarmos dar uma git stash apply temos diversos conflitos devido a essas mudanças. Criando uma nova branch a partir do stash, temos uma branch limpa, e atualizada em relação ao que foi salvo na stash.

Por fim, caso queiramos apenas remover um estado da stash, basta fazermos:

git stash drop numero_da_stash

Caso não passemos o número da stash, o git irá remover o estado do topo.

Para mais informações sobre o stash acesse:

http://git-scm.com/docs/git-stash

Caso use alguma distribuição linux, basta executar no terminal o comando man git-stash ou info git-stash..