Git push rifiutato dopo il rebase del ramo della funzione


919

OK, ho pensato che fosse uno scenario git semplice, cosa mi sto perdendo?

Ho un masterramo e un featureramo. Faccio un po 'di lavoro master, alcuni su feature, e poi altri ancora su master. Finisco con qualcosa del genere (l'ordine lessicografico implica l'ordine dei commit):

A--B--C------F--G  (master)
       \    
        D--E  (feature)

Non ho problemi a git push origin mastermantenere masteraggiornato il telecomando , né con git push origin feature(quando acceso feature) per mantenere un backup remoto per il mio featurelavoro. Fino ad ora, stiamo bene.

Ma ora voglio ribadire featurein cima agli F--Gimpegni sul master, quindi io git checkout featuree git rebase master. Ancora buono. Ora abbiamo:

A--B--C------F--G  (master)
                 \
                  D'--E'  (feature)

Problema: nel momento in cui voglio eseguire il backup del nuovo rebased featureramificato git push origin feature, la spinta viene rifiutata poiché l'albero è cambiato a causa del rebasing. Questo può essere risolto solo con git push --force origin feature.

Odio usare --forcesenza essere sicuro di averne bisogno. Quindi ne ho bisogno? Il rebasing implica necessariamente che il prossimo pushdovrebbe essere --forcecompleto?

Questo ramo delle funzionalità non è condiviso con nessun altro sviluppatore, quindi non ho alcun problema di fatto con la spinta forzata, non perderò alcun dato, la domanda è più concettuale.

Risposte:


682

Il problema è che si git pushpresume che il ramo remoto possa essere inoltrato rapidamente al proprio ramo locale, cioè che tutta la differenza tra i rami locali e remoti sia nel locale con alcuni nuovi commit alla fine in questo modo:

Z--X--R         <- origin/some-branch (can be fast-forwarded to Y commit)
       \        
        T--Y    <- some-branch

Quando esegui i git rebasecommit, D ed E vengono applicati alla nuova base e vengono creati nuovi commit. Ciò significa che dopo rebase hai qualcosa del genere:

A--B--C------F--G--D'--E'   <- feature-branch
       \  
        D--E                <- origin/feature-branch

In tale situazione, la filiale remota non può essere inoltrata rapidamente al locale. Tuttavia, in teoria il ramo locale può essere unito in remoto (ovviamente non è necessario in quel caso), ma poiché git pushesegue solo un avanzamento rapido si fonde genera ed errore.

E quale --forceopzione fa è semplicemente ignorare lo stato del ramo remoto e impostarlo sul commit che stai spingendo in esso. Quindi git push --force origin feature-branchsemplicemente sostituisce origin/feature-branchcon locale feature-branch.

A mio avviso, la reimpostazione dei rami delle funzioni mastere il loro reinserimento forzato nel repository remoto è OK purché tu sia l'unico che lavora su quel ramo.


68
Ad essere onesti, tirando e fondendo la versione originale del ramo delle caratteristiche in una sorta di rebased si elimina l'intera idea di rebasing.
KL-7,

24
Forse non ti ho capito bene, ma se si estrae il ramo della funzionalità, lo si ricolloca sul nuovo ramo principale, non è possibile rimandarlo indietro senza forzare, poiché la versione remota del ramo della funzione non può essere portata avanti rapidamente al nuovo (riequilibrata) versione del ramo funzionalità. Questo è esattamente ciò che OP ha descritto nella sua domanda. Se dopo il rebasing, ma prima di eseguire il push, lo fai git pull feature-branch, questo pull genererà un nuovo commit di unione (unendo le versioni remote e locali del ramo della funzione). Quindi o ottieni un'unione superflua dopo il rebasing o spingi con --force.
KL-7,

6
Ah, penso di averlo capito. Stai descrivendo lo stesso approccio della risposta di Mark Longair. Ma genera un commit di unione. Potrebbe essere utile in alcuni casi, ma uso rebase principalmente nei miei rami delle caratteristiche (quindi push --forcenon è un problema) per mantenere la cronologia dei commit lineare senza alcun commit di merge.
KL-7,

11
Il problema con "force-push" è che puoi davvero "perdere cose" (commit precedenti), cosa che normalmente non dovrebbe MAI essere possibile in nessun sistema di controllo versione ➪ Per questo motivo almeno un ramo "master-ish" dovrebbe avere le impostazioni per non accettare le spinte forzate , per limitare potenziali danni. (Nomina uno dei seguenti: impiegati scontrosi / licenziati, propria idiozia, "decisioni" stanche e oberate di lavoro ...).
Frank Nocke,

13
--force-with-leasecome suggerito da @hardev è un'ottima opzione
augustorsouza

466

Invece di usare -f o --force gli sviluppatori dovrebbero usare

--force-with-lease

Perché? Perché controlla il ramo remoto per le modifiche che è assolutamente una buona idea. Immaginiamo che James e Lisa stiano lavorando allo stesso ramo di funzioni e Lisa abbia fatto un commit. James ora rinnova il suo ramo locale e viene respinto quando tenta di spingere. Ovviamente James pensa che ciò sia dovuto al ribasso e usa - force e riscriverebbe tutti i cambiamenti di Lisa. Se James avesse usato - force-with-lease, avrebbe ricevuto un avvertimento in merito al fatto che sono stati commessi da qualcun altro. Non vedo perché qualcuno dovrebbe usare --force invece di --force-with-lease quando spinge dopo un rebase.


33
Ottima spiegazione git push --force-with-leasemi ha salvato un sacco.
ckib16,

5
Questo è un commento utile, ma in realtà non è una risposta alla domanda.
Dallin,

4
Questa è la risposta, ribellarsi a padroneggiare / sviluppare crea un problema, questo è esattamente il motivo per cui esiste la forza con contratto di locazione.
Tamir Daniely,

3
Questa dovrebbe essere la risposta accettata. Risolve esattamente il problema descritto: forzare la spinta senza forzare se nel frattempo qualcun altro si è impegnato.
Luzian,

3
Penso che sia la risposta accettata sia questa rispondano alla domanda. La risposta accettata spiega perché è necessario forzare. E questo spiega perché --force-with-leaseaffronta la preoccupazione dell'uso--force
Jeff Appareti il

48

Vorrei invece usare "checkout -b" ed è più facile da capire.

git checkout myFeature
git rebase master
git push origin --delete myFeature
git push origin myFeature

quando si elimina si impedisce di inserire un ramo in uscita che contiene ID SHA diverso. In questo caso sto eliminando solo il ramo remoto.


6
Funziona benissimo, specialmente se il tuo team ha un hook git che rifiuta tutti i comandi git push --force.
Ryan Thames,

1
grazie per quello ha funzionato bene. Ecco maggiori dettagli su quello che ho letto per capire meglio. Questo è molto utile quando non vuoi o non puoi fare una spinta forzata. Eliminazione di filiali remote e rebasing
RajKon,

5
Questo ha lo stesso risultato di push --force, quindi è solo un modo per aggirare un repository git che impedisce --force. Come tale, non penso che questa sia mai una buona idea - o il repo lo consente push --force, o per una buona ragione lo disabilita. La risposta di Nabi è più appropriata se --forceè disabilitata sul repository remoto, poiché non ha il rischio di perdere commit da altri sviluppatori o causare problemi in altro modo.
Logan Pickup,

19

Una soluzione a questo è di fare ciò di msysgit merge rebasing script fa - dopo la rebase, unione nella vecchia testa di featurecon -s ours. Si finisce con il grafico di commit:

A--B--C------F--G (master)
       \         \
        \         D'--E' (feature)
         \           /
          \       --
           \    /
            D--E (old-feature)

... e la tua spinta featuresarà un avanzamento veloce.

In altre parole, puoi fare:

git checkout feature
git branch old-feature
git rebase master
git merge -s ours old-feature
git push origin feature

(Non testato, ma penso che sia giusto ...)


26
Credo che il motivo più comune per l'utilizzo git rebase(anziché masterricollegarsi al ramo delle caratteristiche) sia la cronologia delle commit pulite e lineari. Con il tuo approccio, la storia peggiora ulteriormente. E poiché il rebasing crea nuovi commit senza alcun riferimento alle loro versioni precedenti, non sono nemmeno sicuro che il risultato di questa unione sarà adeguato.
KL-7,

6
@ KL-7: Il punto centrale merge -s oursè che aggiunge artificialmente un riferimento parent alla versione precedente. Certo, la storia non sembra pulita, ma l'interrogante sembra essere particolarmente infastidito dal dover forzare la spinta del featureramo, e questo lo aggira. Se vuoi rifare, è più o meno l'uno o l'altro. :) Più in generale, penso che sia interessante che il progetto msysgit faccia questo ....
Mark Longair,

@ KL-7: Per inciso, ho fatto +1 sulla tua risposta, che è chiaramente quella giusta - ho solo pensato che potrebbe essere interessante.
Mark Longair,

È sicuramente interessante, almeno per me. Grazie. Ho già visto la oursstrategia, ma ho pensato che si applica solo alla situazione di conflitto risolvendoli automaticamente utilizzando i cambiamenti nel nostro ramo. Si è scoperto che funziona in modo diverso. E lavorare in questo modo è molto utile se hai bisogno di una versione ridisegnata (ad es. Per il manutentore del repo per applicarlo in modo pulito master) ma vuoi evitare la forzatura della spinta (se molti altri ppl per qualche ragione usano il tuo ramo di funzionalità).
KL-7,

15

Può darsi che non ci sia o meno un solo sviluppatore su questo ramo, che ora (dopo il rebase) non è in linea con l'origine / funzione.

Come tale, suggerirei di utilizzare la seguente sequenza:

git rebase master
git checkout -b feature_branch_2
git push origin feature_branch_2

Sì, nuova filiale, questo dovrebbe risolverlo senza --force, che penso sia generalmente un grosso svantaggio.


3
Mi dispiace dirlo ma: "Continuare a generare rami" per evitare di forzare quelli esistenti non aiuta "sviluppatori di funzionalità solitari" (che possono ignorare) né più persone che lavorano su un ramo di funzionalità (è necessario comunicare "incremento" di quel ramo e dire spostarsi, gente). - È più simile al versioning manuale (“thesis_00.doc, thesis_01.doc, ...”), all'interno di un sistema di versioning ...
Frank Nocke,

2
Inoltre, questo non aiuta quando hai un PR di github aperto su un nome di ramo, dovresti creare un nuovo PR per il nuovo nome di ramo che hai premuto.
gprasant,

1
@frankee Half true dalla mia esperienza. per uno sviluppatore solitario, sì, solo forzare la spinta è abbastanza facile, ma è l'abitudine che potrebbe morderti in seguito. + un nuovo sviluppatore si è appena unito? o forse qualche sistema CI che non sta usando --hard reset? per una squadra che collabora, penso che comunicare il nome della nuova filiale sia abbastanza facile, anche questo può essere facilmente scritto + per una squadra, suggerirei di rifare localmente o quando la filiale è pronta per la fusione, non durante il lavoro quotidiano , il commit extra è meno problematico rispetto alla gestione di conflitti rebase / merge di conseguenza.
JAR.JAR.beans

@gprasant per PR, ancora una volta, penso che sarebbe sbagliato rifare, vorrei davvero vedere i singoli commit con le correzioni PR. Un rebase (squash) dovrebbe avvenire solo più tardi come parte dell'unione da padroneggiare e quando il PR è fatto e pronto (quindi non è necessario aprire nuovi PR).
JAR.JAR.beans

13

Il mio modo di evitare la spinta della forza è quello di creare un nuovo ramo e continuare a lavorare su quel nuovo ramo e, dopo un po 'di stabilità, rimuovere il vecchio ramo che è stato riformato:

  • Rebasing locale del ramo estratto
  • Diramazione dal ramo rinnovato a un nuovo ramo
  • Spingendo quel ramo come nuovo ramo sul telecomando. ed eliminando il vecchio ramo sul telecomando

1
Perché nessun amore per questa opzione? È sicuramente il più pulito, semplice, sicuro.
cdmo,

Perché ho circa 200 sistemi che tracciano il nome del ramo, e deve essere un nome specifico per l'attività, e se comincio a rinominare il ramo ad ogni spinta, perderò la testa.
Tamir Daniely,

@TamirDaniely Non ho provato, ma l'eliminazione del vecchio ramo (dal telecomando) prima di spingere e spingere il nuovo ramo con lo stesso vecchio nome risolve il tuo problema?
Nabi,

2
@Nabi Questo è esattamente ciò che fa --force-with-lease, tranne che verifica anche che non ci siano nuovi commit che non sono tuoi.
Tamir Daniely,

12

Altri hanno risposto alla tua domanda. Se rinnovi un ramo dovrai forzare a spingere quel ramo.

Rebase e un repository condiviso generalmente non vanno d'accordo. Questa è la riscrittura della storia. Se altri stanno usando quel ramo o si sono ramificati da quel ramo, rebase sarà piuttosto spiacevole.

In generale, rebase funziona bene per la gestione delle filiali locali. La gestione remota delle filiali funziona al meglio con fusioni esplicite (--no-ff).

Evitiamo inoltre di unire master in un ramo di funzionalità. Invece, passiamo al master ma con un nuovo nome di ramo (ad esempio aggiungendo un suffisso di versione). Ciò evita il problema della re-assegnazione nel repository condiviso.


5
Potresti aggiungere un esempio per favore?
Thermech,

8

Cosa c'è di sbagliato con a git merge mastersul featureramo? Ciò preserverà il lavoro svolto, mantenendolo separato dal ramo principale.

A--B--C------F--G
       \         \
        D--E------H

Modifica: Ah scusa non ho letto la tua dichiarazione di problema. Avrai bisogno di forza mentre esegui a rebase. Tutti i comandi che modificano la cronologia avranno bisogno --forcedell'argomento. Questo è un modo sicuro per evitare di perdere il lavoro (il vecchio De Esarebbe perso).

Quindi hai eseguito un git rebaseaspetto che faceva sembrare l'albero (anche se parzialmente nascosto come De Enon si trova più in un ramo denominato):

A--B--C------F--G
       \         \
        D--E      D'--E'

Quindi, quando provi a spingere il tuo nuovo featureramo (con D'e E'in esso), perdi De E.


3
Non c'è niente di sbagliato in questo, e so che funzionerà. Non è proprio quello di cui ho bisogno. Come ho detto, la domanda è più concettuale che pratica.
Yuval Adam,

4

Per me seguire i semplici passaggi funziona:

1. git checkout myFeature
2. git rebase master
3. git push --force-with-lease
4. git branch -f master HEAD
5. git checkout master
6. git pull

Dopo aver fatto tutto quanto sopra, possiamo eliminare anche il ramo myFeature seguendo il comando:

git push origin --delete myFeature

3

Per me funziona quanto segue:

git push -f origin branch_name

e non rimuove nessuno dei miei codici.

Ma, se vuoi evitare questo, puoi fare quanto segue:

git checkout master
git pull --rebase
git checkout -b new_branch_name

quindi puoi selezionare tutti i tuoi commit nella nuova filiale. git cherry-pick COMMIT ID e quindi spingere il tuo nuovo ramo.


5
-fè un alias per --force, che è ciò che la domanda sta cercando di evitare, se possibile.
motore d'epoca,

1

Dato che l'OP capisce il problema, cerca solo una soluzione migliore ...

Che ne dici di questo come pratica?

  • Avere un vero ramo di sviluppo delle funzionalità (dove non si ribatte e non si fa mai forza, quindi i tuoi colleghi sviluppatori di funzioni non ti odiano). Qui, prendi regolarmente quei cambiamenti da main con una fusione. Storia più incasinata , sì, ma la vita è facile e nessuno si interrompe nel suo lavoro.

  • Avere un secondo ramo per lo sviluppo di funzionalità, in cui un membro del team di funzioni spinge regolarmente tutti gli impegni a cui è stato impegnato, in effetti ribassati, in effetti forzati. Quindi, quasi in modo pulito, basato su un commit principale abbastanza recente. Al termine della funzione, spingere quel ramo sopra il master.

Potrebbe esserci già un nome di modello per questo metodo.

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.