porcellis.com

ref: master

content/blog/a-magia-do-git-rebase.md


  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
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
---
title: "A Magia do git rebase"
date: 2021-04-29
draft: false
tags: ["git"]
---

Uma das ferramentas que eu acho das mais úteis no `git`, e da qual eu [mais
amo][toot] é o `git-rebase(1)`. Ela se baseia na premissa que trouxe o git para
o mundo dos sistemas de controle de versão: de que você *pode e deve* alterar a
história do teu repositório de forma a tornar ele mais legível e organizado.

[toot]: https://alfazema.club/@eletrotupi/105984543604974096

Ao redor do mundo, cada projeto estabelece sua convenção de como trabalhar com o
git, alguns utilizam técnicas como o git-flow, outros, apenas uma branch e tags,
outros dividem em duas branchs principais, `master/main` e `production/release`.
A convenção no meu trabalho, é de sempre criar uma _branch_ específica para
trabalhar em uma nova funcionalidade, correção de algum erro ou melhoria num
geral. Feito isso, eu adoto a disciplina de (1) manter ela atualizada com a
_branch_ pai usando `git pull --rebase origin/branch_pai` e (2) conforme for
trabalhando nela, procuro manter ela organizada usando o `git rebase -i HEAD~N`.

## Rebase

O rebase tem um funcionamento bastante simples, em essência, ao contrário de
executar um _merge_ (ou seja, criar um _commit_ que puxa as mudanças para a tua
branch), o rebase: temporariamente remove os teus patches, puxa os commits da
_branch_ pai e aplica os teus patches em sequência em cima dessa branch
atualizada. O benefício aqui é claro: a branch contém apenas uma lista de
commits com o seu trabalho novo, de forma legível e coerente.

Utilizando o parâmetro `-i` no comando, inicia um _rebase_ interativo, iniciando
a partir do commit que eu pedi. [^1] Por exemplo, `git rebase -i HEAD~5`, vai
iniciar um rebase até os meus últimos 5 commits. O _rebase_ vai mostrar o
seguinte:

[^1]: `HEAD~N`, significa essencialmente a partir do ultimo commit disponível
  nessa branch até N commits anteriormente.

```ini
pick 9fdd140 hooks: Flesh out the Hook::Destroy service test
pick ecb296e resource: Flesh out the resource test
pick bd115a0 hooks: Generate token when subscribing
pick fcaba9d hooks: Flesh out the handler test
pick 3926d4b doc: Add document about testing

# Rebase bed2ff9..3926d4b onto bed2ff9 (5 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
```

Ou seja, vai me mostrar meus últimos 5 commits, e me pedir para eu escolher
(`pick` ou `break`), remover (`drop`), editar (`edit` ou `reword`) ou juntar
eles (`fixup` e `squash`).

## Disciplina

Meu plano de trabalho, em geral é o seguinte: conforme eu avanço no trabalho que
eu tenho que fazer, commito localmente de forma que eu vagamente consiga
explicar o que eu fiz. No momento que eu sinto que terminei e já vale de
apresentar esse trabalho, utilizo o _rebase_ para reescrever, quebrar/juntar
esses commits em uma narrativa de passos sequenciais mais legíveis e coerente.

Buscando escrever essa narrativa, procuro criar mudanças atômicas que por si só
sejam responsáveis por responder a pergunta de _o que foi feito?_ e _isso não
quebra os testes?_ [^2].

[^2]: Ou seja, sem `fix tests`, `fix typo`.

Uma experiência que me ajudou muito nesse propósito, foi o de contribuir com
projetos que utilizam uma lista de e-mail pública [^3], enviando patches ao
invés de criar _Pull Requests_. Em especial, porque a lógica de trabalho muda. A
forma de trabalho apresentada por ferramentas como Github, GitLab e outras
derivações similares é de criar um PR e infinitamente seguir adicionando commits
nele até que o trabalho esteja pronto, a interface que se vire para mostrar o
que realmente é importante. Contudo, a lógica de trabalho do git por email é de
tentar ser o mais assertivo possível, evitando patchsets [^4] gigantes e com
mudanças em lugares diferentes. Uma das vantagens de lidar com patchsets, é que
é possível aplicar uma seleção de patches de um patchset, e pedir revisão dos
outros que não façam sentido ou necessitem de um trabalho maior naquele momento.

[^3]: Que é o formato de contribuição do qual o git foi feito para funcionar
  originalmente, exemplos notáveis são a lista de email do Kernel, Alpine Linux,
  git, cgit, dwm e outras ferramentas da suite suckless, coreutils, musl-libc,
  freedesktop, vim, emacs, sistemas BSD e por aí vai.

[^4]: Um patch é um commit, e um patchset é uma coleção de patches.

Aplicando essa lógica, procuro sempre quebrar um patchset muito grande em vários
patchsets diferentes [^5] e com patches atômicos, descritivos, geralmente
organizado em módulos ou partes [^6].

[^5]: Um exemplo aqui poderia ser organizar _includes_, remover espaços em
  branco, ou qualquer mudança estética no código que não tenha relação direta
  com a funcionalidade/correção que seja necessário originalmente.

[^6]: Geralmente adoto o seguinte formato para meus commits: `módulo:
  sub-módulo, descrição do que esse commit faz` e descrevo o porque essa mudança
  foi feita e qual abordagem eu tomei no corpo do commit.

Em resumo, o git é fantástico. Ter disciplina para utilizá-lo e aprender como
ele funciona é uma obrigação de qualquer programador. Uma das principais
diferenças dele para os outros sistemas de controle de versão que vieram antes é
justamente que a história de trabalho daquele repositório não é escrita em
pedra, não é apenas leitura. Aliado a isso o git tem uma extensiva documentação
offline, disponível para você estudá-lo a respeito nos seus manuais. Recomendo
fortemente a leitura dessas man pages: git-rebase(1), giteveryday(7) e
gitrevisions(7).