Por que tantos projetos preferem "git rebase" a "git merge"?

68

Uma das vantagens do uso de um DVCS é o fluxo de trabalho de edição-confirmação-mesclagem (sobre a edição-consolidação-mesclagem geralmente imposta por um CVCS). Permitir que cada alteração exclusiva seja registrada no repositório independente de mesclagens garante que o DAG reflita com precisão o verdadeiro pedigree do projeto.

Por que tantos sites falam sobre querer "evitar consolidação de mesclagem"? A mesclagem de pré-confirmação ou re-mesclagem não dificulta o isolamento de regressões, a reversão de alterações anteriores etc.?

Ponto de esclarecimento: O comportamento padrão de um DVCS é criar confirmações de mesclagem. Por que tantos lugares falam sobre o desejo de ver uma história de desenvolvimento linear que oculte esses compromissos de mesclagem?

Jace Browning
fonte
18
As ferramentas nem sempre acertam. E quando eles entendem errado, oh garoto, eles entendem errado.
Oded
2
@Oded está se referindo a confirmações de mesclagem automática (como as criadas por git pull)? Eu posso ver por que isso pode ser problemático, mas minha pergunta é sobre todos os commits de mesclagem.
precisa
possível duplicata de Os conflitos de mesclagem complicada frequente são um sinal de problemas? "Fusões e rebases deve causar exatos mesmos conflitos, para os conflitos inerentes que um ser humano deve resolver (ou seja, dois desenvolvedores mudando a mesma linha de código) ..."
mosquito
Um pensamento que tive depois de postar minha resposta, que IMO não faz exatamente se encaixam como uma resposta: git pull --rebase == svn up(soltas-iguais, não estrita-iguala)
Izkata
em uma loja centralizada como o SourceSafe, as alterações continuariam sendo registradas em um esforço contínuo, mas uma vez atingido um platô estável, rotularíamos todos os arquivos em uma determinada data. Então, pelo menos, você pode correlacionar os erros antes ou depois de tal e qual versão ou rebase- line.
21313 Andyz Smith

Respostas:

64

As pessoas querem evitar confirmações de mesclagem porque isso torna o log mais bonito. A sério. Parece que os logs centralizados com os quais cresceram e localmente podem fazer todo o seu desenvolvimento em uma única ramificação. Além desses aspectos estéticos, não há benefícios, além de vários inconvenientes, como tornar propenso a conflitos extrair diretamente de um colega sem passar pelo servidor "central".

Karl Bielefeldt
fonte
5
Você é o único que entendeu minha pergunta corretamente. Como posso deixar mais claro?
precisa
12
Talvez reformule para "Quais são os benefícios de uma história de desenvolvimento linear?"
Karl Bielefeldt
11
"Frequentemente, você faz isso para garantir que suas confirmações sejam aplicadas de maneira limpa em uma ramificação remota - talvez em um projeto no qual você esteja tentando contribuir, mas que não mantém. Nesse caso, você faria o seu trabalho em uma ramificação e, em seguida, refaça o seu trabalho em origem / mestre quando você estiver pronto para enviar seus patches para o projeto principal. Dessa forma, o mantenedor não precisa fazer nenhum trabalho de integração - apenas uma aplicação rápida ou limpa. " git-scm.com/book/en/Git-Branching-Rebasing
Andyz Smith
12
Você faz parecer apenas cosmético, mas é realmente prático: é muito mais fácil entender o que está acontecendo em uma história linear do que em um DAG complicado com linhas cruzando e mesclando.
21713 Daniel Hershcovich
20
"Não há benefícios além dessa estética" - não sei sobre isso. É realmente difícil entender o que está acontecendo na sua história quando ela parece uma placa de circuito. (Supondo que vários membros da equipe, cada um com suas próprias filiais e fluxos de trabalho.)
Archagon
46

Em duas palavras: git bisect

Um histórico linear permite identificar a fonte real de um bug.


Um exemplo. Este é o nosso código inicial:

def bar(arg=None):
    pass

def foo():
    bar()

A ramificação 1 faz alguma refatoração que argnão é mais válida:

def bar():
    pass

def foo():
    bar()

A ramificação 2 possui um novo recurso que precisa ser usado arg:

def bar(arg=None):
    pass

def foo():
    bar(arg=1)

Não haverá conflitos de mesclagem, no entanto, um bug foi introduzido. Felizmente, este em particular será pego na etapa de compilação, mas nem sempre temos tanta sorte. Se o bug se manifestar como comportamento inesperado, em vez de um erro de compilação, talvez não o encontremos por uma semana ou duas. Nesse ponto, git bisectpara o resgate!

Oh droga. Isto é o que vê:

(master: green)
|             \_________________________
|                \                      \
(master: green)  (branch 1: green)     (branch 2: green)
|                 |                     |
|                 |                     |
(master/merge commit: green)            |
|                         ______________/
|                        /
(master/merge commit: red)
|
...days pass...
|
(master: red)

Portanto, quando enviamos git bisectpara encontrar o commit que quebrou a compilação, ele identifica um commit de mesclagem. Bem, isso ajuda um pouco, mas está essencialmente apontando para um pacote de commits, não um único. Todos os antepassados ​​são verdes. Por outro lado, com o rebase, você obtém um histórico linear:

(master: green)
|
(master: green)
|
(all branch 1 commits: green)
|
(some branch 2 commits: green)
|
(branch 2 commit: red)
|
(remaining branch 2 commits: red)
|
...days pass...
|
(master: still red)

Agora, git bisectapontará o commit exato do recurso que interrompeu a compilação. Idealmente, a mensagem de confirmação explicará o que foi planejado o suficiente para fazer outro refator e corrigir o erro imediatamente.

O efeito é composto apenas em grandes projetos quando os mantenedores não escrevem todo o código, portanto, eles não se lembram necessariamente do porquê de qualquer confirmação feita / para que serve cada ramificação. Portanto, identificar o commit exato (e, em seguida, poder examinar os commits em torno dele) é uma grande ajuda.


Dito isto, eu (atualmente) ainda prefiro a fusão. A reinicialização em uma ramificação de liberação fornecerá seu histórico linear para uso git bisect, mantendo o histórico verdadeiro para o trabalho diário.

Izkata
fonte
2
Obrigado por esta explicação. Você pode expandir por que você ainda prefere a fusão? Sei que muitas pessoas preferem se fundir a rebasear na maioria das situações, mas nunca consegui entender o porquê.
Philip
@ Philip Estou um pouco hesitante em fazê-lo, pois só usei o git por alguns meses em pequenos projetos pessoais, nunca em equipe. Mas, ao acompanhar o que fiz recentemente, gitk --allé extremamente útil ver o fluxo de confirmações entre filiais (especialmente porque geralmente tenho três filiais em um determinado momento, e alterno entre elas todos os dias, dependendo do meu humor em A Hora).
Izkata
16
Mas a mesclagem é o problema, portanto, você deseja um commit de mesclagem como bisectresultado. É fácil encontrar o indivíduo confirmado com um blameou um diferente, bisectse você quiser ir mais fundo. Com um histórico linear, a mesclagem é feita fora do livro, então você não pode mais dizer que uma mesclagem incorreta foi o problema. Parece apenas um idiota usando um argumento que não existe, especialmente se o argumento foi removido vários commits anteriores. Tentar descobrir por que ele faria isso é muito mais difícil quando você destrói a história.
Karl Bielefeldt
11
Realmente discordo desta resposta. Rebasing destrói a utilidade do bisect às vezes. Exceto pelas confirmações principais de uma rebase, as confirmações de uma rebase podem não ser executáveis. Exemplo: você se ramificou em A e fez B, C, D, alguém empurrar E. Você é B e C e trabalha, mas é rebaseado em E, você é B e C são intransitáveis ​​ou são executáveis ​​e não funcionam. Melhor caso é que você desista, pior caso você acha B ou C contém a questão, quando na realidade é D.
Lan
4
@ Izkata Por que você está usando o bisect? Porque erros / erros acontecem. É uma realidade humilhante. Talvez "arrastar o mouse para a esquerda" não fazia parte da lista de testes automáticos / manuais antes e agora percebemos que esse recurso está quebrado, mas sabemos que costumava funcionar.
Lan
17

Em resumo, porque a fusão geralmente é outro lugar para algo dar errado, e ele só precisa dar errado uma vez para deixar as pessoas com muito medo de lidar com isso novamente (uma vez mordido duas vezes tímido, se quiser).

Então, digamos que estamos trabalhando em uma nova tela de gerenciamento de contas, e acontece que um bug foi descoberto no fluxo de trabalho Nova conta. OK, seguimos dois caminhos separados - você termina o Gerenciamento de contas e eu corrigo o erro com Novas contas. Como estamos lidando com contas, trabalhamos com códigos muito semelhantes - talvez tenhamos que ajustar os mesmos trechos de código.

Agora, neste momento, temos duas versões diferentes, mas totalmente funcionais, de software. Ambos executamos um commit em nossas alterações, testamos obedientemente nosso código e, independentemente, estamos muito confiantes de que fizemos um ótimo trabalho. O que agora?

Bem, é hora de fundir, mas ... merda, o que acontece agora? Poderíamos muito bem ir de dois conjuntos de software em funcionamento a um software unificado e horrivelmente quebrado de software com erros, em que o gerenciamento de contas não funciona e as novas contas estão quebradas e nem sei se o bug antigo ainda está lá .

Talvez o software fosse inteligente e dissesse que havia um conflito e insistia em dar orientação. Bem, droga - eu me sento para fazer isso e vejo que você adicionou um código complexo que não entendo imediatamente. Eu acho que está em conflito com as mudanças que fiz ... Eu pergunto, e quando você chegar um minuto você verifica e vê meu código que você não entende. Um de nós dois precisa dedicar algum tempo para se sentar, fazer uma fusão adequada e possivelmente testar novamente a coisa toda para garantir que não a quebremos.

Enquanto isso, oito outros caras estão cometendo códigos como os sádicos, fiz algumas pequenas correções e as enviei antes que soubéssemos que tínhamos um conflito de mesclagem, e, com certeza, parece um bom momento para fazer uma pausa, e talvez você estão de folga para a tarde ou presos em uma reunião ou o que quer. Talvez eu devesse tirar férias. Ou mude de carreira.

E assim, para escapar desse pesadelo, algumas pessoas ficaram com muito medo de se comprometer (o que mais há de novo, certo?). Naturalmente, somos avessos ao risco em cenários como este - a menos que pensemos que somos péssimos e que vamos estragar tudo, caso em que as pessoas começam a agir com abandono imprudente. suspiro

Então lá vai você. Sim, os sistemas modernos são projetados para aliviar essa dor, e é suposto ser capaz de voltar e rebocar, rebaixar, rebaixar e liberar bases e hanglide e tudo isso.

Mas é tudo mais trabalho, e nós apenas queremos apertar o botão no micro-ondas e fazer uma refeição de 4 pratos antes de termos tempo para encontrar um garfo, e tudo parece muito insatisfatório - código é trabalho, é produtivo, significativo, mas manipular graciosamente uma mesclagem simplesmente não conta.

Os programadores, em regra, precisam desenvolver uma ótima memória de trabalho e, em seguida, tendem a esquecer imediatamente todos esses nomes de lixo e variáveis ​​e o escopo assim que terminam o problema, além de lidar com um conflito de mesclagem (ou pior, mesclagem incorreta) é um convite para ser lembrado de sua mortalidade.

BrianH
fonte
5
Surpreendentemente redigido, senhor!
Southpaw Hare
10
Isso responde por que as pessoas têm medo de se fundir, não por que os commits de mesclagem são considerados ruins por muitos.
Jay
23
O problema com esta resposta é que isso ainda é verdade para uma nova recuperação . Afinal, uma rebase ainda está fazendo uma mesclagem na qual você alterou uma linha de código que já foi alterada. Penso que este é um problema com a forma como a questão foi declarada.
deworde
11
Eu diminuí a votação desta resposta porque, como ainda é eloqüente, está além do ponto, imho.
19313 Eugene
5
Isso não parece afetar rebase vs mesclagem.
Andyz Smith
5

O rebaseamento fornece um ponto de ramificação móvel que simplifica o processo de enviar as alterações de volta à linha de base. Isso permite que você trate um ramo de execução longa como se fosse uma alteração local. Sem rebasear, as ramificações acumulam alterações a partir da linha de base, que serão incluídas nas alterações que serão mescladas de volta à linha de base.

A mesclagem deixa sua linha de base no ponto de ramificação original. Se você mesclar algumas semanas de alterações a partir da linha em que você se ramificou, agora terá muitas alterações no ponto de ramificação, muitas das quais estarão em sua linha de base. Isso dificulta a identificação de suas alterações em sua filial. Enviar as alterações de volta à linha de base pode gerar conflitos não relacionados às suas alterações. No caso de conflitos, é possível fazer alterações inconsistentes. Mesclagens contínuas exigem esforço para gerenciar, e é relativamente fácil perder alterações.

Rebase move seu ponto de ramificação para a revisão mais recente em sua linha de base. Quaisquer conflitos que você encontrar serão apenas para as suas alterações. Pressionar as mudanças é muito mais simples. Os conflitos são tratados na ramificação local, fazendo uma nova rebase. No caso de envios conflitantes, o último a enviar suas alterações precisará resolver o problema com as alterações.

BillThor
fonte
11
portanto, tudo o que faz é rebaixar novas alterações para um nível que elas sejam consideradas "a coisa errada" porque são a única mudança . Se todas as alterações antigas forem incluídas na mesclagem, todas as alterações estarão em pé de igualdade, sobre se elas são 'a coisa certa' hoje.
Andyz Smith
@AndyzSmith Eu não consideraria as novas mudanças a coisa errada . Ao tentar determinar o que está sendo alterado e deve ser enviado, eles são a coisa certa . Depois de reformular, você pode ignorar as alterações antigas, pois elas fazem parte da sua linha de base.
BillThor
certo, então Se você não tiver certeza de qual é sua linha de base, ou seja, se o que você mudou já está pronto para ser mesclado é "a coisa certa", não faça uma nova recuperação.
Andyz Smith
E, se você receber um empurrão de mudança de alguém e interromper a compilação porque o protótipo de função não corresponde a um protótipo existente, você sabe instantaneamente, por causa do rebase, que o novo cara está errado. você não precisa adivinhar que talvez o novo cara tenha o protótipo certo e que as mudanças no changset não aplicadas e não aplicadas anteriormente sejam as que têm o protótipo errado.
Andyz Smith
4

As ferramentas automatizadas estão melhorando para garantir que o código de mesclagem seja compilado e executado, evitando conflitos sintáticos , mas não podem garantir a ausência de conflitos lógicos que podem ser introduzidos por mesclagens. Portanto, uma mesclagem 'bem-sucedida' fornece uma sensação de falsa confiança, quando na realidade nada garante e você precisa refazer todos os seus testes.

O verdadeiro problema com ramificação e fusão, a meu ver, é que ele chuta a lata proverbial no futuro. Ele permite que você diga "Vou trabalhar no meu pequeno mundo" por uma semana e lide com os problemas que surgirem mais tarde. Mas corrigir bugs é sempre mais rápido / mais barato quando novos. No momento em que todas as ramificações de código começam a ser mescladas, você já pode esquecer algumas das nuances das coisas que foram feitas.

Reúna os dois problemas mencionados acima e você poderá se encontrar em uma situação em que é mais simples e fácil que todos trabalhem no mesmo tronco e resolvam continuamente conflitos à medida que surgem, mesmo que tornem o desenvolvimento ativo um pouco mais lento.

MrFox
fonte
4
Isso responde por que as pessoas têm medo de se fundir, não por que os commits de mesclagem são considerados ruins por muitos.
Jay
Então, esse tronco a que você se refere, isso envolve rebase? Expound por favor.
Andyz Smith
@AndyzSmith "trunk" é o termo svn para seu equivalente ao ramo "master" do git
Izkata
@ Izkata, então esta resposta defende não usar mesclagem ou rebase então?
21313 Andyz Smith
@AndyzSmith Sim, é assim que eu também leio. E eu discordo disso; depois de trabalhar em uma equipe com 7 outros desenvolvedores da maneira sugerida, eu não sugiro. Deveríamos ter usado ramos de recursos mais do que usamos, mas a maioria deles tem medo de se fundir (como BrianDHall entra em sua resposta).
Izkata
0

Um ponto adicional relevante é o seguinte: com o rebasing, posso facilmente escolher ou reverter um recurso no meu ramo de lançamento.

Bruno Schäpper
fonte
11
Este elaborado sobre isso?
Akavall 17/09/18
Claro :-) No fluxo git, criamos regularmente novas ramificações de lançamento. Se após a criação, um bug é corrigido no ramo de desenvolvimento, usamos git cherry-pick -x <commit>para aplicá-lo ao ramo de lançamento. Ou, podemos querer desfazer algo para o lançamento, com git revert <commit>. Se <commit>é uma mesclagem, fica peludo. Ele é possível, tanto quanto eu sei, mas não é fácil de fazer. Ao usar o rebase, cada 'mesclagem' é um commit gigante, mas regular, facilmente selecionável e reversível.
Bruno Schäpper
0

Três coisas não foram ditas em nenhuma das respostas:

  • Diferindo dentro de um galho:

    • A diferença entre um par arbitrário de confirmações em uma ramificação se torna extremamente difícil quando há confirmações de mesclagem.
  • Mesclando um item por vez:

    • Quando você está resolvendo a diferença entre duas ramificações, uma mesclagem geralmente acontece de uma só vez, e qualquer conflito de mesclagem é deixado sem o contexto do qual a consolidação específica foi responsável pelo conflito. Se você refazer a recuperação, a recuperação será interrompida no ponto em que o conflito ocorreu e permitirá que você o resolva nesse contexto.
  • Limpeza antes do envio:

    • Se você cometeu um erro cometido que mais tarde precisará corrigir, se você não tiver pressionado o rebase interativo, permitirá combinar / dividir / alterar / mover confirmações. Embora você ainda possa fazer isso se tiver mesclado, fica muito difícil se você deseja combinar / dividir / alterar / mover-se através de um limite de mesclagem.
Catskul
fonte