Hg: Come fare un rebase come git's rebase


207

In Git posso fare questo:

1. Inizia a lavorare su una nuova funzione:
$ git co -b newfeature-123 # (un ramo di sviluppo di funzionalità locali)
fai qualche commit (M, N, O)

maestro A --- B --- C
                \
nuova funzione-123 M --- N --- O

2. Estrarre nuove modifiche dal master upstream:
$ git pull
(master aggiornato con ff-commit)

master A --- B --- C --- D --- E --- F
                \
nuova funzione-123 M --- N --- O

3. Rebase off master in modo che la mia nuova funzionalità 
può essere sviluppato rispetto alle ultime modifiche a monte:
(da newfeature-123)
$ git rebase master

master A --- B --- C --- D --- E --- F
                            \
nuova funzione-123 M --- N --- O


Voglio sapere come fare la stessa cosa in Mercurial, e ho cercato il web per una risposta, ma il meglio che ho potuto trovare è stato: git rebase - posso farlo

Questo link fornisce 2 esempi:
1. Devo ammettere che questo: (sostituendo le revisioni dell'esempio con quelle del mio esempio)

hg up -CF  
hg branch -f newfeature-123  
hg trapianto -a -b nuova funzionalità-123 

non è poi così male, tranne per il fatto che lascia l'MNO pre-rebase come testa non unita e crea 3 nuovi commit M ', N', O 'che li rappresentano ramificando la linea principale aggiornata.

Fondamentalmente il problema è che finisco con questo:

master A --- B --- C --- D --- E --- F
                \ \
newfeature-123 \ M '--- N' --- O '
                  \
nuova funzione-123 M --- N --- O

ciò non va bene perché lascia dietro di sé impegni locali e indesiderati che dovrebbero essere eliminati.

  1. L'altra opzione dallo stesso link è
hg qimport -r M: O
hg qpop -a
hg up F
hg branch newfeature-123
hg qpush -a
hg qdel -r qbase: qtip

e questo si traduce nel grafico desiderato:

master A --- B --- C --- D --- E --- F
                            \
nuova funzione-123 M --- N --- O

ma questi comandi (tutti e 6!) sembrano molto più complicati di

$ git rebase master

Voglio sapere se questo è l'unico equivalente in Hg o se è disponibile un altro modo semplice come Git.


7
"questo non va bene perché lascia dietro di sé impegni locali e indesiderati che dovrebbero essere eliminati". - In realtà, Git fa la stessa cosa. Non modifica né rimuove i commit nel ramo originale, ne crea solo di nuovi che applicano lo stesso set di modifiche in cima al master. Puoi ancora accedere a quelli vecchi usando git refloge non sono completamente spariti fino a quando non vengono raccolti i rifiuti. Se vuoi tenerli in giro in un ramo con nome in modo da non dover usare il reflog, fallo git branch feature-123_originalprima di ripassare.
MatrixFrog il

5
Domanda casuale: hai asciugato tu stesso il changeset / branch o c'è uno strumento che lo fa?
Amir Rachum,

2
Li ho fatti da solo con TextWrangler impostato su "sovrascrivi".
jpswain,

Risposte:


235

VonC ha la risposta che stai cercando , l'estensione Rebase. Vale comunque la pena spendere un secondo o due a pensare al motivo per cui né mq né rebase sono abilitati di default in mercurial: perché mercurial è tutto incentrato su cambiamenti indelebili. Quando lavoro nel modo che stai descrivendo, che è quasi quotidiano, ecco lo schema che prendo:

1. Start working on a new feature:
$ hg clone mainline-repo newfeature-123
do a few commits (M, N, O)

master A---B---C
                \
newfeature-123   M---N---O

2. Pull new changes from upstream mainline:
$ hg pull

master A---B---C---D---E---F
                \
newfeature-123   M---N---O

3. merge master into my clone so that my new feature 
can be developed against the latest upstream changes:
(from newfeature-123)
$ hg merge F

master A---B---C---D---E---F
                \           \
newfeature-123   M---N---O---P

e questo è davvero tutto ciò che è necessario. Finisco con un nuovo clone 123 di funzionalità che posso facilmente riportare alla linea principale quando ne sono soddisfatto. Ancora più importante, tuttavia, non ho mai cambiato la storia . Qualcuno può guardare i miei cset e vedere su cosa sono stati originariamente codificati e come ho reagito ai cambiamenti nella linea principale durante il mio lavoro. Non tutti pensano che abbia valore, ma sono fermamente convinto che sia compito del controllo del codice sorgente mostrarci non ciò che avremmo voluto che fosse accaduto, ma ciò che effettivamente accadeva: ogni morto e ogni refattore dovrebbero lasciare una traccia indelebile e ribellarsi e altre tecniche di modifica della cronologia lo nascondono.

Ora vai a scegliere la risposta di VonC mentre metto via la mia soapbox. :)


18
Nota: ovviamente, Git non ti consente esattamente di riscrivere la cronologia, ma di crearne semplicemente una nuova ( utcc.utoronto.ca/~cks/space/blog/tech/GitNewHistory ). Aggiungendo RebaseExtension, Mercurial fornisce lo stesso modo conveniente esatto per sostituire una vecchia cronologia con una nuova. Perché? Perché una fusione non è sempre la risposta giusta, specialmente quando il vostro changeset dovrebbe essere visto come evoluzioni sopra F, e non il contrario (P unito sopra O)
VonC

19
VonC, sono d'accordo, una fusione non è sempre la scelta giusta, ma penso che la differenza sia in ciò che si vuole che la propria storia VCS sia in grado di dire loro. Penso che la storia dovrebbe sempre essere in grado di rispondere a domande del tipo "In un primo momento ho cercato di integrarlo che non ha funzionato e che all'epoca pensavo fosse inutile". Gli scienziati tengono in mano i giornali di bordo con pagine numerate e gli ingegneri del software AFAIC dovrebbero salvare ogni byte che abbiano mai digitato. Riscrivere la storia, persino impostare la parentela, ma sicuramente CollapseExtension, HistEdit, ecc. È totalmente una questione di scelta personale.
Ry4an Brase,

14
+1 per scelta personale. Di conseguenza, con Git uso rebase per banali divergenze e unisco per non banale. Ciò mi consente di preservare la cronologia di unione in cui ritengo sia importante, ma mantenere il registro pulito e lineare per la maggior parte del tempo.
Kos,

16
Inoltre faccio tonnellate di rebase interattivi perché in primo luogo tendo a fare un sacco di piccoli commit, quindi li unisco, li etichetta e li pulisco, quindi li ricollego con (o rebase sopra) il ramo principale. Mi piace codificare e gestire le modifiche come passaggi separati.
Kos,

6
La filosofia "salva ogni byte lungo il percorso di sviluppo" non è davvero appropriata per i grandi progetti open source, in cui i partecipanti propongono una serie di patch, quindi li rielaborano in base al feedback dei manutentori. Quindi alla fine il repository principale del progetto ha solo la versione corretta di tutte le modifiche, con tutte le modifiche correlate in un unico commit. git interattivo rebase è davvero buono per ripulire i cambiamenti di lavoro in una sequenza di commit che non lascia l'albero rotto, e con i messaggi di commit che riflettono ciò che decidi di dire dopo aver finito di lavorare sull'intera faccenda.
Peter Cordes,

104

Potresti cercare Rebase Extension . (implementato come parte del SummerOfCode 2008 )

In questi casi può essere utile "scollegare" le modifiche locali, sincronizzare il repository con il mainstream e quindi aggiungere le modifiche private in cima alle nuove modifiche remote. Questa operazione si chiama rebase.

Arrivare da :

testo alternativo

per:

testo alternativo


Come commentato di seguito da steprobe :

Nel caso in cui non stai inserendo le modifiche e hai i due rami nel tuo repository, puoi fare ( usandokeepbranches ):

hg up newfeature-123 
hg rebase -d master --keepbranches

(--keepbranches : Eredita il nome del ramo originale.)

Mojca menziona:

Mi piace usare hg rebase --source {L1's-sha} --dest {R2's-sha}, ma non sapevo di poterlo aggiungere --keepbranchesalla fine.

Come illustrato di seguito da Jonathan Blackburn :

 hg rebase -d default --keepbranches

1
Ho esaminato l'estensione Rebase, ma non mi è ancora chiaro. Potresti spiegare i passaggi per fare ciò che ho descritto sopra?
jpswain,

6
Nel caso in cui non stai inserendo le modifiche e hai i due rami nel tuo repository, puoi fare: hg up newfeature-123seguito dahg rebase -d master --keepbranches
steprobe

2
Credo che non ci sia nulla di sbagliato in rebase, è solo una questione di scelta. Il problema è che rebase viene abusato e sto con @ Ry4an su questo non riscrivo la storia in modo da poter sapere cosa succede e quando.
Jorge Vargas,

@steprobe: grazie per il suggerimento per l'utilizzo di --keepbranches. Mi piace usare hg rebase --source {L1's-sha} --dest {R2's-sha}, ma non sapevo di poterlo aggiungere --keepbranchesalla fine. Questo è menzionato in altre risposte di rango inferiore, ma sarebbe bello scriverlo esplicitamente anche in questa risposta.
Mojca,

@Mojca Nessun problema. Ho modificato la risposta di conseguenza.
VonC,

44

Supponendo che tu abbia una moderna installazione Hg, puoi semplicemente aggiungere:

[extensions]
rebase = 

a ~ / .hgrc.

Quindi è possibile utilizzare i comandi hg rebase, hg pull --rebaseo hg help rebase.


2
Solo per aggiungere a questo in termini di comando quindi è necessario eseguire sarebbe: hg rebase -s o -d f
Simple-Solution

21

Non credo che le risposte di cui sopra raggiungano l'obiettivo del PO, che era quello di mantenere il suo ramo delle attività, appena ribadito contro un punto successivo sul ramo padre.

Diciamo che inizio con questo grafico (generato usando l'estensione graphlog. Amore serio per i geek per graphlog).

@  9a4c0eb66429 Feature 3 commit 2 tip feature3
|
| o  af630ccb4a80 default againagainagain  
| |
o |  98bdde5d2185 Feature 3 branch commit 1  feature3
|/
o  e9f850ac41da foo   

Se mi trovo sul ramo feature3 e voglio rifarlo dal commit di nuovo, capisco che avrei eseguito hg rebase -d default. Questo ha il seguente risultato:

@  89dada24591e Feature 3 commit 2 tip 
|
o  77dcce88786d Feature 3 branch commit 1  
|
o  af630ccb4a80 default againagainagain  
|
o  e9f850ac41da foo  

Missione compiuta? Io non la penso così. Il problema è che quando gli commit sul ramo feature3 sono stati riprogrammati nuovamente, il ramo feature3 è stato eliminato . I miei commit sono stati spostati nel ramo predefinito, che era quello che stavo cercando di evitare in primo luogo.

In Git, il risultato sarebbe simile al seguente:

@  9a4c0eb66429 Feature 3 commit 2 tip
|
o  98bdde5d2185 Feature 3 branch commit 1 **feature3**
|
o  af630ccb4a80 default againagainagain
|
o  e9f850ac41da foo

Si noti che il ramo feature3 esiste ancora, i due commit sono ancora sul ramo feature3 e non sono visibili per impostazione predefinita. Senza preservare il ramo delle attività, non vedo come questo sia funzionalmente diverso da una fusione.

AGGIORNAMENTO : ho scoperto la --keepbranchesbandiera supportata da hg rebase e sono felice di segnalare che tutto è okey-dokey. Usando hg rebase -d default --keepbranches, replica esattamente il comportamento Git che desideravo. Un paio di alias più tardi e mi sto ribellando come se non fossero affari di nessuno.


7
Ho appena scoperto la bandiera --keepbranches su rebase. Problema risolto. Se questo fosse il mio codice, lo renderei predefinito, ma sono solo io.
Jonathan Blackburn,

1
Penso che sarebbe utile se tu aggiungessi quel po 'di informazioni alla tua risposta sopra - mi ci è voluto un po' per trovarlo.
HansMari,

3

Dal momento che alcune persone hanno reagito dicendo che pensano che sia buono mantenere ogni iterazione di tutto, sottolineo che per progetti open source più grandi, accettare cambiamenti pieni di fusioni e iterazioni di sviluppo porterebbe a una confusa cronologia delle revisioni della mainline e rendere la cronologia delle revisioni meno utile per vedere come è arrivata la versione corrente.

Funziona bene quando le modifiche inviate vengono esaminate da persone che non le hanno scritte, prima che vengano accettate, quindi le modifiche che entrano nella mainline sono generalmente sottoposte a debug e funzionanti. Quindi quando torni indietro all'origine di una linea, vedi tutti i cambiamenti che ne conseguono, non un punto nel mezzo dello sviluppo del cambiamento di cui fa parte.

La pagina dei contributori x265 spiega come eseguire nuovamente il commit di una serie di modifiche a cui stai lavorando, per prepararle per l'invio al progetto x265. (Compreso l'uso di TortoiseHG per eseguire il commit di alcune ma non tutte le modifiche in un singolo file, come lo hunk diff stage / unstage di git gui per commit).

Il processo consiste nell'ottenere hg aggiornato al suggerimento upstream e quindi ripristinare tutte le modifiche nella directory di lavoro. Dodici quelli che non fanno parte di ciò che si desidera inviare, quindi suddividere il resto in altrettanti commit separati appropriati, con bei messaggi di commit.

Immagino che dovresti copiare / incollare e quindi modificare i messaggi di commit delle precedenti iterazioni di un patchset che stai rivedendo. O forse potresti innestare i tuoi vecchi commit (cherry-pick in git language), e poi modificarli uno per uno, per ottenere i tuoi vecchi messaggi di commit come punto di partenza per la modifica.

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.