Mantendo duas versões de software separadas da mesma base de código no controle de versão

45

Digamos que estou escrevendo duas versões diferentes do mesmo software / programa / aplicativo / script e as armazenando sob controle de versão. A primeira versão é uma versão "Básica" gratuita, enquanto a segunda é uma versão "Premium" paga que pega a base de código da versão gratuita e a expande com alguns recursos de valor agregado extras. Quaisquer novos patches, correções ou recursos precisam encontrar o caminho para as duas versões.

Atualmente, estou pensando em usar mastere developbranches para a principal base de código (versão gratuita) ao lado master-premiume develop-premiumbranches para a versão paga. Quando uma alteração é feita na versão gratuita e mesclada à masterramificação (após testes completos develop, é claro), ela é copiada para a develop-premiumramificação por meio do cherry-pickcomando para mais testes e depois mesclada master-premium.

Esse é o melhor fluxo de trabalho para lidar com essa situação? Existem problemas, advertências ou armadilhas em potencial a serem observados? Existe uma estratégia de ramificação melhor do que a que eu já criei?

Os seus comentários são extremamente apreciados!

PS Isto é para um script PHP armazenado no Git, mas as respostas devem se aplicar a qualquer idioma ou VCS.

Joseph Leedy
fonte

Respostas:

83

Em vez de ter duas versões de código com uma base comum, você deve projetar seu aplicativo de forma a tornar esses recursos premium plugáveis ​​e controlados pela configuração, em vez de diferentes bases de código.

Se você tiver medo de enviar esses recursos premium (desativados pela configuração) com a versão básica, ainda poderá remover esse código em uma etapa final de compilação / empacotamento e apenas ter dois perfis de compilação.

Com esse design, você também pode enviar 5 sabores diferentes e ficar muito flexível, talvez até permitindo que terceiros contribuam.

OliverS
fonte
2
Sim, foi nisso que comecei a pensar ontem à noite antes de ir dormir. Obrigado!
Joseph Leedy
3
O Windows moderno foi projetado dessa maneira, todas as versões têm o mesmo código e recursos desbloqueados, dependendo da chave de licença em uso.
Mooing Duck
39

Eu recomendo fortemente não usar ramos para esse fim. Em geral, você deve considerar ramificações para coisas que serão (ou poderão ser) mescladas novamente mais tarde (ou para ramificações de liberação, onde você eventualmente interromperá o desenvolvimento de uma das ramificações). No seu caso, você nunca mesclará suas versões "básica" e "premium" e elas serão mantidas indefinidamente, portanto, as ramificações não são apropriadas.

Em vez disso, mantenha uma versão comum do código-fonte e use a compilação condicional (por exemplo, #ifdefem C / C ++, não sei qual é o equivalente ao PHP) para incluir ou excluir as seções do código que diferem entre "básico" e "premium".

Parece que o PHP pode não ter um recurso de compilação condicional embutido, então você pode usar o pré-processador C ( cppprovavelmente já o possui) para pré-processar seu código-fonte comum e, a partir disso, produzir um "básico" e um "premium" versão sem as diretivas de pré-processador. Obviamente, se você optar por fazer isso, deverá usar makealgo semelhante para automatizar o processo de execução do pré-processador.

Greg Hewgill
fonte
O que você está dizendo sobre galhos faz todo sentido! Talvez eu pudesse criar um repositório separado contendo apenas o código Premium e usar algum tipo de script de lançamento ou um submódulo para combiná-lo com o código base? Isso pode tornar mais difícil TDD, embora ...
Joseph Leedy
14
Criar outro repositório é ainda pior do que criar ramificações! Você definitivamente deseja escolher uma solução que envolva o mínimo de duplicação de código com versão.
Greg Hewgill
2
O objetivo do segundo repo é abrigar apenas o código extra - e não outra cópia de todo o aplicativo.
precisa
1
Ah, eu vejo, isso seria mais parecido com o modelo "plugin", onde seu código básico tem a capacidade de carregar e executar plugins (se eles existirem). O código do plug-in é separado e fornece os recursos premium.
Greg Hewgill
4
@ Joseph: o uso de dois repositórios só é apropriado se o controle de versão das duas bases de código for quase independente um do outro. Se não for esse o caso, eu recomendo fortemente fazer o que Greg escreveu e manter tudo em um repositório. A única coisa que eu repensaria é o uso do "pré-processador C". Eu acho que um pequeno script escrito na linguagem de sua escolha (o PHP em si é bom, Perl ou Python ainda melhor), que faz uma cópia do seu código sem os recursos premium (alguns marcados com o qual) funcionariam.
Doc Brown
8

Estamos usando 2 projetos separados, o Básico e o Premium, que depende do projeto Básico. Não use braches, eles geralmente são usados ​​para recursos.

Silviu Burcea
fonte
Isso me agrada, porque você pode usar seu script de construção para automatizar a criação de programas básicos e premium.
neontapir
1
em geral, você precisa de três projetos: parte comum, geralmente organizada como uma biblioteca, e partes personalizadas para duas versões diferentes.
Andriy Tylychko
3

Embora a maioria das respostas atuais seja a favor da compilação condicional em vez de ramificações, há um cenário em que há um benefício claro em usar ramificações: se você (agora ou mais tarde) decidir disponibilizar o código fonte da versão básica, incluindo todas as histórico de versões, mas excluindo todos os recursos premium, você pode fazer isso com a abordagem de ramificações, mas não com uma única ramificação e compilação condicional.

Aconselho não escolher a cereja e, em vez disso, mesclar todas as alterações da versão básica para a versão premium. Não deve haver recurso ou correção de bug incluída na versão básica, mas ausente na versão premium. Para tornar as coisas o mais simples possível, verifique se a ramificação premium modifica os arquivos comuns o menos possível. Portanto, a ramificação premium deve conter arquivos adicionais e talvez algumas pequenas modificações para criar instruções. Dessa forma, as alterações da versão básica serão mescladas automaticamente sem causar conflitos.

A resposta de Greg sugeriu que você “considere ramificações para coisas que serão (ou poderão ser) mescladas novamente mais tarde”. Com a abordagem que acabei de descrever este for o caso, a não ser que o ramo final para todos os commits será master-premiumnão master(que é, na verdade master-basic).

Obviamente, os submódulos também seriam uma opção. Depende do seu processo de compilação, mas se você puder transformar a versão premium em um projeto que use a versão básica como um módulo, tudo bem. No entanto, você pode ter mais dificuldade se, em algum momento, decidir escolher recursos da filial premium para a filial básica. Com os submódulos, essa alteração seria representada como duas confirmações distintas, enquanto que com as ramificações, essa seria uma confirmação única para a versão básica, e a próxima mesclagem na versão premium saberia que essas alterações já estão incluídas e não têm para ser mesclado novamente.

MvG
fonte
0

No "hardware", isso é feito com frequência, são sistemas vendidos para controlar a bagunça, desculpe, não me lembro como eles são chamados.

Depois que a lavadora de roupas "de gama média" é enviada, seu código não muda senão para uma correção de bug muito importante, mesmo quando o mesmo código é alterado na lavadora de roupas "low-end" que é enviada alguns meses depois.

Os clientes não esperam receber atualizações para uma máquina de lavar roupa que já trouxeram; um novo modelo também não é enviado a cada poucos meses.

A maioria de nós não vive nesse mundo, o que Greg diz, a menos que você esteja escrevendo um software para máquinas de lavar.

Ian
fonte