Esiste una versione "loro" di "git merge -s our"?


876

Quando unisco il ramo argomento "B" in "A" usando git merge, ottengo alcuni conflitti. So che tutti i conflitti possono essere risolti usando la versione in "B".

Ne sono consapevole git merge -s ours. Ma quello che voglio è qualcosa di simile git merge -s theirs.

Perché non esiste? Come posso ottenere lo stesso risultato dopo l'unione in conflitto con i gitcomandi esistenti ? ( git checkoutogni file non unito da B)

AGGIORNAMENTO: La "soluzione" di scartare qualsiasi cosa dal ramo A (il punto di unione della fusione alla versione B dell'albero) non è ciò che sto cercando.


1
Vedi anche questa risposta: stackoverflow.com/questions/928646/… - è banale cambiare l'esempio per usare la versione B invece di A.
Robie Basak

8
Vedi SO answer git command per creare un ramo come un altro per tutti gli attuali modi possibili di simulazionegit merge -s their .
VonC,

4
Quindi non stai davvero cercando un git merge -s theirs(che può essere raggiunto facilmente con git merge -s ourse un ramo temporaneo), poiché il nostro ignora completamente i cambiamenti del ramo di unione ...
Nicolò Martini

21
@Torek - Do The Git sviluppatori davvero trovo che offensivo per fornire theirs, oltre a ours??? Questo è un sintomo di uno dei problemi di ingegneria e progettazione di alto livello in Git: incoerenza.
jww,

3
@jww Il problema qui non riguarda il fatto che "git merge -s our" sia offensivo, ma che sia contro-intuitivo. Dalla domanda del PO si può vedere che se una tale funzione fosse aggiunta, la userebbe per errore quando ciò che realmente vuole fare è un "git merge -s recursive -X loro". È comune voler unire un altro ramo sovrascrivendo i conflitti con la versione sull'altro ramo, ma sovrascrivere completamente il ramo corrente con un altro ramo scartando completamente le modifiche attuali è davvero un caso eccezionale.
ThomazMoura,

Risposte:


1020

Aggiungi l' -Xopzione a theirs. Per esempio:

git checkout branchA
git merge -X theirs branchB

Tutto si fonderà nel modo desiderato.

L'unica cosa che ho visto causa problemi è se i file venivano eliminati da branchB. Si presentano come conflitti se qualcosa di diverso da Git ha fatto la rimozione.

La correzione è semplice. Basta eseguire git rmcon il nome di tutti i file che sono stati eliminati:

git rm {DELETED-FILE-NAME}

Successivamente, -X theirsdovrebbe funzionare come previsto.

Naturalmente, la rimozione effettiva con il git rmcomando impedirà in primo luogo che si verifichi il conflitto.


Nota : esiste anche un'opzione di forma più lunga.

Per usarlo, sostituire:

-X theirs

con:

--strategy-option=theirs

6
Vedi altre risposte di seguito per una soluzione migliore (Paul Pladijs) alla domanda originale.
Malcolm,

204
Vale la pena notare che questo non è lo stesso della "strategia di unione theirs". -Xtheirs è l' opzione strategica applicata alla strategia ricorsiva . Ciò significa che la strategia ricorsiva unirà ancora tutto ciò che può e tornerà alla logica "loro" in caso di conflitto. Mentre questo è ciò di cui uno ha bisogno nella maggior parte dei casi come sopra, non è lo stesso di "prendi tutto dal ramo B così com'è". Fa invece la vera unione comunque.
Timur,

2
Funziona anche con altri comandi. Ho appena fatto un 'git cherry-pick sha1 -X loro'. Grazie!
Max Hohenegger,

48
Questa è una risposta terribile, perché sembra corretta, ma in realtà è sbagliata. "git merge -X theirs" non fa ciò che farebbe "git merge -s theirs". Non sostituisce il ramo corrente con il contenuto del ramo unito. Preferisce i "loro" cambiamenti, ma solo in caso di conflitto. Pertanto, il commit risultante potrebbe essere molto diverso dal ramo "loro". Questo non è ciò che il poster della domanda aveva in mente.
Ivan Krivyakov,

7
@ user3338098: sì, hai ragione. Rileggo la domanda, e non è neanche così buona. Sfortunatamente, la causa principale della confusione non è la risposta e nemmeno la domanda. È la scelta progettuale degli autori git dare lo stesso nome "nostro" a una strategia di unione e a un'opzione di strategia di fusione "ricorsiva". La confusione in questo caso è praticamente inevitabile.
Ivan Krivyakov,

218

Una soluzione possibile e testata per la fusione della filiale B nella nostra filiale controllata A:

# in case branchA is not our current branch
git checkout branchA

# make merge commit but without conflicts!!
# the contents of 'ours' will be discarded later
git merge -s ours branchB    

# make temporary branch to merged commit
git branch branchTEMP         

# get contents of working tree and index to the one of branchB
git reset --hard branchB

# reset to our merged commit but 
# keep contents of working tree and index
git reset --soft branchTEMP

# change the contents of the merged commit
# with the contents of branchB
git commit --amend

# get rid off our temporary branch
git branch -D branchTEMP

# verify that the merge commit contains only contents of branchB
git diff HEAD branchB

Per automatizzarlo puoi avvolgerlo in uno script usando branchA e branchB come argomenti.

Questa soluzione conserva il primo e il secondo genitore del commit di unione, proprio come ci si aspetterebbe git merge -s theirs branchB.


D: Perché hai bisogno di branchTEMP? Non potresti proprio git reset --soft branchA?
cdunn2001,

4
@ cdunn2001: in qualche modo ho pensato la stessa cosa, ma no. Nota che git reset --hardcambia a cui branchApunta il commit .
Tsuyoshi Ito,

5
mi dispiace fare una domanda stupida, ma perché questo metodo è migliore dei -xtheirs? quali sono i vantaggi / gli svantaggi
chrispepper1989,

9
@ chrispepper1989 -x il loro influenza solo i conflitti , se un nostro pezzo può essere applicato in modo pulito, passerà alla fusione risultante. In questo caso, come mostrato dall'ultimo comando, stiamo ottenendo un'unione perfettamente identica a branchB, indipendentemente dal fatto che ci sarebbero stati conflitti o meno.
ZioZeiv,

2
@UncleZeiv ti ringrazio per la spiegazione, quindi fondamentalmente fare un "loro" manterrebbe le modifiche e i file non conflittuali risultanti nel codice int che non è identico al codice estratto.
chrispepper1989,

89

Le versioni precedenti di git ti consentivano di utilizzare la strategia di unione "loro":

git pull --strategy=theirs remote_branch

Ma questo è stato rimosso, come spiegato in questo messaggio da Junio ​​Hamano (il manutentore di Git). Come notato nel link, invece faresti questo:

git fetch origin
git reset --hard origin

Attenzione, tuttavia, che questo è diverso da una fusione reale. La tua soluzione è probabilmente l'opzione che stai davvero cercando.


7
Davvero non capisco affatto la spiegazione di Junio ​​Hamano. Come è git reset --hard originuna soluzione per unire il loro stile? Se volessi unire BranchB in BranchA (come nella risposta di Alan W. Smith), come lo farei usando il resetmetodo?
James McMahon,

3
@James McMahon: il punto di Junio ​​C Hamano non è git reset --harduna fusione in stile "loro". Naturalmente, git reset --hardnon crea alcun commit di unione o alcun commit per quella materia. Il suo punto è che non dovremmo usare un'unione per sostituire qualsiasi cosa in HEAD con qualcos'altro. Non sono necessariamente d'accordo, però.
Tsuyoshi Ito,

11
È un peccato che sia theirsstato rimosso perché la logica era incompleta. Non è riuscito a consentire quei casi in cui il codice è buono, è solo che l'upstream è mantenuto su una diversa base filosofica, quindi in questo senso è "cattivo", quindi uno vuole tenersi aggiornato con l'upstream, ma allo stesso tempo mantengono le buone "correzioni" del codice [git e msysgit hanno questo in parte di questo "conflitto" a causa delle diverse filosofie della loro piattaforma target]
Philip Oakley,

1
Manca anche il caso d'uso in cui mi sono bloccato in questo momento, in cui sto condividendo un ramo con qualcun altro ed entrambi ci è capitato di inviare contemporaneamente modifiche (su file diversi). Quindi voglio bloccare tutte le vecchie versioni che ho localmente e usare invece le sue nuove. Vuole fare lo stesso per i file che ho aggiornato. Non c'è niente da "resettare". E la soluzione per il checkout di ogni singolo file non è pratica quando è ~ 20 file.
szeitlin,

2
Davvero non capisco la filosofia. Ho usato solo theirsperché avevo accidentalmente modificato alcuni file in due rami separati e volevo scartare le modifiche di uno senza doverlo fare manualmente per ogni file.
sudo,

65

Non è del tutto chiaro quale sia il risultato desiderato, quindi c'è qualche confusione sul modo "corretto" di farlo nelle risposte e nei loro commenti. Provo a dare una panoramica e vedere le tre opzioni seguenti:

Prova a unire e usa B per i conflitti

Questa non è la "loro versione per git merge -s ours" ma la "loro versione per git merge -X ours" (che è l'abbreviazione di git merge -s recursive -X ours):

git checkout branchA
# also uses -s recursive implicitly
git merge -X theirs branchB

Questo è ciò che fa ad esempio la risposta di Alan W. Smith .

Usa solo contenuti da B.

Ciò crea un commit di unione per entrambi i rami ma ignora tutte le modifiche branchAe ne impedisce solo il contenuto branchB.

# Get the content you want to keep.
# If you want to keep branchB at the current commit, you can add --detached,
# else it will be advanced to the merge commit in the next step.
git checkout branchB

# Do the merge an keep current (our) content from branchB we just checked out.
git merge -s ours branchA

# Set branchA to current commit and check it out.
git checkout -B branchA

Nota che l'unione commette il primo genitore ora è quello di branchBe solo il secondo è di branchA. Questo è ciò che fa ad esempio la risposta di Gandalf458 .

Usa il contenuto solo da B e mantieni l'ordine genitore corretto

Questa è la vera "loro versione per git merge -s ours". Ha lo stesso contenuto dell'opzione precedente (ovvero solo quella di branchB), ma l'ordine dei genitori è corretto, ovvero il primo genitore viene branchAe il secondo da branchB.

git checkout branchA

# Do a merge commit. The content of this commit does not matter,
# so use a strategy that never fails.
# Note: This advances branchA.
git merge -s ours branchB

# Change working tree and index to desired content.
# --detach ensures branchB will not move when doing the reset in the next step.
git checkout --detach branchB

# Move HEAD to branchA without changing contents of working tree and index.
git reset --soft branchA

# 'attach' HEAD to branchA.
# This ensures branchA will move when doing 'commit --amend'.
git checkout branchA

# Change content of merge commit to current index (i.e. content of branchB).
git commit --amend -C HEAD

Questa è la risposta di Paul Pladijs (senza richiedere una branca temporanea).


Una versione più pulita dell'opzione 3:git checkout branchA; git merge -s ours --no-commit branchB; git read-tree -um @ branchB; git commit
jthill

@jthill: Sebbene la tua versione sia più concisa, usa anche il comando di basso livello ("idraulico") a read-treecui l'utente medio git potrebbe non essere abituato (almeno non lo sono ;-)). Le soluzioni nella risposta utilizzano solo i comandi di alto livello ("porcellana") che la maggior parte degli utenti di Git dovrebbe conoscere. Li preferisco ma grazie comunque per la tua versione!
siegi,

Puoi spiegare il secondo all'ultimo passaggio (ramo di checkout A)? Sembra che non dovrebbe essere lì.
Patrick,

@Patrick, questo passaggio è obbligatorio. Se lo salti, otterrai lo stesso commit ma non avresti un ramo che punta a questo commit. Quindi, non appena si passa a un altro commit / ramo, il commit eseguito con l'ultimo comando ( …commit --amend…) verrebbe "perso". Sto programmando di aggiungere alcune spiegazioni grafiche a questa risposta non appena avrò il tempo di farle ... ;-)
siegi,

62

Da allora ho usato la risposta di Paul Pladijs. Ho scoperto che puoi fare un'unione "normale", si verificano conflitti, quindi lo fai

git checkout --theirs <file>

per risolvere il conflitto usando la revisione dell'altro ramo. Se lo fai per ogni file, hai lo stesso comportamento che ti aspetteresti

git merge <branch> -s theirs

Comunque, lo sforzo è più di quanto sarebbe con la strategia di unione! (Questo è stato testato con la versione 1.8.0 di git)


1
sarebbe fantastico se l'elenco dei file potesse essere recuperato. Ad esempio, git ls-files --modified | xargs git addvolevo farlo per unire le due parti
unite

In realtà git restituisce i file aggiunti su entrambi i lati con git ls-files --modifiedquindi suppongo che questa sia anche una soluzione praticabile.
andho,

1
Grazie per aver aggiunto anche questo. Più spesso non è necessario su base file per file. Inoltre, lo stato git mostra "Percorsi non uniti" in fondo. Per ottenere l'elenco dei percorsi non risolti, uso questo alias:git config --global alias.unresolved '!git status --short|egrep "^([DAU])\1"'
Melvyn,

qual è il significato di -Xe qual è la differenza tra -Xe -s? non è possibile trovare alcun documento.
Lei Yang,

21

Ho risolto il mio problema usando

git checkout -m old
git checkout -b new B
git merge -s ours old

un ramo "B" dal ramo "vecchio"
elmarco

13

Quando unisco il ramo argomento "B" in "A" usando git merge, ottengo alcuni conflitti. So che tutti i conflitti possono essere risolti usando la versione in "B".

Sono a conoscenza di git merge -s our. Ma quello che voglio è qualcosa come git merge> -s loro.

Suppongo che tu abbia creato un ramo fuori dal master e ora desideri ricollegarti al master, sovrascrivendo tutte le vecchie cose nel master. Questo è esattamente quello che volevo fare quando mi sono imbattuto in questo post.

Fai esattamente quello che vuoi fare, tranne unire prima un ramo nell'altro. L'ho appena fatto e ha funzionato alla grande.

git checkout Branch
git merge master -s ours

Quindi, controlla il master e unisci il tuo ramo (andrà liscio ora):

git checkout master
git merge Branch

1
Grazie. Questa dovrebbe essere la risposta accettata, di gran lunga la più semplice e la più corretta. Se il tuo ramo è dietro / è in conflitto con il master, allora è il lavoro del ramo da unire e far funzionare tutto prima di fondere tutto al master.
StackHola

Ha funzionato alla grande, grazie.
Kodie Grantham,

12

Se sei nel ramo A fai:

git merge -s recursive -X theirs B

Testato su git versione 1.7.8


8

Per eseguire davvero un'unione che accetta solo input dal ramo che stai unendo, puoi farlo

git merge --strategy=ours ref-to-be-merged

git diff --binary ref-to-be-merged | git apply --reverse --index

git commit --amend

Non ci saranno conflitti in nessuno scenario che conosco, non è necessario creare ulteriori rami e si comporta come un normale commit di unione.

Questo non funziona bene con i sottomoduli comunque.


Cosa fa la -R qui?
P. Myer Nore,

In questo modo si perde la cronologia dei file "spostati e modificati". Ho ragione?
elysch,

1
-R è --reverse, ho aggiornato la risposta con quella per essere più auto-documentante.
Elijah Lynn,

Questo non sembra funzionare se i file che stai tentando di unire vengono eliminati nel ramo in arrivo.
risolto il

7

Perché non esiste?

Mentre menziono in " comando git per creare un ramo come un altro " come simulare git merge -s theirs, nota che Git 2.15 (Q4 2017) è ora più chiaro:

La documentazione per " -X<option>" per le fusioni è stata scritta in modo fuorviante per suggerire che " -s theirs" esiste, il che non è il caso.

Vedi commit c25d98b (25 set 2017) di Junio ​​C Hamano ( gitster) .
(Unita da Junio ​​C Hamano - gitster- in commit 4da3e23 , 28 set 2017)

strategie di unione: evitare di insinuare che " -s theirs" esiste

La descrizione -Xoursdell'opzione di unione ha una nota tra parentesi che dice ai lettori che è molto diversa da -s ours, che è corretta, ma la descrizione -Xtheirsche segue la segue con noncuranza "questo è l'opposto di ours", dando una falsa impressione che anche i lettori hanno bisogno essere avvertito che è molto diverso da quello -s theirsche in realtà non esiste nemmeno.

-Xtheirsè un'opzione di strategia applicata alla strategia ricorsiva. Ciò significa che la strategia ricorsiva unirà ancora tutto ciò che può e tornerà alla " theirs" logica in caso di conflitti.

Quel dibattito sulla pertinenza o meno di una theirsstrategia di fusione è stato riportato di recente in questo thread di settembre 2017 .
Riconosce le discussioni precedenti (2008)

In breve, la discussione precedente può essere riassunta in "non vogliamo -s theirs" poiché incoraggia il flusso di lavoro sbagliato ".

Menziona l'alias:

mtheirs = !sh -c 'git merge -s ours --no-commit $1 && git read-tree -m -u $1' -

Yaroslav Halchenko prova ancora una volta a sostenere questa strategia, ma Junio ​​C. Hamano aggiunge :

La ragione per cui la nostra e la loro non sono simmetriche è perché tu sei tu e non loro --- il controllo e la proprietà della nostra storia e della loro storia non sono simmetrici.

Una volta che decidi che la loro storia è la linea principale, preferiresti trattare la tua linea di sviluppo come un ramo laterale e fare una fusione in quella direzione, cioè il primo genitore della fusione risultante è un impegno sulla loro storia e il secondo genitore è l'ultimo cattivo della tua storia. Quindi finiresti per usare " checkout their-history && merge -s ours your-history" per mantenere sensibile la prima genitorialità.

E a quel punto, l'uso di " -s ours" non è più una soluzione alternativa per mancanza di " -s theirs".
È una parte propria della semantica desiderata, cioè dal punto di vista della linea di storia canonica sopravvissuta, vuoi preservare ciò che ha fatto, annullando ciò che ha fatto l'altra linea di storia .

Junio ​​aggiunge, come commentato da Mike Beaton :

git merge -s ours <their-ref>dice in modo efficace "contrassegna i commit commessi <their-ref>nella propria succursale come commit da ignorare permanentemente";
e questo è importante perché, se successivamente si uniscono da stati successivi del loro ramo, le loro modifiche successive verranno apportate senza che le modifiche ignorate vengano mai apportate .


1
Questa è una risposta utile, ma non menziona un punto chiave che ho appena capito dalla risposta di Junio ​​C. Hamano: git merge -s ours <their-ref>dice effettivamente 'mark commette fino a <their-ref> sul loro ramo come commit per essere permanentemente ignorato '; e questo è importante perché, se successivamente si uniscono da stati successivi della loro succursale, i loro successivi cambiamenti verranno apportati senza che i cambiamenti ignorati vengano mai introdotti.
MikeBeaton

1
@MikeBeaton Grazie. Ho incluso il tuo commento nella risposta per una maggiore visibilità.
VonC,

5

Vedi la risposta ampiamente citata di Junio ​​Hamano : se hai intenzione di scartare i contenuti impegnati, scarta semplicemente i commit o comunque mantienili fuori dalla storia principale. Perché disturbare tutti in futuro a leggere i messaggi di commit da commit che non hanno nulla da offrire?

Ma a volte ci sono requisiti amministrativi, o forse qualche altro motivo. Per quelle situazioni in cui devi davvero registrare commit che non contribuiscono a nulla, vuoi:

(modifica: wow, sono riuscito a sbagliare prima. Questo funziona.)

git update-ref HEAD $(
        git commit-tree -m 'completely superseding with branchB content' \
                        -p HEAD -p branchB    branchB:
)
git reset --hard

3

Questo utilizza un albero di comando git plumbing read-tree, ma consente un flusso di lavoro complessivo più breve.

git checkout <base-branch>

git merge --no-commit -s ours <their-branch>
git read-tree -u --reset <their-branch>
git commit

# Check your work!
git diff <their-branch>

0

Questo unirà il tuo nuovo Branch in base Branch esistente

git checkout <baseBranch> // this will checkout baseBranch
git merge -s ours <newBranch> // this will simple merge newBranch in baseBranch
git rm -rf . // this will remove all non references files from baseBranch (deleted in newBranch)
git checkout newBranch -- . //this will replace all conflicted files in baseBranch

Suggerirei di aggiungere git commit --amendalla fine del tuo esempio, oppure gli utenti potrebbero vedere l'unione impegnata git loge supporre che l'operazione sia completa.
Michael R,

0

Penso che quello che vuoi davvero sia:

git checkout -B mergeBranch branchB
git merge -s ours branchA
git checkout branchA
git merge mergeBranch
git branch -D mergeBranch

Sembra goffo, ma dovrebbe funzionare. L'unica cosa che non mi piace di questa soluzione è che la cronologia git sarà confusa ... Ma almeno la cronologia sarà completamente preservata e non dovrai fare qualcosa di speciale per i file eliminati.


0

L'equivalente (che mantiene l'ordine genitore) a "git merge -s theirs branchB"

Prima di unire:inserisci qui la descrizione dell'immagine

!!! Assicurati di essere in uno stato pulito !!!

Fai l'unione:

git commit-tree -m "take theirs" -p HEAD -p branchB 'branchB^{tree}'
git reset --hard 36daf519952 # is the output of the prev command

Cosa abbiamo fatto ? Abbiamo creato un nuovo commit che due nostri genitori e loro e la contnet del commit è branchB - loro

Dopo l'unione:inserisci qui la descrizione dell'immagine

Più precisamente:

git commit-tree -m "take theirs" -p HEAD -p 'SOURCE^{commit}' 'SOURCE^{tree}'

0

Questa risposta è stata data da Paul Pladijs. Ho appena preso i suoi comandi e ho creato un alias git per comodità.

Modifica il tuo .gitconfig e aggiungi quanto segue:

[alias]
    mergetheirs = "!git merge -s ours \"$1\" && git branch temp_THEIRS && git reset --hard \"$1\" && git reset --soft temp_THEIRS && git commit --amend && git branch -D temp_THEIRS"

Quindi puoi "git merge -s theirs A" eseguendo:

git checkout B (optional, just making sure we're on branch B)
git mergetheirs A

-1

Di recente ho dovuto farlo per due repository separati che condividono una storia comune. Ho iniziato con:

  • Org/repository1 master
  • Org/repository2 master

Volevo che tutte le modifiche repository2 masterfossero applicate repository1 master, accettando tutte le modifiche che repository2 avrebbe apportato. In termini di git, questa dovrebbe essere una strategia chiamata -s theirsMA NON esiste. Fai attenzione perché il -X theirsnome è come sarebbe quello che vuoi, ma NON è lo stesso (lo dice anche nella pagina man).

Il modo in cui ho risolto questo è stato quello di andare repository2e creare un nuovo ramo repo1-merge. In quel ramo, ho corso git pull git@gitlab.com:Org/repository1 -s ourse si fonde bene senza problemi. Lo spingo poi sul telecomando.

Poi torno a repository1e faccio una nuova filiale repo2-merge. In quel ramo, corro, git pull git@gitlab.com:Org/repository2 repo1-mergeche completerà con problemi.

Infine, dovresti emettere una richiesta di unione repository1per renderla il nuovo master o semplicemente tenerla come una diramazione.


-2

Un modo semplice e intuitivo (secondo me) in due passaggi per farlo è

git checkout branchB .
git commit -m "Picked up the content from branchB"

seguito da

git merge -s ours branchB

(che segna l'unione dei due rami)

L'unico svantaggio è che non rimuove i file che sono stati eliminati in branchB dal tuo ramo attuale. Una semplice differenza tra i due rami in seguito mostrerà se ci sono tali file.

Questo approccio chiarisce anche successivamente dal registro delle revisioni cosa è stato fatto e cosa è stato previsto.


ciò può essere corretto per la domanda originale, tuttavia l'OP ha aggiornato la domanda nel 2008 in modo tale che questa risposta sia errata.
user3338098

1
@ user3338098 Sì, un vero peccato che hanno lasciato il titolo fuorviante e appena schiaffeggiato un aggiornamento non in modo visibile alla fine del corpo domanda .... perché questo risponde fa rispondere correttamente alla domanda del titolo.
RomainValeri,

Sono completamente d'accordo con @RomainValeri
AppleCiderGuy
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.