2009-05-20 14 views
130

Attualmente stiamo utilizzando la sovversione su un numero di codice relativamente grande. Ogni versione ottiene il proprio ramo e le correzioni vengono eseguite contro il tronco e migrate nei rami di rilascio usando svnmerge.pyMercuriale: Filiali denominate o Repository multipli

Credo che sia giunto il momento di passare a un migliore controllo del codice sorgente, e ho lavorato con Mercurial per un po '. .

Sembra che ci siano due scuole su come gestire tale struttura di rilascio utilizzando Mercurial. O ogni versione ottiene il proprio repository e le correzioni vengono eseguite contro il ramo di rilascio e trasferite al ramo principale (e qualsiasi altro ramo di release più recente) O utilizzando rami denominati all'interno di un singolo repository (o più copie corrispondenti).

In entrambi i casi sembra che potrei usare qualcosa come il trapianto in cambiamenti di cherrypick per l'inclusione nei rami di rilascio.

Chiedo a voi; quali sono i meriti relativi di ciascun approccio?

risposta

129

La più grande differenza è come i nomi dei rami sono registrati nella cronologia. Con i rami nominati il ​​nome del ramo è incorporato in in ciascun changeset e diventerà quindi una parte immutabile della cronologia. Con i cloni ci sarà non permanente record di provenienza di un particolare changeset.

Ciò significa che i cloni sono ottimi per esperimenti rapidi in cui non si desidera registrare il nome di un ramo e le diramazioni denominate sono utili per i rami a lungo termine ("1.x", "2.x" e simili).

Si noti inoltre che un singolo repository può facilmente ospitare più rami leggeri in Mercurial. Tali rami in-repository possono essere aggiunti ai segnalibri in modo da poterli ritrovare facilmente. Diciamo che è stato clonato il repository società quando si presentava così:

[a] --- [b] 

È mettere mano e fare [x] e [y]:

[a] --- [b] --- [x] --- [y] 

intanto qualcuno mette [c] e [d] nel repository, in modo quando si tira si ottiene un grafico di storia come questo:

 
      [x] --- [y] 
     /
[a] --- [b] --- [c] --- [d] 

Qui ci sono due teste in un si repository ngle. La tua copia di lavoro rifletterà sempre un singolo changeset, il cosiddetto changeset genitore della copia di lavoro. Verificare con:

% hg parents 

Diciamo che segnala [y]. Potete vedere le teste con

% hg heads 

e questo vi segnalare [y] e [d].Se si desidera aggiornare il repository per un checkout pulita di [d], quindi è sufficiente fare (sostituto [d] con il numero di revisione per [d]):

% hg update --clean [d] 

Si vedrà, quindi, che hg parents rapporto [d]. Ciò significa che il tuo prossimo commit avrà come [d] come genitore. È possibile quindi correggere un bug che hai notato nel ramo principale e creare changeset [e]:

 
      [x] --- [y] 
     /
[a] --- [b] --- [c] --- [d] --- [e] 

Per spingere changeset [e] solo, è necessario fare

% hg push -r [e] 

dove [e] è l'hash di modifiche. Per impostazione predefinita, hg push semplicemente confronterà i repository e vedrai che sono mancanti [x], [y] e [e], ma potresti non voler condividere [x] e [y].

Se il bugfix si effettua anche, si desidera unire con il vostro ramo di caratteristica:

% hg update [y] 
% hg merge 

che lascerà il grafico repository simile a questo:

 
      [x] --- [y] ----------- [z] 
     /     /
[a] --- [b] --- [c] --- [d] --- [e] 

dove [z] è l'unione tra [y] e [e]. Si potrebbe anche scelto di gettare la filiale di via:

% hg strip [x] 

Il mio punto principale di questa storia è questa: un singolo clone può facilmente rappresentare diversi brani di sviluppo. Questo è sempre stato vero per "plain hg" senza utilizzare alcuna estensione. Il bookmarks extension è di grande aiuto, però. Ti permetterà di assegnare nomi (segnalibri) ai changeset. Nel caso sopra vorrai un segnalibro sul tuo capo sviluppo e uno sulla testa dell'upstream. I segnalibri possono essere spinti e tirati con Mercurial 1.6 e sono diventati una funzione incorporata in Mercurial 1.8.

Se avessi scelto di fare due cloni, il clone di sviluppo avrebbe guardato come questo dopo aver fatto [x] e [y]:

[a] --- [b] --- [x] --- [y] 

E il vostro clone monte conterrà:

[a] --- [b] --- [c] --- [d] 

Ora nota il bug e risolvilo. Qui non è necessario hg update poiché il clone upstream è pronto per l'uso.Vi impegnate e creare [e]:

[a] --- [b] --- [c] --- [d] --- [e] 

Per includere il bugfix nel vostro clone di sviluppo si tira in là:

 
[a] --- [b] --- [x] --- [y] 
      \ 
      [c] --- [d] --- [e] 

e unire:

 
[a] --- [b] --- [x] --- [y] --- [z] 
      \     /
      [c] --- [d] --- [e] 

La potenza grafico appare diverso, ma ha la stessa struttura e il risultato finale è lo stesso. Usando i cloni dovevi fare un po 'meno di contabilità mentale.

I rami denominati non sono realmente entrati nell'immagine perché sono piuttosto opzionali. Mercurial stesso è stato sviluppato utilizzando due cloni per anni prima di passare all'utilizzo di rami denominati. Manteniamo un ramo chiamato "stabile" oltre al ramo "predefinito" e rendiamo le nostre versioni basate sul ramo "stabile". Vedere la pagina standard branching nella wiki per una descrizione del flusso di lavoro consigliato.

+1

se il changeset proviene da un utente diverso, sarebbe stato registrato, quindi l'utilizzo dei cloni non è niente male. Quando si spinge una nuova caratteristica, spesso non è interessante sapere che l'hai fatto da un repository separato. C'è anche un'estensione localbranch, che ti dà un solo ramo locale. Utile quando la clonazione del repository è associata a costi elevati (tempo/spazio). –

+2

facendo riferimento a: "I cloni sono ottimi per esperimenti rapidi" - No, non lo sono! Cosa succede se hai qualche tousands di file in repo? La clonazione richiederà anni (in qualsiasi momento al di sopra di 1 minuto) mentre il cambio di ramificazione è di un solo istante (<1 secondo). Continuare a usare i rami nominati inquinerà il log delle modifiche. Non è un vicolo cieco? O mi manca qualcosa? – seler

+0

OK seler; Sembra una modifica alla sua argomentazione originale; I cloni sono buoni laddove il sovraccarico di più copie complete non è importante per te, o quando puoi usare i link simbolici/hardlink di hg per mitigare il costo delle copie di lavoro locali separate per filiale. –

5

La principale differenza, per quanto ne so, è qualcosa che hai già affermato: le named branch sono in un unico repository. I rami con nome hanno tutto a portata di mano in un unico posto. Repositi separati sono più piccoli e facili da spostare. Il motivo per cui ci sono due scuole di pensiero su questo è che non c'è un chiaro vincitore. Le argomentazioni di chi ha più senso per te è probabilmente quella che dovresti seguire, perché è probabile che il loro ambiente sia più simile al tuo.

29

Penso che tu voglia l'intera cronologia in un unico repo. La generazione di un repo a breve termine è per esperimenti a breve termine, non per eventi importanti come le uscite.

Una delle delusioni di Mercurial è che non sembra esserci un modo semplice per creare un ramo di breve durata, giocarci, abbandonarlo e raccogliere la spazzatura. I rami sono per sempre. Sono solidale con il fatto di non voler mai abbandonare la cronologia, ma i rami super-economici usa e getta sono una funzione git che mi piacerebbe davvero vedere in hg.

+20

Puoi facilmente creare questo ramo di funzionalità: "hg update" al tuo punto di diramazione, modifica via e "hg commit". Hai creato una nuova linea di sviluppo divergente: nuovi impegni estenderanno questa branca. Usa "hg clone -r" per sbarazzartene o rimuovilo in linea con "hg strip". Quindi, per favore, non essere deluso, o vieni alle mailing list Mercurial con le tue richieste di funzionalità. –

+8

Sembra che 'hg strip' sia quello che voglio. Perché la documentazione online afferma che le filiali non possono essere cancellate? –

+1

Norman: devi abilitare un'estensione come mq o histedit (~ 'git rebase -i') per ottenere un comportamento distruttivo con Mercurial. È quindi possibile rimuovere i rami indesiderati. –

2

Penso che sia chiaramente una decisione pragmatica a seconda della situazione attuale, ad es. la dimensione di una funzionalità/riprogettazione. Penso che le forcelle siano davvero buone per i contributori con ruoli non-ancora-committer per unirsi al team di sviluppatori dimostrando la loro attitudine con un sovraccarico tecnico trascurabile.

14

È necessario eseguire entrambi.

Inizia con la risposta accettata da @Norman: utilizzare un repository con un ramo denominato per release.

Quindi, disporre di un clone per ramo di rilascio per la creazione e il test.

Una nota chiave è che anche se si utilizzano più repository, si dovrebbe evitare di usare transplant per spostare i changeset tra di loro perché 1) cambia hash, e 2) può introdurre bug che sono molto difficili da rilevare quando ci sono conflitti cambia tra il changeset che trapiantate e il ramo di destinazione. Si vuole fare la solita merge invece (e senza premerge: sempre controllare visivamente la fusione), che si tradurrà in quanto @mg detto al termine della sua risposta:

La potenza grafico appare diverso, ma ha la stessa struttura e il risultato finale è lo stesso.

Più verbosely, se si utilizzano più repository, il repository "trunk" (o di default, principale, lo sviluppo, a prescindere) contiene TUTTI di modifiche in TUTTE repository. Ogni repository di release/branch è semplicemente un ramo nel trunk, tutti riuniti in un modo o l'altro nel trunk, fino a quando non si vuole lasciare indietro un vecchio rilascio. Pertanto, l'unica vera differenza tra il repository principale e il repository singolo nello schema di diramazione denominato è semplicemente se i rami sono denominati o meno.

Ciò dovrebbe rendere evidente il motivo per cui ho detto "iniziare con un repository". L'unico repository è l'unico posto in cui avrai mai bisogno di cercare any changeset in qualsiasi release. Puoi ulteriormente etichettare i changeset sui rami di rilascio per il controllo delle versioni. È concettualmente chiaro e semplice e semplifica l'amministrazione di sistema, poiché è l'unica cosa che deve essere sempre disponibile e recuperabile in ogni momento.

Tuttavia, è comunque necessario mantenere un clone per ramo/versione che è necessario creare e testare. È banale come è possibile hg clone <main repo>#<branch> <branch repo> e quindi hg pull nel repository di succursale verrà solo tirare nuovi changeset su tale ramo (oltre a changeset antenato su rami precedenti che sono stati uniti).

Questa configurazione si adatta meglio i kernel linux impegnano modello della unico estrattore (non ci si sente bene ad agire come Lord Linus. Presso la nostra azienda che noi chiamiamo il ruolo integratore), come il repo principale è l'unica cosa che gli sviluppatori devono clonare e l'estrattore deve coinvolgere. La manutenzione dei repository è puramente per la gestione dei rilasci e può essere completamente automatizzata. Gli sviluppatori non devono mai passare da/push ai repository delle filiali.


L'esempio di @ mg è stato rifuso per questa configurazione. Punto di partenza:

[a] - [b] 

Fai un ramo chiamato per una versione, diciamo "1.0", quando si arriva a alpha release. Commit correzioni di bug su di esso:

[a] - [b] ------------------ [m1] 
     \    /
      (1.0) - [x] - [y] 

(1.0) non è un vero e proprio insieme di modifiche dal nome ramo non esiste finché non si commettono. (È possibile eseguire un commit banale, ad esempio aggiungere un tag, per assicurarsi che i rami nominati siano creati correttamente.)

L'unione [m1] è la chiave per questa configurazione. A differenza di un repository per sviluppatori in cui può esistere un numero illimitato di teste, NON si desidera avere più teste nel repository principale (eccetto per il ramo vecchio, dead release come menzionato prima). Pertanto, ogni volta che si dispone di nuovi changeset sui rami di rilascio, è necessario unirli nuovamente al ramo predefinito (o a un ramo di release successivo) immediatamente. Questo garantisce che qualsiasi correzione di bug in una versione sia inclusa anche in tutte le versioni successive.

Nello sviluppo frattempo sul ramo di default continua verso la prossima release:

  ------- [c] - [d] 
     /
[a] - [b] ------------------ [m1] 
     \    /
      (1.0) - [x] - [y] 

E come al solito, è necessario unire le due teste sul ramo di default:

  ------- [c] - [d] ------- 
     /      \ 
[a] - [b] ------------------ [m1] - [m2] 
     \    /
      (1.0) - [x] - [y] 

e questo è il 1.0 clone di diramazione:

[a] - [b] - (1.0) - [x] - [y] 

Ora è un esercizio aggiungere il ramo successivo di rilascio . Se è 2.0, si diramerà definitivamente. Se è 1.1, puoi scegliere di diramare 1.0 o predefinito. Indipendentemente da ciò, ogni nuovo changeset su 1.0 dovrebbe essere prima unito al ramo successivo, quindi a default. Questo può essere fatto automaticamente se non c'è conflitto, risultando in una semplice fusione vuota.


Spero che l'esempio chiarisca i miei punti precedenti.In sintesi, i vantaggi di questo approccio sono:

  1. Un singolo repository autorevole che contiene il changeset completo e la cronologia delle versioni.
  2. Gestione di rilascio chiara e semplificata.
  3. Flusso di lavoro chiaro e semplificato per sviluppatori e integratori.
  4. Facilita le iterazioni del flusso di lavoro (revisioni del codice) e l'automazione (unione automatica vuota).

hg update per sé does this: il main repo contiene il difetto ed rami stabili, e la stable repo è il clone ramo stabile. Tuttavia, non utilizza il ramo versione, poiché i tag di versione lungo il ramo stabile sono abbastanza buoni per i suoi scopi di gestione del rilascio.

0

Mi piacerebbe davvero sconsigliare l'utilizzo di rami nominati per le versioni. Questo è davvero ciò che i tag sono per. I rami con nome sono pensati per deviazioni di lunga durata, come un ramo stable.

Quindi perché non usare solo i tag? Un esempio di base:

  • sviluppo avviene su un solo ramo
  • Ogni volta che si crea un comunicato, si etichetta di conseguenza
  • sviluppo continua solo da lì
  • Se si dispone di alcuni bug da sistemare (o qualunque cosa) in una certa release, basta aggiornare al suo tag, apportare le modifiche e commit

Ciò creerà una nuova testa senza nome sul ramo default, ovvero. un ramo anonimo, che sta perfettamente bene in hg. È quindi possibile in qualsiasi momento unire il bugfix per tornare alla traccia di sviluppo principale. Non c'è bisogno di rami nominati.

+0

Questo dipende molto dal tuo processo. Un'app Web, ad esempio, funziona bene con una gerarchia di rami stabile/testing/devel. Quando creiamo software desktop, solitamente abbiamo un ramo di sviluppo (predefinito) e da uno a tre (!) Rami diversi nella manutenzione. È difficile prevedere quando potremmo aver bisogno di rivisitare un ramo, e c'è una certa eleganza nell'avere un ramo in una versione major.minor. –

Problemi correlati