Come faccio a ripristinare / risincronizzare dopo che qualcuno ha inviato un rebase o un ripristino a un ramo pubblicato?


88

Abbiamo tutti sentito che non si dovrebbe mai ribasare il lavoro pubblicato, che è pericoloso, ecc. Tuttavia, non ho visto nessuna ricetta pubblicata su come affrontare la situazione nel caso in cui venga pubblicato un rebase .

Ora, tieni presente che questo è realmente fattibile solo se il repository viene clonato solo da un gruppo di persone noto (e preferibilmente piccolo), in modo che chiunque spinga il rebase o il reset possa avvisare tutti gli altri che dovranno prestare attenzione la prossima volta fetch (!).

Una soluzione ovvia che ho visto funzionerà se non hai commit locali fooe viene ribasata:

git fetch
git checkout foo
git reset --hard origin/foo

Questo getterà semplicemente via lo stato locale di fooa favore della sua storia come per il repository remoto.

Ma come si affronta la situazione se si sono commessi cambiamenti locali sostanziali su quel ramo?


+1 per la ricetta semplice del caso. È ideale per la sincronizzazione personale tra macchine, soprattutto se hanno sistemi operativi diversi. È qualcosa che dovrebbe essere menzionato nel manuale.
Philip Oakley

La ricetta ideale per la sincronizzazione personale è git pull --rebase && git push. Se lavori mastersolo su , allora questo quasi immancabilmente farà la cosa giusta per te, anche se hai ribasato e spinto dall'altra parte.
Aristotele Pagaltzis

Poiché sto sincronizzando e sviluppando tra un PC e una macchina Linux, trovo che l'utilizzo di un nuovo ramo per ogni rebase / aggiornamento funzioni bene. Uso anche la variante git reset --hard @{upstream}ora che conosco l'incantesimo di refspec magico per "dimentica quello che ho / avevo, usa quello che ho recuperato dal telecomando". Vedi il mio commento finale su stackoverflow.com/a/15284176/717355
Philip Oakley

Sarai in grado, con Git2.0, di trovare la vecchia origine del tuo ramo (prima che il ramo a monte fosse riscritto con a push -f): vedi la mia risposta sotto
VonC

Risposte:


75

Tornare in sincronia dopo un rebase pushed non è davvero così complicato nella maggior parte dei casi.

git checkout foo
git branch old-foo origin/foo # BEFORE fetching!!
git fetch
git rebase --onto origin/foo old-foo foo
git branch -D old-foo

Cioè. prima imposti un segnalibro per dove si trovava originariamente il ramo remoto, poi lo usi per riprodurre i tuoi commit locali da quel punto in poi sul ramo remoto ribasato.

Ribasare è come la violenza: se non risolve il tuo problema, ne hai solo bisogno di più. ☺

Puoi farlo senza il segnalibro, ovviamente, se cerchi l' origin/fooID commit pre-rebase e lo usi.

Questo è anche il modo in cui gestisci la situazione in cui ti sei dimenticato di creare un segnalibro prima di scaricarlo. Niente è perso: devi solo controllare il reflog per il ramo remoto:

git reflog show origin/foo | awk '
    PRINT_NEXT==1 { print $1; exit }
    /fetch: forced-update/ { PRINT_NEXT=1 }'

Questo stamperà l'ID del commit che origin/foopuntava prima del recupero più recente che ha cambiato la sua cronologia.

Puoi quindi semplicemente

git rebase --onto origin/foo $commit foo

11
Nota rapida: penso che sia abbastanza intuitivo, ma se non conosci bene awk ... quella git reflog show origin/fooriga sta solo guardando attraverso l'output di per la prima riga che dice "fetch: forced-update"; questo è ciò che git registra quando un recupero fa sì che il ramo remoto faccia qualcosa di diverso dall'avanzamento veloce. (Potresti farlo anche a mano - l'aggiornamento forzato è probabilmente la cosa più recente.)
Cascabel

2
Non è per niente come la violenza. La violenza è occasionalmente divertente
Iolo

5
@iolo Vero, ribasare è sempre divertente.
Dan Bechard

1
Come la violenza, evita quasi sempre di ribasare. Ma hai un'idea di come.
Bob Stein

2
Bene, evita di spingere un rebase dove altri saranno influenzati.
Aristotele Pagaltzis

11

Direi che il ripristino dalla sezione rebase upstream della pagina man git-rebase copre praticamente tutto questo.

Non è davvero diverso dal recuperare dal tuo rebase: sposti un ramo e ribassi tutti i rami che lo avevano nella loro storia nella nuova posizione.


4
Ah, è così. Ma anche se ora capisco cosa dice, non l'avrei fatto prima, prima di capirlo da solo. E non esiste una ricetta per un libro di cucina (forse è giusto così in tale documentazione). Presenterò anche che chiamare il "caso duro" duro è FUD. Affermo che la storia riscritta è banalmente gestibile alla scala della maggior parte dello sviluppo interno. Il modo superstizioso con cui viene sempre trattato questo argomento mi infastidisce.
Aristotele Pagaltzis

4
@Aristotle: Hai ragione sul fatto che è molto gestibile, dato che tutti gli sviluppatori sanno come usare git e che puoi comunicare efficacemente con tutti gli sviluppatori. In un mondo perfetto, quella sarebbe la fine della storia. Ma molti progetti là fuori sono abbastanza grandi che un rebase upstream è davvero una cosa spaventosa. (E poi ci sono posti come il mio posto di lavoro, dove la maggior parte degli sviluppatori non ha mai nemmeno sentito parlare di un rebase.) Penso che la "superstizione" sia solo un modo per fornire il consiglio più sicuro e generico possibile. Nessuno vuole essere quello che causa un disastro nel repo di qualcun altro.
Cascabel

2
Sì, capisco il motivo. E sono pienamente d'accordo. Ma c'è un'enorme differenza tra "non provarci se non capisci le conseguenze" e "non dovresti mai farlo perché è malvagio", e solo questo mi mette in discussione. È sempre meglio istruire che instillare paura.
Aristotele Pagaltzis

@Aristotle: d'accordo. Cerco di tendere al fine di "assicurarti di sapere cosa stai facendo", ma soprattutto online, cerco di dargli abbastanza peso in modo che un visitatore occasionale di Google ne prenda nota. Hai ragione, molto probabilmente dovrebbe essere attenuato.
Cascabel

11

A partire con git 1.9 / 2.0 Q1 2014, non dovrà contrassegnare la provenienza ramo precedente prima rebasing sul ramo monte riscritto, come descritto in Aristotele Pagaltzis 's risposta :
See commettere 07d406b e commettere d96855f :

Dopo aver lavorato sul topicramo creato con git checkout -b topic origin/master, la storia del ramo di monitoraggio remoto origin/masterpotrebbe essere stata riavvolta e ricostruita, portando a una storia di questa forma:

                   o---B1
                  /
  ---o---o---B2--o---o---o---B (origin/master)
          \
           B3
            \
             Derived (topic)

dove origin/masterusato per indicare a commit B3, B2, B1e ora punta a B, e la vostra topicfiliale è stato avviato su di esso indietro quando origin/masterera a B3.

Questa modalità utilizza il reflog di origin/masterper trovare B3come punto di biforcazione, in modo che topicpossa essere ribasato in cima a aggiornatoorigin/master da:

$ fork_point=$(git merge-base --fork-point origin/master topic)
$ git rebase --onto origin/master $fork_point topic

Ecco perché il git merge-basecomando ha una nuova opzione:

--fork-point::

Trova il punto in cui un ramo (o qualsiasi storia che porta a <commit>) si è biforcato da un altro ramo (o qualsiasi riferimento) <ref>.
Questo non si limita a cercare l'antenato comune dei due commit, ma tiene anche conto del reflog di <ref>per vedere se la storia che porta a <commit>biforcarsi da una precedente incarnazione del ramo<ref> .


Il git pull --rebasecomando " " calcola il punto di biforcazione del ramo che viene ribasato usando le voci reflog del baseramo " " (tipicamente un ramo di tracciamento remoto) su cui si basava il lavoro del ramo, al fine di far fronte al caso in cui la "base" il ramo è stato riavvolto e ricostruito.

Ad esempio, se la cronologia assomiglia a dove:

  • la punta attuale del " base" ramo è a B, ma in precedenza fetch ha osservato che la sua punta usato per essere B3e poi B2e poi B1 prima di arrivare alla corrente impegnarsi, e
  • il ramo che viene ribasato sull'ultima "base" è basato sul commit B3,

si cerca di trovare B3passando attraverso l'uscita di " git rev-list --reflog base" (vale a dire B, B1, B2, B3) finché non trova un commit che è un antenato della punta corrente " Derived (topic)".

Internamente, possiamo get_merge_bases_many()calcolarlo con una sola volta.
Vorremmo un merge-base between Derivede un merge commit fittizio che risulterebbe unendo tutti i suggerimenti storici di " base (origin/master)".
Quando esiste un commit di questo tipo, dovremmo ottenere un singolo risultato, che corrisponde esattamente a una delle voci di reflog di " base".


Git 2.1 (Q3 2014) aggiungerà rendere questa funzionalità più robusta a questo: vedere commit 1e0dacd di John Keeping ( johnkeeping)

gestire correttamente lo scenario in cui abbiamo la seguente topologia:

    C --- D --- E  <- dev
   /
  B  <- master@{1}
 /
o --- B' --- C* --- D*  <- master

dove:

  • B'è una versione fissa di Bche non è identica alla patch B;
  • C*e D*sono identici alla patch Ce Drispettivamente e sono in conflitto testuale se applicati nell'ordine sbagliato;
  • Edipende testualmente da D.

Il risultato corretto git rebase master devè che Bè identificato come forcella punto deve master, in modo che C, D, Esono i commit che devono essere riprodotti su master; ma Ce Dsono identici alla patch C*e D*e quindi possono essere eliminati, in modo che il risultato finale sia:

o --- B' --- C* --- D* --- E  <- dev

Se il fork-point non viene identificato, il prelievo Bsu un ramo contenente B'risultati in un conflitto e se i commit identici alla patch non sono identificati correttamente, il prelievo Csu un ramo contenente D(o equivalentemente D*) si traduce in un conflitto.


La " --fork-point" modalità di " git rebase" è regredita quando il comando è stato riscritto in C nell'era 2.20, che è stato corretto con Git 2.27 (Q2 2020).

Vedi commit f08132f (09 dic 2019) di Junio ​​C Hamano ( gitster) .
(Fuso da Junio ​​C Hamano - gitster- in commit fb4175b , 27 marzo 2020)

rebase: --fork-pointcorrezione della regressione

Firmato da: Alex Torok
[jc: rinnovato la correzione e utilizzato i test di Alex]
Autografato da: Junio ​​C Hamano

" git rebase --fork-point master" funzionava bene, come si chiamava internamente " git merge-base --fork-point" che sapeva come gestire il refname breve e lo dwim al refname completo prima di chiamare la get_fork_point()funzione sottostante .

Questo non è più vero dopo che il comando è stato riscritto in C, poiché la sua chiamata interna fatta direttamente a get_fork_point()non dwim un breve ref.

Sposta la logica "dwim the refname" alla logica refname completa "usata in" git merge-base "alla get_fork_point()funzione sottostante , in modo che l'altro chiamante della funzione nell'implementazione di" git rebase "si comporti allo stesso modo per correggere questa regressione.


1
Si noti che un --force git push può ora (Git 1.8.5) essere fatto con maggiore prudenza: stackoverflow.com/a/18505634/6309
VonC
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.