Come posso affrontare una fusione complicata


25

Ecco l'accordo, sono entrato in una nuova società e mi è stato chiesto di finire il lavoro in una filiale che non è stata toccata per quasi un anno. Nel frattempo, il ramo principale è cresciuto a un ritmo costante. Idealmente, vorrei unire tutte le modifiche dal ramo principale al ramo delle caratteristiche e continuare il lavoro da lì, ma non sono troppo sicuro di come affrontarlo.

Come eseguo questa unione in modo sicuro preservando importanti modifiche su entrambi i lati del ramo?


Grazie a tutti per il fantastico feedback. Proverò a git-imerge e se questo risulta essere disordinato userò un nuovo approccio di filiale!
Vlad Spreys,

Qualcuno può spiegare perché non possiamo usare git cherry-pickqui?
Santosh Kumar,

1. Preghiere. 2. rebase. 3. test. 4. unisci.
AK_17

1
Preferisco ribellarmi in questa situazione perché andrà a impegnarsi per impegno. Ti permetterà anche di schiacciare la nuova funzionalità prima di rendere disponibile il codice unito. YMMV.
Stephen,

Risposte:


27

Alla base, come combinare due parti di codice (possibilmente non compatibili) è un problema di sviluppo , non un problema di controllo della versione . Il comando Unisci Git può essere d'aiuto in questo processo, ma dipende dalla forma del problema.

Confrontare entrambe le versioni con la base ha prima senso. Questo ti darà un'idea della migliore strategia per portarlo avanti. Il tuo approccio potrebbe essere diverso in base alla natura e alla sovrapposizione delle modifiche in ciascun ramo.

Immagina lo scenario ideale: scopriresti che il ramo principale e il ramo della caratteristica hanno modificato ciascuno di essi solo parti reciprocamente esclusive del codice, in modo da poter semplicemente eseguire il commit di tutte le modifiche ed essere pronto.

Certo, questo non sarà quasi certamente il caso, ma la domanda è: quanto lontano sarà da questo scenario ideale? cioè quanto sono mescolati i cambiamenti?

Inoltre, quanto era maturo il vecchio ramo delle caratteristiche? Era in buone condizioni, oppure no (o sconosciuto)? Quanta parte della funzionalità è stata completata?

Se il codice pertinente nel ramo principale è cambiato molto nell'ultimo anno o la funzionalità non è in uno stato molto maturo, potrei prendere in considerazione la possibilità di creare un nuovo fork dell'ultimo e di incorporare manualmente la vecchia funzionalità di nuovo. Ciò ti consentirà di adottare un approccio incrementale per farlo funzionare.

Se si esegue un'unione disordinata di un sacco di codice e non funziona, sarà abbastanza difficile eseguire il debug. Se la filiale principale è cambiata molto nell'ultimo anno, potrebbero essere necessarie importanti modifiche al design della funzionalità per farlo funzionare. Non sarebbe opportuno apportare queste modifiche tramite "risoluzione dei conflitti", poiché ciò richiederebbe apportare tutte le modifiche contemporaneamente e sperando che funzioni. Questo problema sarebbe aggravato dalla possibilità di bug nel vecchio ramo parzialmente finito.


+1 "Potrei prendere in considerazione l'idea di creare un nuovo fork dell'ultimo e di incorporare di nuovo manualmente la vecchia funzione"
mika,

1
La prima domanda chiave è: costruisce il vecchio ramo di funzionalità? Funziona? Altrimenti sarà molto difficile testare la tua unione.
Móż

22

Nella mia esperienza git limitata posso dire che a volte è più veloce riavviare nuovamente il ramo di funzionalità se il master è andato troppo lontano dal punto di distacco.

Unire due rami senza conoscere la storia dietro il codice (dato che hai appena aderito al progetto) è davvero difficile, e scommetto che anche uno sviluppatore che ha seguito il progetto dall'inizio avrebbe probabilmente commesso alcuni errori nella fusione.

Questo ovviamente ha senso se il ramo della funzione non è enorme, ma potresti semplicemente tenere aperto il ramo della vecchia funzione , ramificarlo nuovamente dal master e reintrodurre manualmente le modifiche che compongono quella funzione. So che è l'approccio più manuale, ma ti consente di avere il controllo completo in caso di codice mancante o spostato.

Associare la programmazione a un senior in questo caso sarebbe lo scenario migliore, aiutandoti a conoscere meglio il codice.
Potrebbe anche rivelarsi più veloce, se si tiene conto della fusione dei conflitti e dei tempi di prova!

Ho pensato che almeno provare a fare una fusione sia ovviamente la cosa migliore da fare. Se ciò fallisce o risulta troppo difficile, allora prova la raccolta delle ciliegie, se ciò va storto vai nel modo manuale.


2
Sicuramente non l'approccio corretto.
Andy,

12
no, questo è l'approccio corretto - se hai un ramo antico e non conosci il codice, provare a unire non avrà molto successo e sarà molto rischioso. Determinare le modifiche che erano state originariamente apportate, indagare se fossero persino rilevanti per il nuovo codice e quindi applicarle per dare un senso è il modo per farlo. Questo è un approccio manuale, ma in queste circostanze, l'unico sicuro da adottare. Certo, prima proverei comunque un'unione, solo per vedere cosa succede, e controllerei il registro per vedere anche quanti cambiamenti si sono verificati sul ramo - potrebbe essere banale.
gbjbaanb,

10
@DavidPacker: non credo che GavianoGrifoni suggerisca di gettare in mare tutto il lavoro. Suggerisce di trasferire manualmente le modifiche dal vecchio ramo all'attuale linea di sviluppo, in modo graduale. Ciò getterà via la vecchia storia , non di più.
Doc Brown,

3
@DavidPacker 1 °: la filiale è un anno obsoleto, 2 °: il ragazzo incaricato di finire non conosce affatto il codice. Dati questi 2 fattori, una riapplicazione manuale è l'unico modo realistico per affrontare l'attività. Nessuno sta suggerendo una semplice copia-incolla della revisione del suggerimento del vecchio ramo.
gbjbaanb,

5
@DavidPacker: Unire i conflitti può diventare malvagio - se devi risolverne 500 contemporaneamente prima di riportare il programma in uno stato compilabile e testabile. Questo è il tipo di situazione che l'OP si aspetta qui. Se ritieni che sia possibile usare git in modo efficiente per evitare questa situazione "tutto-o-niente", perché non modifichi la tua risposta e dì al PO come si può ottenere?
Doc Brown,

16

git-imerge è progettato esattamente per questo scopo. È uno strumento git che fornisce un metodo per l' unione incrementale . Unendo in modo incrementale, devi solo gestire le collisioni tra due versioni, mai più. Inoltre, è possibile eseguire automaticamente un numero molto maggiore di fusioni poiché le singole modifiche sono minori.


6

Cercare di fondere la testa della linea principale su un ramo stantio di un anno può essere un esercizio di frustrazioni e approfondire l'ammaccatura sulla scrivania con la fronte.

La linea principale non è arrivata dove si trova in un colpo solo nel corso dei mesi. Aveva anche sviluppo e versioni. Cercare di aggiornare tutto in un'unica fusione monolitica può essere travolgente.

Invece, inizia unendo dalla prima funzione unisci nuovamente alla linea principale dopo la divisione del ramo stantio. Fai funzionare quella fusione. Quindi la funzione successiva si unisce. E così via. Molte di queste fusioni di funzionalità si fonderanno senza conflitti. È ancora importante assicurarsi che l'attuale funzionalità del ramo stantio rimanga compatibile con la direzione in cui è andata la linea principale.

Potresti voler diramare dal capo del ramo stantio per il ruolo di fusione in altre modifiche. Si tratta più di assicurarsi che gli impegni e la storia quando qualcuno lo guarda sia chiaro e comunichi qual è il ruolo e la politica di ciascun ramo. Il ramo stantio era un ramo caratteristica. Quello da cui stai lavorando è un ramo di accumulazione e riconciliazione.

Molto di questo sarà più facile se la vecchia funzione o i rami di rilascio esistono ancora là fuori e sono facilmente accessibili (alcuni posti hanno una politica di ripulire i nomi dei rami che sono più vecchi di qualche data in modo che l'elenco dei rami non sia travolgente ).

La cosa importante in tutto questo è assicurarsi di testare e correggere dopo aver unito correttamente ogni parte della cronologia della linea principale. Anche se qualcosa può fondersi senza conflitti, ciò significa che il codice non è in conflitto. Se il modo in cui è stato eseguito l'accesso alla funzione non aggiornata è stato deprecato o rimosso, potrebbe essere necessario apportare correzioni dopo l'unione corretta.

A parte questo, funziona anche con altri sistemi di controllo di versione. Occasionalmente ho dovuto unire un gruppo specifico di commit svn in un ramo (cherry picking) per una funzione, correggere il ramo in modo che funzioni con quella funzione, quindi unire il gruppo successivo di commit svn piuttosto che semplicemente fare un svn all'ingrosso fondersi.

Mentre uno può fare una scelta di git cherry qui, e consente di portare impegni specifici , questo ha alcuni svantaggi che possono complicare il processo. Il cherry pick non mostrerà informazioni sul commit da cui hai scelto (puoi aggiungerlo al messaggio di commit). Ciò rende più difficile il monitoraggio degli commit nella storia.

Inoltre, significa che non hai intenzione di riprodurre efficacemente il master sul ramo stantio - sceglierai probabilmente funzioni incomplete - e quelle funzionalità potrebbero essere giocate fuori servizio.

Il motivo principale per cui si dovrebbero unire i commit storici da padroneggiare nel ramo stantio è quello di essere in grado di mantenere il, chiamiamolo "storia futura" del ramo stantio in uno stato su cui si può ragionare. Puoi vedere chiaramente le fusioni della storia sul ramo stantio e le correzioni per reintegrare la funzionalità. Le funzionalità vengono aggiunte nello stesso ordine in cui erano da padroneggiare. E quando hai finito, e infine fai l'unione dalla testa del maestro al ramo stantio, sai che tutto è stato unito e non ti manca alcun commit.


+1 Questa è un'interessante soluzione alternativa potenziale che utilizza l'unione per incorporare grandi cambiamenti. Tuttavia, posso vedere un rovescio della medaglia: supponiamo di avere versioni principali ABCDE e di voler incorporare il ramo delle funzionalità A1 in E. Potrebbero esserci molti sforzi sprecati nel fondere il codice in BC e D. Ad esempio, se D a E fosse un grande cambiamento di progettazione che ha reso irrilevanti i cambiamenti incrementali in B, C e D? Inoltre, dipende ancora da quanto era matura la funzionalità in primo luogo. Un potenziale approccio utile, ma la sua adeguatezza deve essere considerata prima di iniziare.

1

Passaggio 1. Informazioni sul codice, analisi della sua architettura e delle modifiche apportate su entrambi i rami dall'ultimo antenato comune.

Passaggio 2. Se la funzione appare ampiamente indipendente e tocca principalmente diverse aree di codice, unisci, correggi conflitti, verifica, correggi ecc. Il percorso è felice, sei praticamente pronto. Altrimenti vai al passaggio 3

Passaggio 3. Analizzare le aree di conflitto, comprendere l'impatto funzionale e le ragioni in dettaglio. Potrebbero esserci facilmente conflitti nei requisiti aziendali che vengono alla luce qui. Discutere con BA, altri sviluppatori a seconda dei casi. Scopri la complessità della risoluzione dell'interferenza.

Passaggio 4. Alla luce di quanto sopra, decidi se puntare a unire / selezionare ciliegia / anche tagliare e incollare solo quelle parti che non sono in conflitto e riscrivere i pezzi in conflitto, O se riscrivere l'intera funzionalità da zero .


0

1. Passa al ramo utilizzato come ramo sviluppatore / release principale.

Questo è il ramo che contiene le ultime modifiche al sistema. Può essere master, core, dev, dipende dalla società. Nel tuo caso è probabilmente masterdirettamente.

git checkout master
git pull

Tirare per assicurarsi di avere acquistato l'ultima versione del ramo di sviluppo principale.

2. Fai il checkout e tira il ramo che contiene il lavoro che dovresti finire.

Tiri per assicurarti di avere davvero gli ultimi contenuti del ramo. Effettuando il check out direttamente, senza prima crearlo localmente, ti assicuri di non avere i nuovi contenuti master(o rispettivamente il ramo di sviluppo principale) al suo interno.

git checkout <name of the obsolete branch>
git pull origin <name of the obsolete branch>

3. Unire il ramo di sviluppo principale al ramo obsoleto.

Prima di eseguire il comando seguente, assicurarsi, digitando git brancho git statusche si è sul ramo obsoleto.

git merge master

Il git mergecomando tenterà di unire i contenuti dal ramo specificato, in questo caso master, al ramo in cui ci si trova attualmente.

L'enfasi su proverà a farlo . Potrebbero esserci conflitti di unione, che dovranno essere risolti solo da te e da te.

4. Correggi i conflitti di unione, esegui il commit e invia la correzione del conflitto

Dopo aver corretto il conflitto di unione in tutti i file in cui è presente, mettere in scena, eseguire il commit e inviare la risoluzione del conflitto origin.

git add .
git commit -m "fixed the merge conflict from the past year to update the branch"
git push

In genere è possibile chiamare git add .per mettere in scena tutti i file per il commit. Quando si affrontano i conflitti di unione, si desidera aggiornare tutti i file necessari.

Nota aggiuntiva

Risolvere i conflitti di unione può essere un lavoro noioso. Soprattutto se sei nuovo in un'azienda. Potresti non avere nemmeno la conoscenza adeguata per risolvere tutti i conflitti di unione da solo, ancora.

Prenditi il ​​tuo tempo per ispezionare attentamente tutti i conflitti che si sono verificati e risolverli in modo appropriato, prima di continuare il tuo lavoro.


Può succedere così, inizi a lavorare su un ramo di un anno, fondi lo stato di sviluppo corrente in esso e non avrai alcun conflitto di unione.

Ciò accade quando anche se il sistema è cambiato molto durante l'anno, nessuno ha toccato i file che sono stati effettivamente modificati nel ramo di un anno.


6
# 4 è potenzialmente il problema. Se ci sono state molte modifiche nell'ultimo anno, potrebbero essere necessarie importanti modifiche alla vecchia funzionalità. Per farlo, è necessario apportare importanti modifiche al codice tutto in una volta e sperare che funzioni, il che non è una buona pratica di sviluppo. Inoltre chi sa quale fosse lo stato della caratteristica incompiuta? Potresti finire con un enorme casino di codice non funzionante, e chissà se era dovuto a problemi con l'originale o alle modifiche che hai apportato?

David, finché funziona questo approccio standard, va bene, e l'OP dovrebbe provarlo prima. Ma c'è sicuramente il rischio di ottenere troppi conflitti di unione nella situazione descritta per gestirli in questo modo "tutto o niente".
Doc Brown,

@ dan1111 Che ci siano conflitti di unione è del tutto OK, e infatti attraversarli è la strada da percorrere. Dal momento che il ramo è rimasto intatto per un anno, puoi essere abbastanza sicuro che non è niente di così importante e non influenzerà gran parte del sistema. Quindi, anche se il ramo è indietro di un anno, puoi ottenere da 2 a nessuno per unire i conflitti.
Andy,

4
L'ipotesi che questo ramo non sia importante è ingiustificata. Potrebbe essere stato un cambiamento di progettazione fondamentale che è stato abbandonato e ora viene ripreso. Potrebbe essere qualsiasi cosa. Hai ragione sul fatto che potrebbe essere una questione semplice e potrebbero esserci pochi o nessun conflitto, nel qual caso la tua risposta sarebbe corretta. Ma questa non è l'unica possibilità.

@ dan1111 Se qualcuno non ha toccato un posto di funzione in un ramo separato per un anno, non rifletterà molto sui cambiamenti del sistema. Questo deriva dalla mia esperienza con filiali obsolete (di età superiore ai 6 mesi).
Andy,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.