In git, come fare il controllo delle versioni per una dozzina di librerie tutte funzionate in parallelo

11

Stiamo facendo progetti, ma riutilizziamo molto codice tra i progetti e abbiamo molte librerie che contengono il nostro codice comune. Mentre implementiamo nuovi progetti, troviamo più modi per calcolare il codice comune e inserirlo nelle librerie. Le librerie dipendono l'una dall'altra e i progetti dipendono dalle librerie. Ogni progetto e tutte le librerie utilizzate in quel progetto devono utilizzare la stessa versione di tutte le librerie a cui si riferiscono. Se rilasciamo un software, dovremo risolvere bug e magari aggiungere nuove funzionalità per molti anni, a volte per decenni. Abbiamo circa una dozzina di librerie, i cambiamenti spesso vengono tagliati su più di due e diversi team lavorano su più progetti in parallelo, apportando modifiche simultanee a tutte queste librerie.

Recentemente siamo passati a git e abbiamo creato repository per ogni libreria e ogni progetto. Usiamo lo stash come un repository comune, facciamo cose nuove sui branch delle feature, quindi eseguiamo le richieste pull e le uniamo solo dopo la revisione.

Molti dei problemi che dobbiamo affrontare nei progetti ci impongono di apportare modifiche a diverse librerie e al codice specifico del progetto. Questi includono spesso modifiche alle interfacce della libreria, alcune delle quali sono incompatibili. (Se pensate che questo suoni sia di pesce: ci interfacciamo con l'hardware e nascondiamo hardware specifico dietro interfacce generiche: quasi ogni volta che integriamo l'hardware di altri fornitori ci imbattiamo in casi che le nostre attuali interfacce non hanno previsto, e quindi dobbiamo perfezionarli.) Per esempio, immagina un progetto P1 usando le librerie L1 , L2 e L3 . L1 usa anche L2 e L3 , e L2 usa anche L3 . Il grafico delle dipendenze ha il seguente aspetto:

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

Ora immagina che una funzione per questo progetto richieda modifiche in P1 e L3 che cambiano l'interfaccia di L3 . Ora aggiungi i progetti P2 e P3 nel mix, che si riferiscono anche a queste librerie. Non possiamo permetterci di passare tutti alla nuova interfaccia, eseguire tutti i test e implementare il nuovo software. Quindi qual è l'alternativa?

  1. implementa la nuova interfaccia in L3
  2. fai una richiesta di pull per L3 e attendi la revisione
  3. unisci la modifica
  4. crea una nuova versione di L3
  5. inizia a lavorare sulla funzione in P1 facendo riferimento alla nuova versione di L3 , quindi implementa la funzione sul ramo di funzione di P1
  6. fai una richiesta di pull, fai revisionare e unire

(Ho appena notato che ho dimenticato di passare L1 e L2 alla nuova versione e non so nemmeno dove posizionarlo, perché dovrebbe essere fatto in parallelo con P1 ...)

Questo è un processo noioso, soggetto a errori e molto lungo per implementare questa funzione, richiede revisioni indipendenti (il che rende molto più difficile la revisione), non è affatto ridimensionabile e rischia di farci uscire affari perché siamo così impantanati nel processo che non otteniamo mai nulla.

Ma come impiegare la ramificazione e il tagging per creare un processo che ci permetta di implementare nuove funzionalità in nuovi progetti senza troppi sovraccarichi?

    
posta sbi 10.06.2015 - 17:10
fonte

6 risposte

5

Tipo di mettere l'ovvio qui, ma forse vale la pena menzionarlo.

Solitamente, i repository git sono personalizzati per lib / project perché tendono ad essere indipendenti. Aggiorna il tuo progetto e non ti importa del resto. Altri progetti che dipendono da esso aggiorneranno semplicemente la loro lib ogni qualvolta lo ritengano opportuno.

Tuttavia, il tuo caso sembra molto dipendente dai componenti correlati, quindi una caratteristica di solito ne influenza molti. E il tutto deve essere confezionato come un pacchetto. Dal momento che implementare una funzionalità / modifica / bug spesso richiede l'adattamento di molte librerie / progetti diversi contemporaneamente, forse ha senso inserirli tutti nello stesso repository.

Ci sono forti vantaggi / svantaggi a questo.

I vantaggi:

  • Tracciabilità: il ramo mostra tutto cambiato in ogni progetto / lib relativo a questa funzionalità / bug.
  • Raggruppamento: scegli un tag e otterrai tutte le risorse giuste.

Svantaggi:

  • Unione: ... a volte è già difficile con un singolo progetto. Con team diversi che lavorano su filiali condivise, essere pronti a prepararsi per l'impatto.
  • Fattore "oops" pericoloso: se un dipendente incasina il repository facendo qualche errore, potrebbe avere un impatto su tutti progetti & squadre.

Spetta a te sapere se il prezzo vale il beneficio.

EDIT:

Funzionerebbe così:

  • La caratteristica X deve essere implementata
  • Crea ramo feature_x
  • Tutti gli sviluppatori coinvolti lavorano su questo ramo e lavorano in modo paralizzante su di esso, probabilmente in directory dedicate relative al loro progetto / lib
  • Una volta terminato, esaminalo, testalo, impacchettalo, qualunque sia
  • Uniscilo nel master ... e questa potrebbe essere la parte difficile dal momento che nel frattempo potrebbe essere stato aggiunto anche feature_y e feature_z . Diventa una fusione di "cross-team". Questo è il motivo per cui è un grave inconveniente.

solo per la cronaca: penso che nella maggior parte dei casi sia una cattiva idea e dovrebbe essere fatto con cautela perché lo svantaggio di fusione è solitamente più alto di quello che si ottiene attraverso la gestione delle dipendenze / il corretto tracciamento delle caratteristiche.

    
risposta data 19.06.2015 - 13:46
fonte
4

La soluzione che stai cercando è uno strumento di gestione delle dipendenze in coordinamento con i sottomodelli git

Strumenti come:

  • Maven
  • Ant
  • Compositore

Puoi utilizzare questi strumenti per definire le dipendenze di un progetto.

Puoi richiedere che un submodulo sia almeno versione > 2.x.x o denota un intervallo di versioni compatibili = 2.2. * o meno di una versione specifica < 2.2.3

Ogni volta che rilasci una nuova versione di uno dei pacchetti puoi taggarla con il numero di versione, in questo modo puoi inserire la versione specifica del codice in tutti gli altri progetti

    
risposta data 10.06.2015 - 20:01
fonte
0

moduli

Dovresti provare a utilizzare i sottomoduli git , come suggerito in un commento.

Quando il progetto P1 si riferisce ai tre sottomoduli L1 , L2 e L3 , in realtà memorizza un riferimento a commit specifici in tutti e tre i repository: quelli sono le versioni di lavoro di ogni libreria per quel progetto .

Quindi più progetti possono lavorare con più sottomoduli: P1 potrebbe riferirsi alla vecchia versione della libreria L1 mentre il progetto P2 ha usato la nuova versione.

Che cosa accade quando invii una nuova versione di L3 ?

  • implementa una nuova interfaccia in L3
  • commit, test , fai richiesta di pull, rivedi, unisci, ... (non puoi evitarlo)
  • assicurati che L2 funzioni con L3 , commit, ...
  • assicurati che L1 funzioni con il nuovo L2 , ...
  • assicurati che P1 funzioni con le nuove versioni di tutte le librerie:
    • all'interno della copia di lavoro locale di P1 di L1 , L2 e L3 , recupera le modifiche che ti interessano.
    • commit changes, git add L1 L2 L3 per confermare il nuovo riferimento ai moduli
    • rich richiesta per P1 , prova, revisione, richiesta pull, unione ...

Metodologia

This is a tedious, error-prone, and very long process to implement this feature, it requires to independent reviews (which makes it much harder to review), does not scale at all, and is likely to put us out of business because we get so bogged down in process we never get anything done.

Sì, richiede recensioni indipendenti, perché cambi:

  • la libreria
  • librerie che dipendono da questo
  • progetti che dipendono da più librerie

Saresti messo fuori mercato perché offri merda? (Forse no, in realtà). In caso affermativo, è necessario eseguire test e rivedere le modifiche.

Con gli strumenti git appropriati (anche gitk ), puoi facilmente vedere quali versioni delle librerie usano ogni progetto, e puoi aggiornarle indipendentemente in base alle tue esigenze. I sottomoduli sono perfetti per la tua situazione e non rallenteranno il tuo processo.

Forse puoi trovare un modo per automatizzare parte di questo processo, ma la maggior parte dei passaggi sopra richiede cervello umano. Il modo più efficace per ridurre il tempo sarebbe quello di garantire che le tue librerie e i tuoi progetti siano facili per evolvere. Se la base di codice è in grado di gestire con garbo i nuovi requisiti, le revisioni del codice saranno più semplici e impiegheranno un po 'di tempo.

(Modifica) un'altra cosa che potrebbe aiutarti è raggruppare le revisioni del codice correlate. Apporti tutte le modifiche e attendi fino a quando non hai propagato tali modifiche a tutte le librerie e ai progetti che li utilizzano prima di applicare le richieste pull (o prima di prenderti cura di esse). Finisci per fare una recensione più grande per l'intera catena di dipendenze. Forse questo può aiutarti a risparmiare tempo se ogni modifica locale è piccola.

    
risposta data 19.06.2015 - 16:19
fonte
0

Quindi quello che ho capito è che tu per P1 vuoi cambiare l'interfaccia L3 ma vuoi che gli altri P2 e P3 che dipendono dall'interfaccia L3 cambino subito. Questo è un tipico caso di retrocompatibilità. C'è un bell'articolo su questo preservazione della compatibilità con le versioni precedenti

Ci sono diversi modi per risolverlo:

  • Devi creare nuove interfacce ogni volta che possono estendere le vecchie interfacce.

o

  • Se vuoi ritirare la vecchia interfaccia dopo un po 'di tempo puoi avere diverse versioni di interfacce e una volta che tutti i progetti dipendenti si spostano rimuovi le interfacce precedenti.
risposta data 19.06.2015 - 18:44
fonte
0

Se ho capito bene il tuo problema:

  • hai 4 moduli correlati, P1 e L1 a L3
  • devi apportare una modifica a P1 che alla fine influirà da L1 a L3
  • conta come un fallimento del processo se devi cambiare tutti e 4 insieme
  • conta come un fallimento del processo se devi cambiarli tutti 1 per 1.
  • conta come un fallimento del processo se devi identificare in anticipo i blocchi in cui devono essere apportate modifiche.

Quindi l'obiettivo è che puoi fare P1 e L1 in un colpo solo, e poi un mese dopo fare L2 e L3 in un altro.

Nel mondo Java, questo è banale e forse il metodo predefinito per lavorare:

  • tutto va in un repository senza l'uso rilevante della ramificazione
  • I moduli
  • sono compilati + collegati insieme da Maven in base ai numeri di versione, non dal fatto che si trovano tutti nello stesso albero di directory.

Quindi puoi avere il codice sul tuo disco locale per L3 che non si compilerebbe se si stava compilando contro la copia del P1 nell'altra directory sul tuo disco; per fortuna non sta facendo così. Java può farlo direttamente perché compila / collega i racconti con i file jar compilati, non con il codice sorgente.

Non sono a conoscenza di una soluzione preesistente ampiamente utilizzata per questo problema per il mondo C / C ++, e immagino che non vogliate cambiare lingua. Ma qualcosa potrebbe essere facilmente hackerato insieme a creare file che facevano la cosa equivalente:

  • librerie installate + intestazioni a directory conosciute con numeri di versione incorporati
  • modificato i percorsi del compilatore per modulo nella directory per i numeri di versione appropriati

Potresti anche utilizzare il supporto C / C ++ in maven , anche se la maggior parte degli sviluppatori C ti guarderebbe in modo strano se l'hai fatto ...

    
risposta data 19.06.2015 - 22:51
fonte
-1

Esiste una soluzione semplice: tagliare i rami di rilascio su tutto il repository, unire tutte le correzioni a tutte le versioni attivamente spedite (è facile in chiaro dovrebbe essere possibile in git).

Tutte le alternative creeranno un orribile disordine nel tempo e con la crescita del progetto.

    
risposta data 18.06.2015 - 14:48
fonte

Leggi altre domande sui tag