Perché molti progetti preferiscono "git rebase" su "git merge"?

64

Uno dei vantaggi dell'utilizzo di un DVCS è il flusso di lavoro edit-commit-merge (su edit-merge-commit spesso applicato da un CVCS). Consentendo ad ogni modifica univoca di essere registrata nel repository indipendente dalle unioni garantisce che DAG rifletta accuratamente il vero pedigree del progetto.

Perché tanti siti web parlano di voler "evitare l'unione di commit"? Non unire il pre-commit o il rebasing post-merge rende più difficile isolare le regressioni, annullare le modifiche precedenti, ecc.?

Punto di chiarimento: il comportamento predefinito per un DVCS è per creare commit di unione. Perché così tanti luoghi parlano del desiderio di vedere una storia di sviluppo lineare che nasconde questi commit di fusione?

    
posta Jace Browning 18.11.2013 - 19:15
fonte

6 risposte

62

Le persone vogliono evitare i commit di unione perché rendono il registro più carino. Sul serio. Sembra che i registri centralizzati con cui sono cresciuti, e localmente possano fare tutto il loro sviluppo in un unico ramo. Non ci sono vantaggi a parte quelli estetici, e diversi inconvenienti oltre a quelli che hai citato, come renderli inclini al conflitto direttamente da un collega senza passare attraverso il server "centrale".

    
risposta data 18.11.2013 - 20:30
fonte
44

In due parole: git bisect

Una cronologia lineare ti consente di individuare la vera fonte di un bug.

Un esempio. Questo è il nostro codice iniziale:
def bar(arg=None):
    pass

def foo():
    bar()

Il ramo 1 esegue alcuni refactoring in modo che arg non sia più valido:

def bar():
    pass

def foo():
    bar()

Il ramo 2 ha una nuova funzione che deve utilizzare arg :

def bar(arg=None):
    pass

def foo():
    bar(arg=1)

Non ci saranno conflitti di fusione, tuttavia è stato introdotto un bug. Fortunatamente, questo particolare verrà catturato nella fase di compilazione, ma non siamo sempre così fortunati. Se l'errore si manifesta come comportamento imprevisto, piuttosto che un errore di compilazione, potremmo non trovarlo per una settimana o due. A quel punto, git bisect al salvataggio!

Oh merda. Questo è ciò che vede:

(master: green)
|             \_________________________
|                \                      \
(master: green)  (branch 1: green)     (branch 2: green)
|                 |                     |
|                 |                     |
(master/merge commit: green)            |
|                         ______________/
|                        /
(master/merge commit: red)
|
...days pass...
|
(master: red)

Quindi, quando inviamo git bisect per trovare il commit che ha interrotto la build, verrà individuato un commit di unione. Bene, questo aiuta un po ', ma essenzialmente sta puntando su un pacchetto di commit, non su uno solo. Tutti gli antenati sono verdi. D'altra parte, con la ridefinizione, ottieni una cronologia lineare:

(master: green)
|
(master: green)
|
(all branch 1 commits: green)
|
(some branch 2 commits: green)
|
(branch 2 commit: red)
|
(remaining branch 2 commits: red)
|
...days pass...
|
(master: still red)

Ora, git bisect punta al preciso commit di funzionalità che ha interrotto la build. Idealmente, il messaggio di commit spiegherà cosa è stato inteso abbastanza bene da fare un altro refactoring e correggere il bug subito.

L'effetto è composto solo in progetti di grandi dimensioni quando i manutentori non scrivono tutto il codice, quindi non necessariamente ricordano perché è stato eseguito un determinato commit / a cosa serviva ciascun ramo. Quindi individuare il commit esatto (e quindi essere in grado di esaminare i commit attorno ad esso) è di grande aiuto.

Detto questo, io (attualmente) preferisco ancora unire. Il rebasing su un ramo di rilascio ti fornirà la cronologia lineare da utilizzare con git bisect , mantenendo la cronologia reale per il lavoro quotidiano.

    
risposta data 19.11.2013 - 05:20
fonte
16

In breve, perché la fusione è spesso un altro posto in cui qualcosa va storto, e basta sbagliare una volta per fare in modo che le persone abbiano molta paura di affrontarlo di nuovo (una volta morso due volte timido, se vuoi).

Quindi, diciamo che stiamo lavorando su una nuova schermata di gestione degli account, e si scopre che c'è un bug scoperto nel flusso di lavoro del nuovo account. OK, prendiamo due percorsi separati: finisci la gestione degli account e correggo il bug con i nuovi account. Dal momento che entrambi abbiamo a che fare con gli account, abbiamo lavorato con codice molto simile - forse abbiamo anche dovuto regolare gli stessi pezzi di codice.

Ora, in questo momento abbiamo due versioni di software diverse ma pienamente funzionanti. Abbiamo entrambi eseguito un commit sui nostri cambiamenti, entrambi abbiamo rigorosamente testato il nostro codice, e indipendentemente siamo molto fiduciosi di aver fatto un lavoro fantastico. E adesso?

Bene, è tempo di unire, ma ... cazzate, cosa succede ora? Potremmo benissimo passare da due serie di software funzionanti a un unico, unificato, orribilmente rotto pezzo di software nuovo buggy in cui la gestione degli account non funziona e nuovi account sono rotti e io non so nemmeno se il vecchio bug è ancora lì .

Forse il software era intelligente e ha detto che c'era un conflitto e ha insistito nel dargli una guida. Bene, merda - mi siedo per farlo e vedo che hai aggiunto un codice complesso che non capisco immediatamente. Penso che sia in conflitto con le modifiche che ho apportato ... Te lo chiedo, e quando ottieni un minuto controlli e vedi il mio codice che non capisci. Uno o entrambi dobbiamo prenderci il tempo di sederci, fare un'unione vera e propria, e magari ripetere il test sull'intera cosa per assicurarci di non romperla.

Nel frattempo altri 8 ragazzi stanno commettendo codice come i sadici che sono, ho fatto alcune piccole correzioni di bug e le ho presentate prima che sapessimo che avremmo avuto un conflitto di fusione, e l'uomo è sicuramente un buon momento per fare una pausa, e forse sei fuori per il pomeriggio o bloccato in una riunione o qualsiasi altra cosa. Forse dovrei solo fare una vacanza. O cambiare carriera.

E così, per sfuggire a questo incubo, alcune persone sono diventate molto impaurite dall'impegno (cos'altro è nuovo, in modo ambiguo?). Naturalmente siamo avversi al rischio in scenari come questo - a meno che non pensiamo che facciamo schifo e lo roviniamo comunque, nel qual caso la gente inizia a comportarsi con abbandono sconsiderato. sospirare

Quindi eccoti. Sì, i sistemi moderni sono progettati per alleviare questo dolore e si suppone che sia in grado di tornare facilmente indietro e rebase e debase e freebase e hanglide e tutto il resto.

Ma è tutto più lavoro, e vogliamo solo premere il pulsante sul microonde e fare un pasto di 4 portate prima di avere il tempo di trovare una forchetta, e tutto sembra così insoddisfacente - il codice è lavoro, è produttivo, il suo significato, ma gestendo con garbo un'unione solo non conta.

I programmatori, di regola, devono sviluppare una grande memoria di lavoro, e quindi avere la tendenza a dimenticare immediatamente tutti i nomi di spazzatura e variabile e l'ambito non appena hanno finito il problema e gestire un conflitto di unione (o peggio, una fusione erroneamente gestita) è un invito a ricordare la tua mortalità.

    
risposta data 18.11.2013 - 19:38
fonte
4

Il rebasing fornisce un punto di diramazione mobile che semplifica il processo di invio delle modifiche alla baseline. Ciò consente di trattare un ramo a lungo termine come se si trattasse di una modifica locale. Senza la ridefinizione, i rami accumulano le modifiche dalla linea di base che verranno incluse nelle modifiche che verranno ricondotte alla baseline.

L'unione lascia la tua linea di base nel punto di diramazione originale. Se si uniscono alcune settimane di cambiamenti dalla linea da cui si dipartiva, ora si hanno molte modifiche dal punto di diramazione, molte delle quali saranno nella linea di base. Questo rende difficile identificare le modifiche nel tuo ramo. Riportare le modifiche alla linea di base può generare conflitti non correlati alle modifiche. In caso di conflitti, è possibile inviare modifiche incoerenti. Le continue fusioni richiedono sforzi per gestirle, ed è relativamente facile perdere i cambiamenti.

Rebase sposta il punto di diramazione sull'ultima revisione sulla linea di base. Tutti i conflitti che incontrerai saranno per il / i tuo / i cambiamento / i. Spingere i cambiamenti è molto più semplice. I conflitti sono trattati nel ramo locale facendo un rebase aggiuntivo. Nel caso di spinte contrastanti, l'ultimo a spingere il loro cambiamento dovrà risolvere il problema con il loro cambiamento.

    
risposta data 19.11.2013 - 03:31
fonte
3

Gli strumenti automatizzati stanno migliorando per garantire che il codice di unione venga compilato ed eseguito, evitando così conflitti sintattici , ma non possono garantire l'assenza di conflitti logici che può essere introdotto da fusioni. Quindi una fusione "di successo" ti dà un senso di falsa sicurezza, quando in realtà non garantisce nulla e devi ripetere tutti i tuoi test.

Il vero problema con la ramificazione e la fusione, come la vedo io, è che prende a calci il proverbiale in fondo alla strada. Ti consente di dire "lavorerò nel mio piccolo mondo" per una settimana e di affrontare qualsiasi problema si presenti più tardi. Ma correggere bug è sempre più veloce / economico quando sono freschi. Quando tutti i rami del codice iniziano a fondersi, potresti già dimenticare alcune delle sfumature delle cose che sono state fatte.

Combina insieme i due problemi summenzionati e potresti trovarti in una situazione in cui è più semplice e semplice far lavorare tutti allo stesso tronco e risolvere continuamente i conflitti man mano che si presentano, anche se rendono lo sviluppo attivo un po 'più lento .

    
risposta data 18.11.2013 - 20:49
fonte
0

Un ulteriore punto rilevante è questo: con rebasing posso facilmente selezionare o ripristinare una funzionalità nel mio ramo di rilascio.

    
risposta data 28.01.2018 - 18:07
fonte

Leggi altre domande sui tag