Git si fonde con la sovrascrittura forzata


100

Ho un ramo chiamato demoche devo unire al masterramo. Posso ottenere il risultato desiderato con i seguenti comandi:

git pull origin demo
git checkout master
git pull origin master
git merge demo
git push origin master

La mia unica preoccupazione è che, se ci sono problemi di unione , voglio dire gitdi sovrascrivere le modifiche nel masterramo senza darmi il prompt di unione. Quindi fondamentalmente cambia indemo branch dovrebbero sovrascrivere automaticamente le modifiche inmaster ramo.

Mi sono guardato intorno ci sono più opzioni ma non voglio correre rischi con la fusione.


git push -f origin master
MD XF

4
@MDXF: Forse mi sbaglio ma non dovrei usare l' -fopzione con il mergecomando e non con il pushcomando
OpenStack

Potresti provare entrambi e vedere cosa funziona per te
MD XF

Vedi sotto link per una soluzione di forza sovrascrittura: stackoverflow.com/a/42454737/984471
Manohar Reddy Poreddy

Risposte:


99

Non proprio correlato a questa risposta, ma me ne sarei andato git pull, che viene semplicemente git fetchseguito da git merge. Stai facendo tre unioni, che faranno eseguire a Git tre operazioni di recupero, quando un recupero è tutto ciò di cui hai bisogno. Quindi:

git fetch origin   # update all our origin/* remote-tracking branches

git checkout demo         # if needed -- your example assumes you're on it
git merge origin/demo     # if needed -- see below

git checkout master
git merge origin/master

git merge -X theirs demo   # but see below

git push origin master     # again, see below

Controllare l'unione più complicata

La parte più interessante qui è git merge -X theirs. Come notato da root545 , le -Xopzioni vengono passate alla strategia di unione e sia la recursivestrategia predefinita che la resolvestrategia alternativa accettano -X ourso -X theirs(l'una o l'altra, ma non entrambe). Per capire cosa fanno, però, devi sapere come Git trova e tratta, unire i conflitti .

Un conflitto di unione può verificarsi all'interno di alcuni file 1 quando la versione di base è diversa dalla versione corrente (chiamata anche locale, HEAD o --ours) e dall'altra (chiamata anche remota o --theirs) dello stesso file. Cioè, l'unione ha identificato tre revisioni (tre commit): base, nostro e loro. La versione "base" proviene dalla base merge base tra il nostro commit e il loro commit, come si trova nel grafico del commit (per molto di più su questo, vedi altri post di StackOverflow). Git ha quindi trovato due serie di modifiche: "cosa abbiamo fatto" e "cosa hanno fatto". Questi cambiamenti si trovano (in generale) riga per riga, puramente testualibase. Git non ha una reale comprensione del contenuto dei file; sta semplicemente confrontando ogni riga di testo.

Questi cambiamenti sono ciò che vedi git diffnell'output e, come sempre, hanno anche un contesto . È possibile che le cose che abbiamo modificato siano su linee diverse da quelle che sono cambiate, in modo che le modifiche sembrino non entrare in collisione, ma anche il contesto è cambiato (ad esempio, a causa del fatto che la nostra modifica è vicina all'inizio o alla fine del file, in modo che il file si esaurisca nella nostra versione, ma nella loro hanno anche aggiunto più testo in alto o in basso).

Se le modifiche avvengono su righe diverse , ad esempio, passiamo coloralla colourriga 17 e passano fredalla barneyriga 71, allora non c'è conflitto: Git accetta semplicemente entrambe le modifiche. Se le modifiche avvengono sulle stesse righe, ma sono modifiche identiche , Git esegue una copia della modifica. Solo se le modifiche sono sulla stessa linea, ma sono modifiche diverse, o quel caso speciale di contesto che interferisce, si ottiene un conflitto di modifica / modifica.

Le opzioni -X ourse -X theirsdicono a Git come risolvere questo conflitto, scegliendo solo una delle due modifiche: la nostra o la loro. Dato che hai detto che ti stai fondendo demo(il loro) nel master(nostro) e vuoi le modifiche da demo, vorresti -X theirs.

L'applicazione alla cieca -X, tuttavia, è pericolosa. Solo perché le nostre modifiche non sono entrate in conflitto riga per riga, non significa che le nostre modifiche non siano effettivamente in conflitto! Un classico esempio si verifica nelle lingue con dichiarazioni di variabili. La versione base potrebbe dichiarare una variabile non utilizzata:

int i;

Nella nostra versione, cancelliamo la variabile inutilizzata per far scomparire un avviso del compilatore e nella loro versione aggiungono un ciclo alcune righe dopo, usando icome contatore dei cicli. Se combiniamo le due modifiche, il codice risultante non viene più compilato. L' -Xopzione non è di aiuto qui poiché le modifiche sono su linee diverse .

Se disponi di una suite di test automatizzata, la cosa più importante da fare è eseguire i test dopo l'unione. Puoi farlo dopo aver eseguito il commit e sistemare le cose in un secondo momento, se necessario; oppure puoi farlo prima di eseguire il commit, aggiungendo --no-commital git mergecomando. Lasceremo i dettagli per tutto questo ad altri post.


1 Puoi anche ottenere conflitti rispetto alle operazioni "a livello di file", ad esempio, forse correggiamo l'ortografia di una parola in un file (in modo da avere una modifica), e cancellano l'intero file (in modo che abbiano un Elimina). Git non risolverà questi conflitti da solo, indipendentemente dagli -Xargomenti.


Esecuzione di meno fusioni e / o unioni più intelligenti e / o utilizzo di rebase

Ci sono tre unioni in entrambe le nostre sequenze di comandi. Il primo è portare origin/demonel locale demo(il tuo usa git pullche, se il tuo Git è molto vecchio, non si aggiornerà origin/demoma produrrà lo stesso risultato finale). Il secondo è portare origin/masterdentro master.

Non mi è chiaro chi stia aggiornando demoe / o master. Se scrivi il tuo codice sul tuo demoramo e altri lo scrivono e lo inviano al demoramo origin, allora questa unione del primo passaggio può avere conflitti o produrre una unione reale. Il più delle volte, è meglio usare rebase, piuttosto che merge, per combinare il lavoro (ammettiamolo, questa è una questione di gusti e opinioni). Se è così, potresti voler usare git rebaseinvece. D'altra parte, se non esegui mai nessuno dei tuoi commit demo, non hai nemmeno bisogno di un : questo farà avanzare velocemente il tuo per abbinare il aggiornatodemo ramo. In alternativa, se vuoi automatizzare molto di questo, ma essere in grado di controllare attentamente quando ci sono commit che sia tu che altri, potresti voler usaregit merge --ff-only origin/demodemoorigin/demo se possibile, e semplicemente fallirà in caso contrario (a quel punto puoi ispezionare i due set di modifiche e scegliere un'unione reale o un rebase a seconda dei casi).

Questa stessa logica vale per master, anche se si sta facendo l'unione su master , quindi è sicuramente bisogno di una master. Tuttavia, è ancora più probabile che si desideri che l'unione fallisca se non può essere eseguita come una non unione in avanti veloce, quindi probabilmente dovrebbe essere anche questo git merge --ff-only origin/master.

Diciamo che non ti impegni mai da solo demo. In questo caso possiamo abbandonare completamente il nome demo:

git fetch origin   # update origin/*

git checkout master
git merge --ff-only origin/master || die "cannot fast-forward our master"

git merge -X theirs origin/demo || die "complex merge conflict"

git push origin master

Se si sta facendo il proprio democommit ramo, questo non è utile; potresti anche mantenere l'unione esistente (ma magari aggiungerla a --ff-onlyseconda del comportamento che vuoi), o cambiarla per fare un rebase. Nota che tutti e tre i metodi potrebbero non riuscire: l'unione potrebbe non riuscire con un conflitto, l'unione con --ff-onlypotrebbe non essere in grado di avanzare rapidamente e il rebase potrebbe non riuscire con un conflitto (il rebase funziona, in sostanza, con commit selettivi, che utilizza l'unione macchinari e quindi può ottenere un conflitto di unione).


19

Ho avuto un problema simile, in cui avevo bisogno di sostituire efficacemente qualsiasi file che avesse modifiche / conflitti con un ramo diverso.

La soluzione che ho trovato è stata quella di utilizzare git merge -s ours branch.

Nota che l'opzione è -se non -X. -sdenota l'uso di ourscome strategia di unione di primo livello, -Xapplicando l' oursopzione alla recursivestrategia di unione, che non è ciò che io (o noi) vogliamo in questo caso.

Passi, dov'è oldbranchil ramo con cui vuoi sovrascrivere newbranch.

  • git checkout newbranch controlla il ramo che desideri mantenere
  • git merge -s ours oldbranch si fonde nel vecchio ramo, ma conserva tutti i nostri file.
  • git checkout oldbranch controlla il ramo che vuoi sovrascrivere
  • get merge newbranch si fonde nel nuovo ramo, sovrascrivendo il vecchio ramo

Nota: nessuna delle altre soluzioni qui ha funzionato per me, quindi sto postando la mia risposta.
Niall Mccormack

1
Non ha funzionato per me. Ha risolto il conflitto (risolto i file in conflitto) ma il file non è stato unito. E non si può fondere nessuno dei due.
c-an

Se qualcuno si blocca dove ti viene chiesto di "Inserisci un messaggio di commit per spiegare perché questa unione è necessaria": inserisci il tuo messaggio, quindi premi il tasto ESC sulla tastiera, digita: wq e premi INVIO per uscire dal prompt.
topherPedersen

17

Questo approccio di unione aggiungerà un commit in cima al masterquale incolla qualunque cosa sia dentro feature, senza lamentarsi di conflitti o altre schifezze.

inserisci qui la descrizione dell'immagine

Prima di toccare qualsiasi cosa

git stash
git status # if anything shows up here, move it to your desktop

Ora prepara il maestro

git checkout master
git pull # if there is a problem in this step, it is outside the scope of this answer

Vestiti featuretutti

git checkout feature
git merge --strategy=ours master

Vai per l'uccisione

git checkout master
git merge --no-ff feature

1
git push to finish
Kochchy

git-scm.com/docs/git-merge#Documentation/git-merge.txt-ours. Ha funzionato quando i commit non venivano uniti in modo pulito. Ma questo approccio non funzionerà sempre, per citare la fonteChanges from the other tree that do not conflict with our side are reflected in the merge result . Non ha funzionato per me, poiché avevo unito in modo pulito i commit nel mio ramo che venivano sovrascritti. C'è un altro modo?
NiharGht

11

Puoi provare l'opzione "nostra" in git merge,

git merge branch -X nostro

Questa opzione costringe gli hunk in conflitto a essere risolti automaticamente in modo pulito favorendo la nostra versione. Le modifiche dall'altro albero che non sono in conflitto con il nostro lato si riflettono nel risultato dell'unione. Per un file binario, l'intero contenuto viene preso dalla nostra parte.


3
Questo sarebbe al contrario, poiché l'OP ha detto che vuole che la demoversione sia preferita anche se si sta fondendo master. C'è -X theirsanche, ma non è chiaro se questo sia ciò che l'OP vuole veramente.
torek

Non hai letto per intero. La tua nota descrive cosa oursfa quando si utilizza sul backend con l' -sopzione. Quando si usa ourscon -X: Elimina tutto ciò che ha fatto l'altro albero, dichiarando che la nostra cronologia contiene tutto ciò che è accaduto al suo interno. fonte
jimasun

2

Quando ho provato a utilizzare -X theirse altre opzioni di comando correlate, ho continuato a ricevere un commit di unione. Probabilmente non lo capivo correttamente. Un'alternativa facile da capire è semplicemente eliminare il ramo e rintracciarlo di nuovo.

git branch -D <branch-name>
git branch --track <branch-name> origin/<branch-name>

Questa non è esattamente una "fusione", ma è quello che stavo cercando quando mi sono imbattuto in questa domanda. Nel mio caso volevo estrarre le modifiche da un ramo remoto che erano state forzate.

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.