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). |