Perché git esegue per impostazione predefinita fusioni con avanzamento rapido?


641

Proveniente da mercurio, utilizzo le filiali per organizzare le funzionalità. Naturalmente, voglio vedere questo flusso di lavoro anche nella mia storia.

Ho iniziato il mio nuovo progetto usando git e ho finito la mia prima opera. Quando ho unito la funzione, mi sono reso conto che git usa l'avanzamento rapido, ovvero applica le mie modifiche direttamente al ramo principale, se possibile, e dimentica il mio ramo.

Quindi, per pensare al futuro: sono l'unico a lavorare a questo progetto. Se utilizzo l'approccio predefinito di git (fusione rapida), la mia storia risulterebbe in un gigantesco ramo master. Nessuno sa che ho usato un ramo separato per ogni caratteristica, perché alla fine avrò solo quel ramo principale gigante. Non sembrerà poco professionale?

Con questo ragionamento, non voglio una fusione rapida e non riesco a capire perché sia ​​l'impostazione predefinita. Cosa c'è di così bello?


38
Nota: vedere anche sandofsky.com/blog/git-workflow.html , ed evitare " no-ff" con i suoi "punti di arresto" che interrompono il bisect o la colpa.
VonC

15
ti penti di usare git su un progetto one man?
HaveAGuess,

27
Assolutamente no! Nella mia cartella di lavoro ho 7 progetti one-man in cui utilizzo git. Consentitemi di riformularlo: ho avviato molti progetti da quando ho posto questa domanda e tutti sono stati modificati tramite git. Per quanto ne so, solo git e mercurial supportano il versioning locale, che è essenziale per me da quando mi sono abituato. È facile da impostare e hai sempre tutta la storia con te. Nei progetti di gruppo è ancora meglio, dal momento che puoi impegnarti senza interferire con il tuo codice work-in-progress. Inoltre uso github per condividere alcuni dei miei progetti (ad esempio micro-optparse) in cui git è un requisito.
Florian Pilz,

7
@Cawas true, -no-ffraramente è una buona idea, ma può comunque aiutare a mantenere una cronologia interna delle funzionalità mentre si registra solo un commit sul ramo principale. Questo ha senso per la lunga cronologia delle funzionalità, quando di tanto in tanto unisci la sua progressione sul ramo principale.
VonC,

12
A proposito, alla tua domanda di "Quella [una storia del ramo lineare] non sembra poco professionale?". Non c'è nulla di poco professionale nell'uso di un sistema di codice sorgente con i suoi valori predefiniti. Non si tratta di professionalità. Si tratta di determinare quale filosofia di branching ti iscrivi. Ad esempio, @VonC si collega all'articolo di sandofsky in cui sostiene l'utilizzo di avanzamento veloce come approccio superiore. Non giusto o sbagliato, solo filosofie diverse per contesti diversi.
Marplesoft,

Risposte:


718

La fusione in avanti veloce ha senso per i rami di breve durata, ma in una storia più complessa , la fusione in avanti non veloce può rendere la storia più facile da capire e rendere più semplice il ripristino di un gruppo di commit.

Avvertenza : l' avanzamento non rapido ha anche potenziali effetti collaterali. Si prega di rivedere https://sandofsky.com/blog/git-workflow.html , evitare il "no-ff" con i suoi "commit checkpoint" che rompono il bisect o la colpa e valutare attentamente se dovrebbe essere l'approccio predefinito per master.

testo alternativo
(Da nvie.com , Vincent Driessen , pubblica " Un modello di ramificazione Git di successo ")

Incorporando una funzionalità finita su sviluppo

Le funzionalità finite possono essere unite nel ramo di sviluppo per aggiungerle alla prossima versione:

$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff myfeature
Updating ea1b82a..05e9557
(Summary of changes)
$ git branch -d myfeature
Deleted branch myfeature (was 05e9557).
$ git push origin develop

Il --no-ffflag fa sì che l'unione crei sempre un nuovo oggetto commit, anche se l'unione potrebbe essere eseguita con un avanzamento veloce. Ciò evita di perdere informazioni sull'esistenza storica di un ramo di funzionalità e raggruppa tutti i commit che insieme hanno aggiunto la funzionalità.

Jakub Narębski menziona anche la configurazionemerge.ff :

Per impostazione predefinita, Git non crea un commit di unione extra quando si unisce un commit che è un discendente del commit corrente. Invece, la punta del ramo corrente viene fatta avanzare rapidamente.
Se impostato su false, questa variabile dice a Git di creare un commit di unione extra in tal caso (equivalente a dare l' --no-ffopzione dalla riga di comando).
Se impostato su ' only', sono consentite solo tali fusioni di avanzamento rapido (equivalenti a fornire l' --ff-onlyopzione dalla riga di comando).


L'avanzamento rapido è l'impostazione predefinita perché:

  • i rami di breve durata sono molto facili da creare e utilizzare in Git
  • I rami di breve durata spesso isolano molti commit che possono essere riorganizzati liberamente all'interno di quel ramo
  • tali commit fanno effettivamente parte del ramo principale: una volta riorganizzato, il ramo principale viene fatto avanzare rapidamente per includerli.

Ma se prevedi un flusso di lavoro iterativo su un argomento / ramo di funzionalità (ovvero, unisco, torno a questo ramo di funzionalità e aggiungo altri commit), quindi è utile includere solo l'unione nel ramo principale, anziché tutti i commit intermedi del ramo funzione.

In questo caso, puoi finire per impostare questo tipo di file di configurazione :

[branch "master"]
# This is the list of cmdline options that should be added to git-merge 
# when I merge commits into the master branch.

# The option --no-commit instructs git not to commit the merge
# by default. This allows me to do some final adjustment to the commit log
# message before it gets commited. I often use this to add extra info to
# the merge message or rewrite my local branch names in the commit message
# to branch names that are more understandable to the casual reader of the git log.

# Option --no-ff instructs git to always record a merge commit, even if
# the branch being merged into can be fast-forwarded. This is often the
# case when you create a short-lived topic branch which tracks master, do
# some changes on the topic branch and then merge the changes into the
# master which remained unchanged while you were doing your work on the
# topic branch. In this case the master branch can be fast-forwarded (that
# is the tip of the master branch can be updated to point to the tip of
# the topic branch) and this is what git does by default. With --no-ff
# option set, git creates a real merge commit which records the fact that
# another branch was merged. I find this easier to understand and read in
# the log.

mergeoptions = --no-commit --no-ff

L'OP aggiunge nei commenti:

Vedo un senso nell'avanzamento rapido per i rami [di breve durata], ma renderlo l'azione predefinita significa che git presume che tu abbia spesso rami [di breve durata]. Ragionevole?

Jefromi risponde:

Penso che la durata delle filiali vari notevolmente da utente a utente. Tra gli utenti esperti, tuttavia, c'è probabilmente la tendenza ad avere filiali molto più di breve durata.

Per me, un ramo di breve durata è quello che creo per rendere più facile una certa operazione (rebasing, probabile o patching e test rapidi), e quindi eliminare immediatamente una volta terminato.
Ciò significa che probabilmente dovrebbe essere assorbito nel ramo dell'argomento da cui è stato biforcato e il ramo dell'argomento verrà unito come un ramo. Nessuno ha bisogno di sapere cosa ho fatto internamente al fine di creare la serie di commit implementando quella determinata funzionalità.

Più in generale, aggiungo:

dipende davvero dal flusso di lavoro di sviluppo :

  • se è lineare, un ramo ha senso.
  • Se è necessario isolare le funzionalità e lavorarci per un lungo periodo di tempo e unirle ripetutamente, diversi rami hanno senso.

Vedi " Quando dovresti diramare? "

In realtà, quando si considera il modello di ramo Mercurial, è al suo interno un ramo per repository (anche se è possibile creare teste anonime, segnalibri e persino rami con nome )
Vedi "Git e Mercurial - Confronta e Contrasto" .

Mercurial, per impostazione predefinita, utilizza codeline leggere anonime, che nella sua terminologia sono chiamate "teste".
Git utilizza rami con nome leggero, con mappatura iniettiva per mappare i nomi dei rami nel repository remoto ai nomi dei rami di tracciamento remoto.
Git "ti costringe" a nominare i rami (beh, ad eccezione di un singolo ramo senza nome, che è una situazione chiamata " HEAD distaccato "), ma penso che funzioni meglio con flussi di lavoro pesanti come il flusso di lavoro dei rami dell'argomento, il che significa più rami in un unico paradigma di repository.


Wow, è stato veloce. ;) Conosco l'opzione --no-ff, ma scopro l'avanzamento veloce solo dopo aver rovinato la mia funzione. Vedo un senso nell'avanzamento rapido per i rami a vita breve, ma renderlo l'azione predefinita significa che git presume che tu abbia spesso rami a vita così breve. Ragionevole?
Florian Pilz,

@Florian: credo che questa sia una visione ragionevole del processo. Ho aggiunto un esempio di una configurazione che imposterà il modo in cui vuoi gestire l'unione al master.
VonC,

Grazie, il file di configurazione dovrebbe aiutare a evitare tali problemi. :) Applicato questa configurazione solo localmente con "git config branch.master.mergeoptions '--no-ff'"
Florian Pilz,

2
@BehrangSaeedzadeh: il rebasing è un altro argomento (che non è stato menzionato in questa pagina): fintanto che non hai spinto il tuo featureramo in un repository pubblico, puoi rifarlo su mastertutte le volte che vuoi. Vedere stackoverflow.com/questions/5250817/...
VonC

4
@BehrangSaeedzadeh: un rebase da solo non renderà lineare una storia. È il modo in cui si integrano featurenuovamente i cambiamenti del proprio ramo masterche rende lineare o no la storia. Una semplice fusione rapida renderà lineare. Ciò ha senso se hai pulito la cronologia di quel featureramo prima dell'unione di avanzamento rapido, lasciando solo commit significativi, come menzionato in stackoverflow.com/questions/7425541/… .
VonC,

42

Vorrei espandere un po 'la risposta molto completa di una VonC :


Innanzitutto, se lo ricordo correttamente, il fatto che Git per impostazione predefinita non crei commit di unione nel caso di avanzamento rapido è venuto dalla considerazione di "repository uguali" a ramo singolo, in cui il pull reciproco viene utilizzato per sincronizzare questi due repository (un flusso di lavoro che puoi trovare come primo esempio nella documentazione della maggior parte degli utenti, inclusi "Il Manuale dell'utente di Git" e "Controllo della versione per esempio"). In questo caso non usi pull per unire il ramo realizzato, lo usi per tenere il passo con altri lavori. Non vuoi avere fatti effimeri e non importanti quando ti capita di fare una sincronizzazione salvata e archiviata in un repository, salvata per il futuro.

Si noti che l'utilità dei rami delle funzionalità e di disporre di più rami nel singolo repository è arrivata solo più tardi, con un uso più diffuso di VCS con un buon supporto di fusione e con la prova di vari flussi di lavoro basati su unione. Ecco perché, ad esempio, Mercurial inizialmente supportava un solo ramo per repository (oltre a suggerimenti anonimi per il monitoraggio di rami remoti), come visto nelle revisioni precedenti di "Mercurial: The Definitive Guide".


In secondo luogo, quando si seguono le migliori pratiche di utilizzo dei rami delle caratteristiche , vale a dire che i rami delle caratteristiche dovrebbero iniziare tutti da una versione stabile (di solito dall'ultima versione), per essere in grado di selezionare e selezionare quali funzioni includere selezionando quali rami di caratteristiche da unire, si di solito non si trovano in una situazione di avanzamento rapido ... il che rende controverso questo problema. Devi preoccuparti di creare una vera unione e di non avanzare rapidamente quando unisci un primo ramo (supponendo che non apporti modifiche a commit singolo direttamente su "master"); tutte le altre fusioni successive si trovano ovviamente in una situazione non di avanzamento rapido.

HTH


Per quanto riguarda i rami delle funzionalità di versioni stabili: cosa succede se una funzionalità che sto sviluppando per la prossima versione dipende dalle modifiche di un'altra funzionalità che ho già sviluppato e unito in master? Sicuramente ciò significa che devo creare questo secondo ramo di funzionalità dal master?
dOxxx,

1
@dOxxx: Sì, ci sono eccezioni, come ad esempio il caso in cui un ramo si basa sull'altro (direttamente o dopo aver unito il precedente al master).
Jakub Narębski,
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.