Perché così tanti progetti preferiscono "git rebase" piuttosto che "git merge"?


69

Uno dei vantaggi dell'utilizzo di un DVCS è il flusso di lavoro edit-commit-merge (rispetto a edit-merge-commit spesso imposto da un CVCS). Consentire la registrazione di ogni modifica unica nel repository indipendentemente dalle fusioni garantisce che il DAG rifletta accuratamente il vero pedigree del progetto.

Perché così tanti siti web parlano del voler "evitare impegni di fusione"? La fusione tra pre-commit o rebasing post-merge non rende più difficile isolare le regressioni, ripristinare le modifiche passate, ecc.?

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


18
Gli strumenti non sempre funzionano bene. E quando sbagliano, oh ragazzo, sbagliano.
Oded,

2
@Oded ti riferisci ai commit di unione automatica (come quelli creati da git pull)? Posso capire perché ciò potrebbe essere problematico, ma la mia domanda riguarda tutti i commit di merge.
Jace Browning,

possibile duplicato di I conflitti di unione complicati frequenti sono un segno di problemi? "Fusioni e rinunce dovrebbero causare gli stessi identici conflitti, per quei conflitti intrinseci che un essere umano deve risolvere (cioè due sviluppatori che cambiano la stessa linea di codice) ..."
moscerino del

Un pensiero che ho avuto dopo aver pubblicato la mia risposta, che l'IMO non si adatta esattamente come una risposta: git pull --rebase == svn up(sciolto uguale, non rigoroso uguale)
Izkata

in un negozio centralizzato come SourceSafe, le modifiche continuerebbero ad essere registrate in uno sforzo continuo, ma una volta raggiunto un plateau stabile etichettiamo tutti i file a partire da una certa data. Quindi almeno puoi correlare i bug a prima o dopo tale e tale versione, o rebase -line.
Andyz Smith,

Risposte:


65

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


5
Sei l'unico che ha capito correttamente la mia domanda. Come posso renderlo più chiaro?
Jace Browning,

12
Forse riformulare "Quali sono i vantaggi di una storia di sviluppo lineare?"
Karl Bielefeldt,

11
"Spesso lo farai per assicurarti che i tuoi commit si applichino in modo pulito su una filiale remota - forse in un progetto a cui stai cercando di contribuire ma che non mantieni. In questo caso, faresti il ​​tuo lavoro in una succursale e poi rimodellare il tuo lavoro su origin / master quando eri pronto a inviare le tue patch al progetto principale. In questo modo, il manutentore non deve fare alcun lavoro di integrazione - solo un avanzamento rapido o un'applicazione pulita. " git-scm.com/book/en/Git-Branching-Rebasing
Andyz Smith

12
Lo fai sembrare come se fosse solo cosmetico, ma in realtà è pratico: è molto più facile capire cosa sta succedendo in una storia lineare che da un complicato DAG con linee che si incrociano e si fondono.
Daniel Hershcovich,

20
"Non ci sono benefici a parte quell'estetica" - Non lo so. È davvero difficile capire cosa sta succedendo nella tua storia quando la tua storia sembra un circuito. (Supponendo che più membri del team, ognuno con i propri rami e flussi di lavoro.)
Archagon,

46

In due parole: git bisect

Una cronologia lineare consente di individuare l'origine effettiva di un bug.


Un esempio. Questo è il nostro codice iniziale:

def bar(arg=None):
    pass

def foo():
    bar()

La filiale 1 esegue alcuni refactoring in modo che argnon siano più validi:

def bar():
    pass

def foo():
    bar()

Branch 2 ha una nuova funzionalità che deve usare arg:

def bar(arg=None):
    pass

def foo():
    bar(arg=1)

Non ci saranno conflitti di unione, tuttavia è stato introdotto un bug. Fortunatamente, questo particolare verrà catturato dal passaggio della compilation, ma non siamo sempre così fortunati. Se il bug si manifesta come comportamento imprevisto, piuttosto che un errore di compilazione, potremmo non trovarlo per una settimana o due. A quel punto, git bisectin soccorso!

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 spediamo git bisectper trovare il commit che ha interrotto la build, individuerà un commit di unione. Bene, questo aiuta un po ', ma essenzialmente indica un pacchetto di commit, non uno solo. Tutti gli antenati sono verdi. D'altra parte, con il rebasing, 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 bisectindicherà l'esatto commit di funzionalità che ha interrotto la build. Idealmente, il messaggio di commit spiegherà cosa è stato progettato abbastanza bene per fare un altro refactor e correggere immediatamente il bug.

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


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


2
Grazie per questa spiegazione Puoi espandere il motivo per cui preferisci ancora la fusione? So che molte persone preferiscono fondersi con il rebasing nella maggior parte delle situazioni, ma non sono mai stato in grado di capire il perché.
Filippo,

@Philip Sono un po 'titubante nel farlo, dato che ho usato git solo per alcuni mesi su piccoli progetti personali, mai in gruppo. Ma quando tengo traccia di ciò che ho fatto di recente, essere in grado di vedere il flusso di commit tra le filiali in gitk --allè estremamente utile (soprattutto perché di solito ho filiali 3-ish in un dato momento e passare da una all'altra ogni giorno a seconda del mio umore a il tempo).
Izkata,

16
Ma l'unione è il problema, quindi come risultato si desidera un commit di unione bisect. È abbastanza facile trovare i singoli commit con uno blameo un diverso bisectse vuoi scavare più a fondo. Con una cronologia lineare, l'unione viene eseguita fuori libro, quindi non si può più dire che il problema è stato una fusione errata. Sembra solo un idiota che usa un argomento che non esiste, specialmente se l'argomento è stato rimosso diversi commit precedenti. Cercare di capire perché lo farebbe è molto più difficile quando si distrugge la storia.
Karl Bielefeldt,

1
Davvero in disaccordo con questa risposta. Rebasing distrugge l'utilità di bisect a volte. Fatta eccezione per il commit di un rebase, i commit di un rebase potrebbero non essere eseguibili. Esempio: ti sei ramificato in A e hai creato B, C, D, qualcuno ha spinto E. Sei B e C e lavori ma ti sei rifatto su E, sei B e C non sono programmabili o sono eseguibili e non funzionano. Il caso migliore è mollare, nel peggiore dei casi pensi che B o C contenga il problema quando in realtà è D.
Lan

4
@Izkata Perché stai usando bisect? Perché si verificano bug / errori. È una realtà umiliante. Forse "trascinare il mouse a sinistra" non faceva parte dell'elenco dei test automatizzati / manuali prima e ora notiamo che quella funzionalità è rotta ma sappiamo che funzionava.
Lan,

17

In breve, perché la fusione è spesso un altro posto in cui qualcosa può andare storto, e deve solo andare storto una volta per rendere le persone molto spaventate di affrontarlo di nuovo (una volta morso due volte timido, se vuoi).

Supponiamo quindi che stiamo lavorando su una nuova schermata di gestione dell'account e si scopre che c'è un bug scoperto nel flusso di lavoro del nuovo account. OK, prendiamo due percorsi separati: finisci la gestione dell'account e correggo il bug con i nuovi account. Dato che entrambi abbiamo a che fare con gli account, abbiamo lavorato con un codice molto simile, forse abbiamo dovuto persino modificare gli stessi pezzi di codice.

Ora, in questo momento abbiamo due versioni diverse ma perfettamente funzionanti del software. Entrambi abbiamo eseguito un commit sulle nostre modifiche, entrambi abbiamo testato diligentemente il nostro codice e indipendentemente siamo molto fiduciosi di aver fatto un ottimo lavoro. E adesso?

Bene, è tempo di fondersi, ma ... merda, cosa succede adesso? Potremmo benissimo passare da due set di software funzionanti a uno, unificato, orribilmente rotto di software di nuova generazione in cui la gestione dell'account non funziona e i nuovi account sono rotti e non so nemmeno se il vecchio bug è ancora lì .

Forse il software era intelligente e diceva che c'era un conflitto e ha insistito perché gli dessimo una guida. Beh, 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 ricevi un minuto controlli e vedi il mio codice che non capisci. Uno o entrambi dobbiamo prenderci il tempo per sederci, fare una fusione corretta e possibilmente ripetere il test dell'intera cosa del pericolo per assicurarci di non averlo rotto.

Nel frattempo altri 8 ragazzi stanno tutti commettendo codice come i sadici, ho fatto alcune piccole correzioni di bug e le ho inviate prima di sapere che abbiamo avuto un conflitto di unione, e l'uomo sembra sicuramente un buon momento per fare una pausa, e forse tu sono fuori per il pomeriggio o bloccati in una riunione o altro. Forse dovrei solo andare in vacanza. O cambia carriera.

E così, per sfuggire a questo incubo, alcune persone hanno avuto molta paura dell'impegno (cos'altro c'è di nuovo, prima?). Ovviamente siamo avversi al rischio in scenari come questo - a meno che non pensiamo di fare schifo e comunque lo rovineremo, nel qual caso le persone iniziano a recitare con abbandono spericolato. sospiro

Quindi eccoti. Sì, i sistemi moderni sono progettati per alleviare questo dolore, e dovrebbe essere in grado di retrocedere e rifare facilmente, 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, è significativo, ma gestire con grazia una fusione non conta.

I programmatori, di regola, devono sviluppare una grande memoria di lavoro, e quindi hanno la tendenza a dimenticare immediatamente tutti quei nomi spazzatura e variabili e scoping non appena hanno finito il problema e gestiscono un conflitto di unione (o peggio, un unione erroneamente gestita) è un invito a ricordare la tua mortalità.


5
Incredibilmente formulato, signore!
Lepre meridionale

10
Questo risponde al motivo per cui le persone hanno paura di fondersi, non perché i merge commit sono considerati cattivi da così tanti.
Jay,

23
Il problema con questa risposta è che questo è ancora vero per un rebase . Dopotutto, un rebase sta ancora facendo un'unione in cui hai cambiato una riga di codice che era già stata modificata. Penso che questo sia un problema con il modo in cui è stata dichiarata la domanda.
deworde,

2
Ho ridimensionato questa risposta perché, essendo ancora eloquente, va oltre il punto, imho.
Eugene,

6
Questo non sembra colpire affatto rebase vs merge-merge.
Andyz Smith,

5

Il rifacimento fornisce un punto di diramazione mobile che semplifica il processo di invio delle modifiche alla linea di base. Ciò consente di trattare un ramo con esecuzione prolungata come se si trattasse di una modifica locale. Senza la rifasatura, i rami accumulano le modifiche dalla linea di base che verranno incluse nelle modifiche che vengono ricondotte alla linea di base.

La fusione lascia la linea di base nel punto di diramazione originale. Se unisci alcune settimane di modifiche dalla linea da cui sei stato ramificato, ora hai molte modifiche dal tuo punto di diramazione, molte delle quali saranno nella tua linea di base. Ciò rende difficile identificare le modifiche nella tua filiale. La restituzione delle modifiche alla linea di base può generare conflitti non correlati alle modifiche. In caso di conflitti, è possibile spingere cambiamenti incoerenti. Le fusioni continue richiedono sforzi per la gestione ed è relativamente facile perdere i cambiamenti.

Rebase sposta il punto di diramazione sull'ultima revisione sulla baseline. Qualsiasi conflitto che incontrerai sarà solo per le tue modifiche. Spingere le modifiche è molto più semplice. I conflitti vengono risolti nella filiale locale effettuando un rebase aggiuntivo. In caso di push contrastanti, l'ultimo a spingere la modifica dovrà risolvere il problema con la modifica.


1
quindi tutto ciò che fa è ridurre le nuove modifiche a un livello che sono considerate "la cosa sbagliata" perché sono l'unica modifica . Se tutte le vecchie modifiche sono incluse nell'unione, tutte le modifiche sono su un piano di parità, per quanto riguarda se sono "la cosa giusta" oggi.
Andyz Smith,

@AndyzSmith Non considererei le nuove modifiche come una cosa sbagliata . Quando si cerca di determinare cosa viene cambiato e dovrebbe essere spinto, sono la cosa giusta . Una volta effettuato il riepilogo, puoi ignorare le vecchie modifiche poiché fanno parte della tua baseline.
BillThor,

giusto, quindi Se non sei sicuro di quale sia la tua linea di base, ovvero se ciò che hai cambiato già pronto per essere unito sia "la cosa giusta", allora non ribattere.
Andyz Smith,

E, se ricevi una spinta al cambiamento da qualcuno, e si rompe la build perché il loro prototipo di funzione non corrisponde a un prototipo esistente, sai immediatamente, a causa del rebase, che il nuovo ragazzo ha torto. non devi indovinare che forse il nuovo ragazzo ha il prototipo giusto e le modifiche al changset precedentemente, non ancora basate, non applicate sono quelle con il prototipo sbagliato.
Andyz Smith,

4

Gli strumenti automatizzati stanno migliorando per assicurarsi che il codice di fusione verrà compilato ed eseguito, evitando conflitti sintattici , ma non possono garantire l'assenza di conflitti logici che possono essere introdotti dalle fusioni. Quindi una fusione "riuscita" ti dà un senso di falsa fiducia, quando in realtà non garantisce nulla e devi ripetere tutti i test.

Il vero problema con le ramificazioni e le fusioni, secondo me, è che calcia il proverbiale barattolo lungo la strada. Ti permette di dire "Lavorerò nel mio piccolo mondo" per una settimana, e affronterà qualsiasi problema si presenti in seguito. Ma correggere i 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.

Prendi i due problemi sopra menzionati e potresti trovarti in una situazione in cui è più semplice e più facile far lavorare tutti dallo stesso tronco e risolvere continuamente i conflitti mentre si presentano, anche se rendono lo sviluppo attivo un po 'più lento.


4
Questo risponde al motivo per cui le persone hanno paura di fondersi, non perché i merge commit sono considerati cattivi da così tanti.
Jay,

Quindi, questo tronco a cui ti riferisci, comporta rebase? Expound per favore.
Andyz Smith,

@AndyzSmith "trunk" è il termine svn per il suo equivalente al ramo "master" di git
Izkata

@Izkata, quindi questa risposta sostiene di non usare né unisci né rebase allora?
Andyz Smith,

@AndyzSmith Sì, è così che l'ho letto anche io. E non sono d'accordo con questo; dopo aver lavorato in un team con altri 7 sviluppatori nel modo suggerito, non lo consiglio. Avremmo dovuto usare i rami delle funzionalità più di quanto abbiamo fatto, ma la maggior parte di loro ha paura di fondersi (come BrianDHall ottiene nella sua risposta).
Izkata,

0

Un altro punto rilevante è questo: con il rebasing posso facilmente selezionare o ripristinare una funzione nel mio ramo di rilascio.


1
Questo elaborato su questo?
Akavall,

Sicuro :-) In git flow, creiamo regolarmente nuovi rami di rilascio. Se dopo la creazione, viene corretto un bug nel ramo di sviluppo, lo utilizziamo git cherry-pick -x <commit>per applicarlo al ramo di rilascio. Oppure, potremmo voler annullare qualcosa per l'uscita, con git revert <commit>. Se <commit>è una fusione, diventa pelosa. Si è possibile per quanto ne so, ma non è facile da fare. Quando si utilizza rebase, ogni "unione" è un commit gigante ma regolare, facilmente selezionabile e riveribile.
Bruno Schäpper,

0

Tre cose non sono state dette in nessuna delle risposte:

  • Diffondere all'interno di un ramo:

    • La differenza tra una coppia arbitraria di commit in un ramo diventa estremamente difficile quando ci sono commit di unione.
  • Unione di un elemento alla volta:

    • Quando si risolve la differenza tra due rami, in genere si verifica un'unione in una sola volta e tutti i conflitti di unione che si lasciano fare senza contesto di quale specifico commit era responsabile del conflitto. Se si ribatte, il rebase si fermerà nel punto in cui si è verificato il conflitto e ti consentirà di risolverlo in quel contesto.
  • Pulizia prima della spinta:

    • Se hai commesso un errore che devi correggere in seguito, se non hai spinto il rebase interattivo ti consentirà di combinare / dividere / cambiare / spostare i commit. Mentre puoi ancora farlo se ti sei unito, diventa molto difficile se vuoi combinare / dividere / cambiare / spostare attraverso un confine di unione.
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.