No git, como fazer o controle de versão para uma dúzia de bibliotecas, todas trabalhadas em paralelo


11

Estamos desenvolvendo projetos, mas reutilizamos muito código entre os projetos e temos muitas bibliotecas que contêm nosso código comum. À medida que implementamos novos projetos, encontramos mais maneiras de fatorar o código comum e colocá-lo nas bibliotecas. As bibliotecas dependem umas das outras e os projetos dependem das bibliotecas. Cada projeto e todas as bibliotecas usadas nesse projeto precisam usar a mesma versão de todas as bibliotecas às quais estão se referindo. Se lançarmos um software, teremos que corrigir bugs e talvez adicionar novos recursos por muitos anos, às vezes por décadas. Temos cerca de uma dúzia de bibliotecas, as mudanças geralmente abrangem mais de duas e várias equipes trabalham em vários projetos em paralelo, fazendo alterações simultâneas em todas essas bibliotecas.

Recentemente, mudamos para o git e configuramos repositórios para cada biblioteca e cada projeto. Usamos o stash como um repositório comum, fazemos novas coisas nas ramificações de recursos, fazemos solicitações pull e as mesclamos somente após a revisão.

Muitos dos problemas com os quais lidamos nos projetos exigem que façamos alterações em várias bibliotecas e no código específico do projeto. Isso geralmente inclui alterações nas interfaces da biblioteca, algumas das quais são incompatíveis. (Se você acha que isso soa suspeito: fazemos interface com hardware e ocultamos hardware específico por trás de interfaces genéricas. Quase sempre que integramos o hardware de algum outro fornecedor, nos deparamos com casos que nossas interfaces atuais não antecipavam e, portanto, precisamos refiná-las.) exemplo, imagine um projeto P1usando as bibliotecas L1, L2e L3. L1também usa L2e L3, e L2usa L3também. O gráfico de dependência é assim:

   <-------L1<--+
P1 <----+  ^    |
   <-+  |  |    |
     |  +--L2   |
     |     ^    |
     |     |    |
     +-----L3---+

Agora imagine que um recurso para este projeto exija alterações P1e as L3quais alterem a interface do L3. Agora adicione projetos P2e P3à mistura, que também se referem a essas bibliotecas. Não podemos nos permitir mudar todos eles para a nova interface, executar todos os testes e implantar o novo software. Então, qual é a alternativa?

  1. implementar a nova interface em L3
  2. faça uma solicitação de L3recebimento e aguarde a revisão
  3. mesclar a mudança
  4. crie uma nova versão do L3
  5. comece a trabalhar no recurso P1, fazendo com que ele se refira ao L3novo lançamento, e implemente o recurso na P1ramificação do recurso
  6. faça uma solicitação pull, revise-a e mesclada

(Eu notei que eu esqueci de mudar L1e L2para a nova versão. E eu nem sei onde enfiar isso no, porque ele teria que ser feito em paralelo com P1...)

Esse é um processo tedioso, propenso a erros e muito longo para implementar esse recurso, requer análises independentes (o que torna muito mais difícil a revisão), não é escalável e provavelmente nos afastará dos negócios porque ficamos tão atolados no processo que nunca fazemos nada.

Mas como empregamos ramificação e marcação para criar um processo que nos permita implementar novos recursos em novos projetos sem muita sobrecarga?


1
Alterar suas ferramentas não deve afetar muito os processos que você possui. Então, como você estava lidando com esse problema antes de mudar para o git?
Bart van Ingen Schenau

É possível simplesmente adicionar um novo método à interface sem interromper o existente quando muitas bibliotecas dependem dele? Normalmente, essa não é a melhor idéia, mas pelo menos isso permitiria que você "continuasse" implementando o novo recurso e poderá desaprovar adequadamente o método antigo sempre que houver um momento livre. Ou essas interfaces são muito stateful para que "interfaces paralelas" assim funcionem?
Ixrec

1
@Ixrec: Foi-me dito que "este não é o caminho" para fazer as coisas. Todo mundo usa repositórios individuais para projetos individuais, então foi decidido fazer isso também.
SBI

2
Eu argumentaria que eles não são projetos separados, se frequentemente precisam ser alterados em conjunto. Os limites entre "projetos" devem sempre ter algum tipo de garantia de compatibilidade com versões anteriores a longo prazo.
Ixrec

1
@Ixrec: integrar mais hardware é semelhante à portabilidade de código para mais plataformas: quanto mais você faz isso, menos precisa mudar para outro hardware / plataforma. Portanto, a longo prazo, o código se estabilizará. No entanto, neste momento, precisaremos encontrar um processo que nos permita permanecer no mercado por tempo suficiente para chegar lá.
SBI

Respostas:


5

Tipo de colocar o óbvio aqui, mas talvez valha a pena mencionar.

Geralmente, os repositórios git são personalizados por lib / projeto porque tendem a ser independentes. Você atualiza seu projeto e não se importa com o resto. Outros projetos, dependendo dele, simplesmente atualizarão sua biblioteca sempre que entenderem.

No entanto, seu caso parece altamente dependente de componentes correlacionados, de modo que um recurso geralmente afeta muitos deles. E o todo deve ser empacotado como um pacote. Como a implementação de um recurso / alteração / bug geralmente requer adaptação de diferentes bibliotecas / projetos ao mesmo tempo, talvez faça sentido colocá-los todos no mesmo repositório.

Existem fortes vantagens / desvantagens nisso.

Vantagens:

  • Rastreabilidade: o ramo mostra tudo que foi alterado em cada projeto / lib relacionado a esse recurso / bug.
  • Pacote: basta escolher uma tag e você terá todas as fontes corretas.

Desvantagens:

  • Mesclando: ... às vezes já é difícil com um único projeto. Com equipes diferentes trabalhando em filiais compartilhadas, esteja pronto para se preparar para o impacto.
  • Fator perigoso de "ops": se um funcionário atrapalha o repositório cometendo algum erro, isso pode afetar todos os projetos e equipes.

Cabe a você saber se o preço vale o benefício.

EDITAR:

Funcionaria assim:

  • O recurso X deve ser implementado
  • Criar ramo feature_x
  • Todos os desenvolvedores envolvidos trabalham neste ramo e trabalham paralelamente nele, provavelmente em diretórios dedicados relacionados ao seu projeto / lib
  • Quando terminar, revise, teste, empacote, o que for
  • Mesclá-lo de volta no mestre ... e essa pode ser a parte mais difícil, pois nesse meio tempo feature_ye também feature_zpode ter sido adicionada. Torna-se uma mesclagem "entre equipes". É por isso que é uma desvantagem séria.

Apenas para constar: acho que essa é, na maioria dos casos, uma péssima idéia e deve ser feita com cautela, porque a desvantagem da mesclagem geralmente é maior do que a obtida com o gerenciamento de dependências / rastreamento adequado de recursos.


Obrigado, de fato estamos atualmente analisando isso. O que eu não entendo à luz disso é como faríamos ramificações com o git. No SVN, ramificações significa copiar uma subárvore (no repositório) para outro lugar (no repositório). Com isso, é fácil criar ramificações de qualquer subárvore em seu repositório. Portanto, se você tiver muitos projetos, poderá ramificar cada um deles individualmente. Existe algo assim no git, ou sempre poderíamos apenas ramificar um repositório inteiro?
SBI

@sbi: você ramificaria o repositório inteiro. Você não pode trabalhar com subárvores em diferentes ramificações, o que meio que anularia o argumento no seu caso. O Git não "copia" nada, mas simplesmente rastreia as alterações no ramo em que você está trabalhando.
dagnelies

Portanto, isso exige que alguém que crie uma ramificação de recurso para uma biblioteca também mescle todas as outras ao mesclar ou refazer a reestruturação. Esta é uma verdadeira desvantagem. (BTW, SVN também só já faz cópias preguiçosos.)
SBI

@sbi: see edit
dagnelies

1
Bem, atualmente a maioria de nós não está confortável. :-/Além disso, mesmo aqueles que são (e que pressionaram pela mudança para o git), não sabem como ajustar nosso processo de desenvolvimento ao git. Suspiro. Receio que sejam alguns meses difíceis, até que as coisas comecem a ficar mais suaves. Obrigado de qualquer maneira, a sua é a resposta mais / única útil até agora.
SBI

4

A solução que você procura é uma ferramenta de gerenciamento de dependência em coordenação com os submódulos git

Ferramentas como:

  • Maven
  • Formiga
  • Compositor

Você pode usar essas ferramentas para definir dependências de um projeto.

Você pode exigir que um submódulo seja pelo menos versão > 2.xx ou denotar um intervalo de versões compatíveis = 2.2. * Ou menor que uma versão específica <2.2.3

Sempre que você libera uma nova versão de um dos pacotes, pode identificá-lo com o número da versão, dessa forma, pode inserir essa versão específica do código em todos os outros projetos.


Mas o gerenciamento de dependências não é nosso problema, isso está resolvido. Atualmente, fazemos alterações regularmente em muitas bibliotecas e precisamos minimizar a sobrecarga da criação de novas versões, mantendo as versões estáveis ​​do projeto. Sua resposta parece não fornecer uma solução para isso.
SBI

@sbi Isso gerenciaria a sobrecarga de criação de novas versões e manutenção de versões estáveis ​​do projeto. Como você pode determinar que o projeto x depende do projeto y versão 2.1.1, é possível criar novas versões do projeto y que não afetariam o projeto x.
Patrick

Mais uma vez, declarar dependências não é nosso problema. Já podemos fazer isso. O problema é como gerenciar as mudanças que atravessam vários projetos / bibliotecas de maneira eficiente. Sua resposta não explica isso.
SBI

@sbi: então, qual é exatamente o seu problema? Você faz as alterações, aumenta a versão, atualiza as dependências quando necessário e pronto. O que você descreveu em seu post inicial é típico da maven & co. coisa. Cada distribuição é baseada em libs versionadas claramente definidas. Como isso poderia ser mais claro?
dagnelies

@arnaud: Os tempos de resposta para esse processo para alterações (atualmente bastante comuns) que cortam três ou mais camadas nos matariam. Eu pensei que minha pergunta descrevesse isso.
SBI

0

Submodules

Você deve tentar git submódulos , como sugerido em um comentário.

Quando projeto P1refere-se aos três sub-módulos L1, L2e L3, na verdade ele armazena uma referência ao commits particulares em todos os três repositórios: esses são os que trabalham versões de cada bibliotecas para esse projeto .

Portanto, vários projetos podem trabalhar com vários submódulos: P1podem se referir à versão antiga da biblioteca L1enquanto o projeto P2usou a nova versão.

O que acontece quando você entrega uma nova versão do L3?

  • implementar nova interface em L3
  • confirmar, testar , fazer solicitação pull, revisar, mesclar, ... (você não pode evitar isso)
  • garantir L2trabalhos com L3, confirmar, ...
  • garantir L1trabalhos com novas L2, ...
  • verifique se P1funciona com as novas versões de todas as bibliotecas:
    • dentro P1da cópia de trabalho local de L1, L2e L3, fetche as mudanças que você está interessado.
    • confirmar alterações, git add L1 L2 L3confirmar a nova referência aos módulos
    • solicitação de extração P1, teste, revisão, solicitação de extração, mesclagem ...

Metodologia

Esse é um processo tedioso, propenso a erros e muito longo para implementar esse recurso, requer análises independentes (o que torna muito mais difícil a revisão), não é escalável e provavelmente nos afastará dos negócios porque ficamos tão atolados no processo que nunca fazemos nada.

Sim, requer análises independentes, porque você altera:

  • a biblioteca
  • bibliotecas que dependem dele
  • projetos que dependem de várias bibliotecas

Você ficaria fora do negócio por entregar lixo? (Talvez não, na verdade). Se sim, você precisa executar testes e revisar as alterações.

Com as ferramentas git apropriadas (par gitk), você pode ver facilmente quais versões das bibliotecas cada projeto usa e pode atualizá-las independentemente, de acordo com suas necessidades. Os submódulos são perfeitos para a sua situação e não atrasam o processo.

Talvez você possa encontrar uma maneira de automatizar parte desse processo, mas a maioria das etapas acima requer cérebros humanos. A maneira mais eficaz de reduzir o tempo seria garantir que suas bibliotecas e projetos sejam fáceis de evoluir. Se a sua base de código puder lidar com novos requisitos normalmente, as revisões de código serão mais simples e levarão pouco do seu tempo.

(Editar) Outra coisa que pode ajudá-lo é agrupar revisões de código relacionadas. Você confirma todas as alterações e aguarda até propagá-las para todas as bibliotecas e projetos que as usam antes de submeter solicitações pull (ou antes de cuidar delas). Você acaba fazendo uma revisão maior para toda a cadeia de dependências. Talvez isso ajude você a economizar tempo se cada alteração local for pequena.


Você descreve como resolver o problema da dependência (que, como eu disse antes, resolvemos) e nega o mesmo problema que estamos tendo. Por que você se incomoda? (FWIW, escrevemos software que as plantas unidades de energia limpa, segura e código completamente revisto é uma característica principal..)
SBI

@sbi O que são submódulos, se não um caso especial de ramificação e marcação? Você acha que os submódulos são sobre gerenciamento de dependências, porque eles também controlam as dependências. Mas claro, por favor, reinvente sub-módulos com tags, se você quiser, não me importo. Não entendo o seu problema: se o código revisado é um recurso essencial, você deve orçar algum tempo para revisões. Você não está atolado, vai o mais rápido possível com as restrições impostas a você.
coredump

Comentários são muito importantes para nós. Essa é uma das razões pelas quais estamos preocupados com o fato de um problema (e suas revisões) ser dividido em várias revisões para várias alterações em vários repositórios. Além disso, não queremos ficar atolados ao administrar até a morte por causa de um problema: preferimos gastar o tempo escrevendo e revisando o código. Re submódulos: até agora, a única coisa que ouvi sobre eles é "não se preocupe, esse não é o caminho do git". Bem, uma vez que nossas necessidades parecem ser tão único, talvez devêssemos rever esta decisão ...
SBI

0

Então, o que eu entendo é que, para P1, você deseja alterar a interface L3, mas deseja que os outros P2 e P3, que dependem da interface L3, sejam alterados imediatamente. Este é um caso típico de compatibilidade com versões anteriores. Há um bom artigo sobre Preservando a compatibilidade com versões anteriores

Existem várias maneiras de resolver isso:

  • Você precisa criar novas interfaces sempre que puder estender as interfaces antigas.

OU

  • Se você deseja desativar a interface antiga após algum tempo, poderá ter várias versões de interfaces e, depois que todos os projetos dependentes forem movidos, você removerá as interfaces mais antigas.

1
Não, a compatibilidade com versões anteriores é garantida por meio de ramificações de lançamento e não é nosso problema. O problema é que estamos sentados em uma base de código que está mudando rapidamente, que agora deseja separar em bibliotecas, apesar do fato de as interfaces ainda estarem na fase em que mudam frequentemente. Eu sei como gerenciar essas bestas no SVN, mas não sei como fazer isso no git sem ser afogado na administração.
SBI

0

Se estou acertando seu problema:

  • você tem 4 módulos inter-relacionados, P1 e L1 a L3
  • você precisa fazer uma alteração em P1 que, em última análise, afetará L1 a L3
  • conta como uma falha no processo se você precisar alterar todos os 4 juntos
  • conta como uma falha no processo se você precisar alterá-los todos 1 por 1.
  • conta como uma falha no processo se você precisar identificar antecipadamente os blocos nos quais as alterações devem ser feitas.

Portanto, o objetivo é fazer P1 e L1 de uma só vez e, um mês depois, L2 e L3 de outra.

No mundo Java, isso é trivial e talvez a maneira padrão de trabalhar:

  • tudo acontece em um repositório sem uso relevante de ramificação
  • Os módulos são compilados + vinculados pelo maven com base nos números de versão, não no fato de que todos estão na mesma árvore de diretórios.

Portanto, você pode ter o código no disco local do L3 que não seria compilado se estivesse compilando com a cópia do P1 no outro diretório do disco; felizmente, não está fazendo isso. O Java pode fazer isso diretamente porque os contos de compilação / vinculação são colocados em arquivos jar compilados, não no código-fonte.

Não conheço uma solução amplamente usada preexistente para esse problema para o mundo C / C ++, e imagino que você não queira mudar de idioma. Mas algo pode ser facilmente hackeado com arquivos make que fazem o equivalente:

  • bibliotecas instaladas + cabeçalhos para diretórios conhecidos com números de versão incorporados
  • caminhos do compilador alterados por módulo para o diretório para os números de versão apropriados

Você pode até usar o suporte a C / C ++ no maven , embora a maioria dos desenvolvedores de C olhe para você de forma estranha, se você o fizer ...


"conta como uma falha no processo se você precisar alterar todos os 4 juntos" . Na verdade não. De fato, foi exatamente isso que fizemos usando o SVN.
S22 /

Nesse caso, acho que não há problema em simplesmente colocar todos os projetos no repositório.
soru

Agora, estamos avaliando a colocação das bibliotecas em apenas dois repositórios. Ainda é mais do que um, mas muito menos do que "um para cada projeto", e as bibliotecas podem muito bem ser divididas em dois grupos. Obrigado pela sua contribuição!
SBI

PS: "Eu imagino que você quase não queira mudar de idioma". Isso é coisa embutida. :)
SBI

-1

Existe uma solução simples: cortar ramificações de lançamento em todo o repositório, mesclar todas as correções para todos os lançamentos enviados ativamente (é fácil em casos claros deve ser possível no git).

Todas as alternativas criarão uma bagunça horrível ao longo do tempo e com o crescimento do projeto.


Você poderia por favor elaborar? Não tenho certeza do que você está sugerindo.
SBI

No caso claro, você define um ponto de ramificação como ramificação base e carimbo de data e hora. Você tem a seguinte hierarquia: ramificação base -> release-development-branch -> private-development branch. Todo o desenvolvimento é feito em ramos particulares e depois mesclado na hierarquia. As ramificações de liberação do cliente são divididas na ramificação de desenvolvimento de liberação. Não estou familiarizado com o git, mas parece a coisa mais próxima de esclarecer os casos entre os sistemas de controle de fonte livre.
zzz777

Uma leitura cuidadosa da minha pergunta deve ter mostrado que temos problemas com a sobrecarga envolvida na propagação de alterações entre repositórios. Isso não tem nada a ver com sua resposta.
SBI

@sbi Me desculpe, eu não entendi sua pergunta. E temo que você enfrente uma bagunça horrível mais cedo ou mais tarde.
Zzz777
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.