Meu escritório deseja que uma ramificação infinita seja mesclada como política; que outras opções nós temos?

119

Meu escritório está tentando descobrir como lidamos com divisões e mesclagens de filiais e enfrentamos um grande problema.

Nossa questão é com sidebranches de longo prazo - do tipo em que você tem algumas pessoas trabalhando em um sidebranch que se separa do master, desenvolvemos por alguns meses e quando atingimos um marco sincronizamos os dois.

Agora, IMHO, a maneira natural de lidar com isso é esmagar o sidebranch em um único commit. master continua avançando; como deveria - não estamos retroativamente descartando meses de desenvolvimento paralelo na história de master . E se alguém precisa de uma resolução melhor para a história do sidebranch, bem, é claro que tudo ainda está lá - não é apenas em master , é no sidebranch.

Aqui está o problema: Eu trabalho exclusivamente com a linha de comando, mas o resto da minha equipe usa o GUIS. E descobri que o GUIS não tem uma opção razoável para exibir o histórico de outras ramificações. Então, se você chegar a um commit do squash, dizendo "este desenvolvimento esmagado do branch XYZ ", é um enorme esforço para ver o que está em XYZ .

No SourceTree, até onde eu posso encontrar, é uma grande dor de cabeça: se você está em master , e quer ver o histórico de master+devFeature , você precisa verificar master+devFeature (tocando cada arquivo que é diferente), ou então percorra um log mostrando ALL as ramificações do seu repositório em paralelo até encontrar o lugar certo. E boa sorte para descobrir onde você está lá.

Meus colegas de equipe, com razão, não querem ter um histórico de desenvolvimento tão inacessível. Então, eles querem que essas grandes e longas sidebranches de desenvolvimento sejam incorporadas, sempre com um commit de mesclagem. Eles não querem nenhum histórico que não seja imediatamente acessível a partir do branch master.

Eu odeio essa ideia; significa um emaranhado interminável e inavegável da história do desenvolvimento paralelo. Mas eu não estou vendo que alternativa nós temos. E estou muito perplexo; isso parece bloquear quase tudo que eu sei sobre o bom gerenciamento de filiais, e será uma constante frustração para mim se eu não conseguir encontrar uma solução.

Temos alguma opção aqui além de constantemente mesclar sidebranches em master com merge-commits? Ou, há uma razão pela qual constantemente usar merge-commits não é tão ruim quanto eu temo?

    
por Standback 19.12.2016 / 16:23
fonte

7 respostas

242

Mesmo que eu use o Git na linha de comando - eu tenho que concordar com seus colegas. Não é sensato espremer grandes alterações em um único commit. Você está perdendo a história dessa maneira, não apenas tornando-a menos visível.

O ponto de controle de origem é rastrear o histórico de todas as alterações. Quando o que mudou porque? Para esse fim, todo commit contém ponteiros para commits pai, um diff e metadados como uma mensagem de commit. Cada commit descreve o estado do código-fonte e o histórico completo de todas as mudanças que levaram a esse estado. O coletor de lixo pode excluir confirmações que não estão acessíveis.

Ações como rebasing, cherry picking ou squashing excluem ou reescrevem o histórico. Em particular, os commits resultantes não fazem mais referência aos commits originais. Considere isto:

  • Você esmaga alguns commits e nota na mensagem de commit que o histórico esmagado está disponível no commit original abcd123.
  • Você exclui [1] todas as ramificações ou tags que incluem abcd123, já que elas estão mescladas.
  • Você permite que o coletor de lixo seja executado.

[1]: Alguns servidores do Git permitem que filiais sejam protegidas contra exclusão acidental, mas duvido que você queira manter todos os seus ramos de recursos por toda a eternidade.

Agora você não pode mais procurar esse commit - ele simplesmente não existe.

Fazer referência a um nome de ramificação em uma mensagem de confirmação é ainda pior, já que os nomes das ramificações são locais para um repo. O que é master+devFeature no seu checkout local pode ser doodlediduh no meu. As ramificações estão apenas movendo rótulos que apontam para algum objeto de confirmação.

De todas as técnicas de reescrita do histórico, o rebasing é o mais benigno porque duplica os commits completos com todo o seu histórico e apenas substitui um commit pai.

Que o histórico de master inclui o histórico completo de todas as ramificações que foram mescladas nele é uma coisa boa, porque isso representa a realidade. [2] Se houvesse desenvolvimento paralelo, isso deveria ser visível no log.

[2]: Por esse motivo, também prefiro commits explícitos de mesclagem sobre a história linearizada, mas, em última análise, falsa, resultante da recomposição.

Na linha de comando, git log tenta simplificar o histórico exibido e manter todos os itens exibidos comete relevante. Você pode ajustar a simplificação do histórico para atender às suas necessidades. Você pode ser tentado a escrever sua própria ferramenta de registro git que percorre o gráfico de commit, mas geralmente é impossível responder “este commit foi originalmente cometido neste ou naquele branch?”. O primeiro pai de uma consolidação de mesclagem é o HEAD anterior, ou seja, o commit na ramificação na qual você está mesclando. Mas isso pressupõe que você não fez uma mesclagem reversa do mestre no ramo de feição, então redirecionou o mestre para a mesclagem.

A melhor solução para filiais de longo prazo que encontrei é evitar filiais que são mescladas após alguns meses. A fusão é mais fácil quando as alterações são recentes e pequenas. O ideal é que você se fundir pelo menos uma vez por semana. A integração contínua (como na Programação Extrema, não como em “configurar um servidor Jenkins”), sugere até várias mesclagens por dia, ou seja, não manter ramificações de recursos separadas, mas compartilhar uma ramificação de desenvolvimento como uma equipe. Mesclar antes de um recurso ser QA requer que o recurso esteja oculto atrás de um sinalizador de recurso.

Em troca, a integração frequente possibilita detectar possíveis problemas muito mais cedo e ajuda a manter uma arquitetura consistente: mudanças de longo alcance são possíveis porque essas mudanças são rapidamente incluídas em todas as filiais. Se uma alteração quebra algum código, apenas um par de dias de trabalho será interrompido, não um par de meses.

A reescrita do histórico pode fazer sentido para projetos realmente grandes quando há milhões de linhas de código e centenas ou milhares de desenvolvedores ativos. É questionável por que um projeto tão grande teria que ser um único repositório do git em vez de ser dividido em bibliotecas separadas, mas nessa escala é mais conveniente que o repositório central contenha apenas “releases” dos componentes individuais. Por exemplo. o kernel do Linux emprega squash para manter o histórico principal gerenciável. Alguns projetos de código aberto exigem que os patches sejam enviados por e-mail, em vez de uma mesclagem no nível do git.

    
por 19.12.2016 / 17:31
fonte
110

Eu gosto da resposta do Amon , mas senti que uma pequena parte precisava de muito mais ênfase: Você pode facilmente simplificar o histórico enquanto visualiza os registros para atender às suas necessidades, mas outros não podem adicionar histórico ao visualizar os registros para atender às suas necessidades. É por isso que é preferível manter o histórico como ocorreu.

Aqui está um exemplo de um de nossos repositórios. Usamos um modelo pull-request, portanto, o recurso every se parece com seus ramos de longa duração no histórico, mesmo que eles geralmente sejam executados apenas uma semana ou menos. Desenvolvedores individuais, por vezes, optam por esmagar o seu histórico antes de se fundirem, mas geralmente combinamos com os recursos, o que é relativamente incomum. Aqui estão os principais commits em gitk , o gui que vem junto com o git:

Sim,umpoucocomplicado,mastambémgostamosporquepodemosvercomprecisãoquemteveoquemudaaquehoras.Issorefletecomprecisãoonossohistóricodedesenvolvimento.Sequisermosverumavisualizaçãodenívelmaisalto,umasolicitaçãodepullémescladaporvez,podemosveraseguinteview,queéequivalenteaocomandogitlog--first-parent:

git log tem muitas outras opções projetadas para fornecer a você as visualizações desejadas. gitk pode pegar qualquer argumento git log arbitrário para construir uma visualização gráfica. Tenho certeza de que outras GUIs têm recursos semelhantes. Leia os documentos e aprenda a usá-los corretamente, em vez de aplicar sua visualização git log preferencial a todos no momento da mesclagem.

    
por 19.12.2016 / 18:34
fonte
34

Our issue is with long-term sidebranches -- the kind where you've got a few people working a sidebranch that splits from master, we develop for a few months, and when we reach a milestone we sync the two up.

Meu primeiro pensamento é - nem faça isso a menos que seja absolutamente necessário. Suas fusões devem ser desafiadoras às vezes. Mantenha filiais independentes e tão curta quanto possível. É um sinal de que você precisa dividir suas histórias em partes de implementação menores.

Caso você tenha que fazer isso, então é possível mesclar o git com a opção --no-ff para que os históricos sejam mantidos distintos em sua própria ramificação. As confirmações ainda aparecerão no histórico mesclado, mas também podem ser vistas separadamente no ramo de recursos, de modo que, pelo menos, é possível determinar em qual linha de desenvolvimento elas faziam parte.

Eu tenho que admitir que quando comecei a usar o git eu achei um pouco estranho que o branch commits aparecesse no mesmo histórico que o branch principal após a junção. Foi um pouco desconcertante porque não parecia que os commits pertencessem àquela história. Mas na prática, não é algo realmente doloroso, se considerarmos que o ramo de integração é apenas isso - todo o propósito é combinar os ramos de funcionalidades. Em nossa equipe, não fazemos squash e fazemos commits frequentes de mesclagem. Usamos o --no-ff o tempo todo para garantir que seja fácil ver o histórico exato de qualquer recurso se quisermos investigá-lo.

    
por 19.12.2016 / 20:11
fonte
11

A eliminação de um sidebranch de longo prazo faria com que você perdesse muita informação.

O que eu faria é tentar rebaixar o mestre no sidebranch de longo prazo antes de mesclar o sidebranch no master . Dessa forma, você mantém cada commit no master, tornando o histórico de commits linear e mais fácil de entender.

Se eu não pudesse fazer isso facilmente em cada commit, eu deixaria que fosse não-linear , a fim de manter o contexto de desenvolvimento claro. Na minha opinião, se eu tiver uma fusão problemática durante o rebase do mestre no sidebranche, isso significa que a não-linearidade tinha significado no mundo real. Isso significa que será mais fácil entender o que aconteceu caso eu precise investigar a história. Eu também recebo o benefício imediato de não ter que fazer um rebase.

    
por 20.12.2016 / 11:55
fonte
10

Deixe-me responder seus pontos direta e claramente:

Our issue is with long-term sidebranches -- the kind where you've got a few people working a sidebranch that splits from master, we develop for a few months, and when we reach a milestone we sync the two up.

Você geralmente não quer deixar suas filiais sem sincronização por meses.

Seu ramo de recursos se ramificou de algo dependendo do seu fluxo de trabalho; vamos chamá-lo de master para simplificar. Agora, sempre que você se comprometer a dominar, você pode e deve git checkout long_running_feature ; git rebase master . Isso significa que seus branches são, por padrão, sempre em sincronia.

git rebase também é a coisa correta para fazer aqui. Não é um hack ou algo estranho ou perigoso, mas completamente natural. Você perde um bit de informação, que é o "aniversário" do ramo de recursos, mas é isso. Se alguém acha que isso é importante, pode ser fornecido salvando-o em outro lugar (em seu sistema de tickets ou, se necessário, em git tag ...).

Now, IMHO, the natural way to handle this is, squash the sidebranch into a single commit.

Não, você absolutamente não quer isso, você quer um commit de mesclagem. Um commit de mesclagem também é um "commit único". Ele não, de alguma forma, insere todas as ramificações individuais confirmadas "em" mestre. É um único commit com dois pais - o master head e o branch head no momento da fusão.

Certifique-se de especificar a opção --no-ff , é claro; A fusão sem --no-ff deve, em seu cenário, ser estritamente proibida. Infelizmente, --no-ff não é o padrão; mas acredito que há uma opção que você pode definir para que isso aconteça. Veja git help merge para o que --no-ff faz (em resumo: ativa o comportamento que descrevi no parágrafo anterior), é crucial.

we're not retroactively dumping months of parallel development into master's history.

Absolutamente não - você nunca está jogando algo "na história" de algum ramo, especialmente com um commit de merge.

And if anybody needs better resolution for the sidebranch's history, well, of course it's all still there -- it's just not in master, it's in the sidebranch.

Com um commit de mesclagem, ele ainda está lá. Não no mestre, mas no sidebranch, claramente visível como um dos pais do merge commit, e mantido por toda a eternidade, como deveria ser.

Veja o que eu fiz? Todas as coisas que você descreve para o seu commit do squash estão ali com a mesclagem --no-ff commit.

Here's the problem: I work exclusively with the command line, but the rest of my team uses GUIS.

(Nota lateral: Eu quase exclusivamente trabalho com a linha de comando também (bem, isso é uma mentira, eu costumo usar o emacs magit, mas isso é outra história - se eu não estiver em um lugar conveniente com minha configuração emacs individual, eu prefira a linha de comando também.) Mas por favor faça um favor e tente pelo menos git gui uma vez. É tão muito mais eficiente para escolher linhas, blocos etc. para adicionar / desfazer somas.)

And I've discovered the GUIS don't have a reasonable option to display history from other branches.

Isso porque o que você está tentando fazer é totalmente contra o espírito de git . git é construído a partir do núcleo em um "gráfico acíclico direcionado", o que significa que muitas informações estão no relacionamento pai-filho de confirmações. E, para mesclagens, isso significa que a mesclagem verdadeira compromete dois pais e um filho. As GUIs dos seus colegas ficarão bem assim que você usar no-ff merge commits.

So if you reach a squash commit, saying "this development squashed from branch XYZ", it's a huge pain to go see what's in XYZ.

Sim, mas isso não é um problema da GUI, mas do commit do squash. Usar um squash significa deixar a cabeça de ramificação do recurso pendente e criar um novo commit inteiro em master . Isso quebra a estrutura em dois níveis, criando uma grande confusão.

So they want these big, long development-sidebranches merged in, always with a merge commit.

E eles estão absolutamente certos. Mas eles não são "mesclados em ", eles são apenas mesclados. Uma mesclagem é realmente equilibrada, não tem um lado preferencial que é mesclado "em" o outro ( git checkout A ; git merge B é exatamente o mesmo que git checkout B ; git merge A exceto pequenas diferenças visuais como as ramificações sendo trocadas em git log etc. ).

They don't want any history that isn't immediately accessible from the master branch.

Qual é completamente correto. No momento em que não há recursos não mesclados, você teria um único branch master com um rico histórico encapsulando todas as linhas de commit de recursos que já existia, voltando para o git init commit desde o começo do tempo (note que eu especificamente evite usar o termo "branches" na última parte desse parágrafo, porque a história naquele momento não é mais "branches", embora o grafo de commits seja bastante branchy).

I hate that idea;

Então você sente um pouco de dor, já que está trabalhando contra a ferramenta que está usando. A abordagem git é muito elegante e poderosa, especialmente na área de ramificação / mesclagem; se você fizer isso corretamente (como aludido acima, especialmente com --no-ff ) é aos trancos e barrancos superou as outras abordagens (por exemplo, a confusão da subversão de ter estruturas de diretório paralelas para ramificações).

it means an endless, unnavigable tangle of parallel development history.

Infinito, paralelo - sim.

Inacessível, emaranhado - não.

But I'm not seeing what alternative we have.

Por que não funciona como o inventor de git , seus colegas e o resto do mundo todos os dias?

Do we have any option here besides constantly merging sidebranches into master with merge-commits? Or, is there a reason that constantly using merge-commits is not as bad as I fear?

Nenhuma outra opção; não tão ruim.

    
por 20.12.2016 / 23:09
fonte
1

Pessoalmente, prefiro fazer meu desenvolvimento em uma bifurcação e, em seguida, extrair solicitações para mesclar no repositório principal.

Isso significa que, se eu quiser rebaixar minhas alterações no topo do master ou esmagar alguns commits do WIP, eu posso fazer isso totalmente. Ou posso apenas pedir que toda a minha história seja incorporada também.

O que eu gosto fazer é fazer o meu desenvolvimento em um ramo, mas freqüentemente rebase contra master / dev. Dessa forma, eu obtenho as alterações mais recentes do master sem ter um monte de commits de mesclagem na ramificação my , ou tendo que lidar com uma carga inteira de conflitos de mesclagem quando é hora de mesclar para master.

Para responder explicitamente à sua pergunta:

Do we have any option here besides constantly merging sidebranches into master with merge-commits?

Sim - você pode mesclá-los uma vez por ramificação (quando o recurso ou correção está "completo") ou se você não gosta de ter os commits de merge no seu histórico, você pode simplesmente fazer uma fusão rápida no master depois de fazer um rebase final.

    
por 19.12.2016 / 22:23
fonte
-1

O controle de revisão é um lixo descartável.

O problema é que o trabalho em andamento no ramo de recursos pode conter muitos "vamos tentar isto ... não que não funcionou, vamos substituí-lo por isso" e todos os commits exceto o final "que "acabam poluindo a história inutilmente.

Por fim, o histórico deve ser mantido (alguns deles podem ser úteis no futuro), mas apenas uma "cópia limpa" deve ser mesclada.

Com o Git, isso pode ser feito ramificando o ramo de feição primeiro (para manter todo o histórico), então (interativamente) rebaixando o ramo do ramo de feição a partir do mestre e depois mesclando o ramo rebaseado.

    
por 19.12.2016 / 20:41
fonte