Есть ли «их» версия «git merge -s ours»?

877

При объединении ветки темы "B" в "A" с помощью git merge возникают конфликты. Я знаю, что все конфликты могут быть решены с помощью версии в "B".

Я в курсе git merge -s ours. Но я хочу что-то вроде git merge -s theirs.

Почему его не существует? Как я могу достичь того же результата после конфликтующего слияния с существующими gitкомандами? ( git checkoutкаждый распакованный файл из B)

ОБНОВЛЕНИЕ: «Решение» простого отказа от чего-либо из ветви A (точка фиксации слияния до версии B дерева) не является тем, что я ищу.

Elmarco
источник
1
Также смотрите этот ответ: stackoverflow.com/questions/928646/… - тривиально изменить пример для использования версии B вместо A.
Роби Басак
8
Смотрите SO answer git command, чтобы сделать одну ветку похожей на другую для всех текущих возможных способов симуляцииgit merge -s their .
VonC
4
Таким образом, вы на самом деле не ищете git merge -s theirs(что может быть легко достигнуто с git merge -s oursпомощью временной ветки), поскольку -s наша полностью игнорирует изменения ветви слияния-от ...
Nicolò Martini
21
@Torek - сделайте Git УБС действительно найти , что наступление , чтобы обеспечить theirsв дополнение к ours??? Это признак одной из проблем проектирования и проектирования высокого уровня в Git: несогласованность.
августа
3
@jww Проблема здесь не в том, что «git merge -s ours» оскорбительный, а в том, что он нелогичен. Вы можете видеть из вопроса ОП, что если бы такая функция была добавлена, он использовал бы ее по ошибке, когда он действительно хотел бы сделать это «git merge -s рекурсивный -X их». Обычно требуется объединить конфликты, переопределяющие другую ветвь, с версией в другой ветке, но полная перезапись текущей ветки другой ветвью, полностью отбрасывающей изменения текущей, на самом деле является исключением.
ThomazMoura

Ответы:

1022

Добавьте -Xопцию в theirs. Например:

git checkout branchA
git merge -X theirs branchB

Все слится желаемым образом.

Единственное, что я видел, вызывает проблемы, если файлы были удалены из BranchB. Они появляются как конфликты, если удаление выполнено чем-то другим, кроме git.

Исправить легко. Просто запустите git rmс именем любых файлов, которые были удалены:

git rm {DELETED-FILE-NAME}

После этого -X theirsдолжна работать как положено.

Конечно, выполнение фактического удаления с помощью git rmкоманды предотвратит конфликт в первую очередь.


Примечание . Также существует более длинный вариант формы.

Чтобы использовать его, замените:

-X theirs

с:

--strategy-option=theirs
Алан В. Смит
источник
6
Смотрите другой ответ ниже для лучшего решения (Paul Pladijs) на оригинальный вопрос.
Малкольм
204
Стоит отметить, что это не то же самое, что «стратегия слияния theirs». -Xtheirs - вариант стратегии, применяемый к рекурсивной стратегии . Это означает, что рекурсивная стратегия по-прежнему объединит все, что может, и в случае конфликтов вернется к «своей» логике. Хотя это то, что нужно в большинстве случаев, как описано выше, это не то же самое, что «просто взять все из ветви B как есть». В любом случае, он выполняет реальное слияние.
Тимур
2
Он работает и с другими командами. Я только что сделал 'git cherry-pick sha1 -X их'. Спасибо!
Макс Хоэнеггер,
48
Это ужасный ответ, потому что выглядит правильно, но на самом деле неправильно. «git merge -X их» не делает то, что делает «git merge -s их». Он не заменяет текущую ветку содержимым объединенной ветви. Предпочитает «свои» изменения, но только в случае конфликта. Поэтому результирующий коммит может сильно отличаться от "их" ветки. Это не то, что имел в виду плакат с вопросом.
Иван Кривяков
7
@ user3338098: да, ты прав. Я перечитал вопрос, и это тоже не так хорошо. К сожалению, коренной причиной путаницы является не ответ и даже не вопрос. Авторы git предпочитают придавать одно и то же имя «нашему» стратегии слияния, а опцию стратегии слияния - «рекурсивной». Путаница в этом случае практически неизбежна.
Иван Кривяков
218

Возможное и проверенное решение для объединения ветки B в нашу проверенную ветку 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

Чтобы автоматизировать это, вы можете заключить его в скрипт, используя в качестве аргументов branchA и branchB.

Это решение сохраняет первого и второго родителя коммита слияния, как и следовало ожидать git merge -s theirs branchB.

Пол Пладийс
источник
Q: Зачем вам нужен branchTEMP? Не могли бы вы просто git reset --soft branchA?
cdunn2001
4
@ cdunn2001: Почему-то я тоже так думал, но нет. Обратите внимание, что git reset --hardизменения, на которые branchAуказывает фиксация .
Tsuyoshi Ito
5
извините за глупый вопрос, но почему этот метод лучше? какие преимущества / недостатки
chrispepper1989
9
@ chrispepper1989 -x их влияет только на конфликты , если наш кусок может быть применен аккуратно, он дойдет до полученного слияния. В этом случае, как показано последней командой, мы получаем слияние, полностью идентичное BranchB, независимо от того, были ли конфликты или нет.
UncleZeiv
2
@UncleZeiv спасибо за объяснение, поэтому в основном выполнение "их" сохранит несогласованные изменения и файлы, в результате чего будет получен int-код, который не идентичен извлеченному коду.
chrispepper1989
89

Более старые версии git позволяли вам использовать «их» стратегию слияния:

git pull --strategy=theirs remote_branch

Но с тех пор это было удалено, как объяснено в этом сообщении Джунио Хамано (сопровождающий Git). Как отмечено в ссылке, вместо этого вы должны сделать это:

git fetch origin
git reset --hard origin

Остерегайтесь, однако, что это отличается от фактического слияния. Ваше решение, вероятно, является вариантом, который вы действительно ищете.

Пэт Нотц
источник
7
Я действительно не понимаю объяснений Хунио Хамано. Как git reset --hard originрешение для их слияния стиля? Если бы я хотел объединить BranchB с BranchA (как в ответе Алана У. Смита), как бы я это сделал, используя resetметод?
Джеймс МакМэхон
3
@ Джеймс МакМэхон: точка зрения Хунио Хамано не в том git reset --hard, чтобы «объединить их». Конечно, git reset --hardне создает никакого коммита слияния или коммитов в этом отношении. Он считает, что мы не должны использовать слияние для замены чего-либо в HEAD чем-то другим. Я не обязательно согласен, хотя.
Tsuyoshi Ito
11
Обидно, что это theirsбыло удалено, потому что обоснование было неполным. Не удалось учесть те случаи, когда код хорош, просто восходящий поток поддерживается на другой философской основе, поэтому в этом смысле он «плохой», так что каждый хочет быть в курсе восходящего потока, но в то же время сохраните хорошие исправления кода [у git & msysgit есть некоторые из этого 'конфликта' из-за философии их различных целевых платформ]
Филип Окли
1
Здесь также отсутствует сценарий использования, с которым я сейчас застрял, когда я делюсь веткой с кем-то другим, и мы оба случайно нажали изменения (в разных файлах) одновременно. Поэтому я хочу уничтожить все старые версии, которые у меня есть, и использовать вместо них его новые. Он хочет сделать то же самое для файлов, которые я обновил. Там нет ничего, чтобы «сбросить». И решение для извлечения каждого отдельного файла не практично, когда это ~ 20 файлов.
Сейтлин
2
Я действительно не понимаю философию. Я просто использовал, theirsпотому что я случайно изменил некоторые файлы в двух отдельных ветвях и хотел отменить изменения одного без необходимости делать это вручную для каждого файла.
Судо
65

Не совсем ясно, каков ваш желаемый результат, поэтому существует некоторая путаница в отношении «правильного» способа сделать это в ответах и ​​их комментариях. Я пытаюсь дать обзор и вижу следующие три варианта:

Попробуйте объединить и использовать B для конфликтов

Это не «их версия для git merge -s ours», а «их версия для git merge -X ours» (что сокращенно git merge -s recursive -X ours):

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

Вот что делает, например , ответ Алана У. Смита .

Использовать только контент из B

Это создает коммит слияния для обеих веток, но отбрасывает все изменения branchAи только сохраняет содержимое 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

Обратите внимание, что слияние совершает первый родительский элемент теперь branchBи только от второго branchA. Вот что делает, например , ответ Gandalf458 .

Используйте контент только из B и сохраняйте правильный родительский порядок

Это настоящая "их версия для git merge -s ours". Он имеет то же содержание, что и в предыдущем варианте (т. Е. Только из branchB), но порядок родителей правильный, т. Е. Первый родитель branchAи второй 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

Это то, что делает ответ Пола Пладийса (не требуя временной ветки).

siegi
источник
Более чистый вариант варианта 3:git checkout branchA; git merge -s ours --no-commit branchB; git read-tree -um @ branchB; git commit
до
@jthill: Хотя ваша версия более лаконична, она также использует низкоуровневую команду («слесарное дело») read-tree, к которой средний пользователь git может не привыкнуть (по крайней мере, я не так ;-)). Решения в ответе используют только высокоуровневые («фарфоровые») команды, которые должны знать большинство пользователей git. Я предпочитаю их, но тем не менее спасибо за вашу версию!
siegi
Можете ли вы объяснить второй до последнего шага (оформить ответвление A)? Кажется, этого не должно быть.
Патрик
@ Патрик, этот шаг обязателен. Если вы пропустите его, вы получите тот же коммит, но у вас не будет ветки, указывающей на этот коммит. Поэтому, как только вы переключитесь на другой коммит / ветвь, коммит, который вы сделали с помощью последней команды ( …commit --amend…), будет «потерян». Я планирую добавить некоторые графические объяснения к этому ответу, как только у меня будет время, чтобы сделать их… ;-)
siegi
62

С тех пор я использовал ответ от Поля Пладийса. Я обнаружил, что вы можете сделать "нормальное" слияние, конфликты происходят, так что вы делаете

git checkout --theirs <file>

разрешить конфликт с помощью ревизии из другой ветки. Если вы сделаете это для каждого файла, у вас будет то же поведение, которое вы ожидаете от

git merge <branch> -s theirs

Во всяком случае, усилия больше, чем было бы со стратегией слияния! (Это было протестировано с git версии 1.8.0)

musicmatze
источник
1
было бы здорово, если бы список файлов можно было получить. Например, git ls-files --modified | xargs git addя хотел сделать это для добавления с обеих сторон слияния: /
andho
На самом деле git возвращает добавленные с обеих сторон файлы, git ls-files --modifiedтак что я думаю, что это также жизнеспособное решение.
andho
1
Спасибо за добавление этого тоже. Чаще всего это не нужно для каждого файла. Кроме того, статус git показывает «Unmerged Paths» в самом низу. Чтобы получить список неразрешенных путей, я использую этот псевдоним:git config --global alias.unresolved '!git status --short|egrep "^([DAU])\1"'
Melvyn
в чем смысл -Xи в чем разница между -Xи -s? не могу найти ни одного документа.
Лей Ян
21

Я решил свою проблему, используя

git checkout -m old
git checkout -b new B
git merge -s ours old
Elmarco
источник
ветка "B" из "старой" ветки
elmarco
13

При слиянии ветки темы "B" в "A" с помощью git merge я получаю некоторые конфликты. Я> знаю, что все конфликты могут быть решены с помощью версии в "B".

Я знаю, что Git Merge - наш. Но я хочу что-то вроде git merge> -s их.

Я предполагаю, что вы создали ветку от master и теперь хотите объединиться с master, переопределяя все старые вещи в master. Это именно то, что я хотел сделать, когда наткнулся на этот пост.

Делайте именно то, что вы хотите сделать, за исключением того, что сначала объедините одну ветку с другой. Я только что сделал это, и это сработало отлично.

git checkout Branch
git merge master -s ours

Затем извлеките мастер и объедините в нем свою ветку (теперь все пройдет гладко):

git checkout master
git merge Branch
Gandalf458
источник
1
Спасибо. Это должен быть принятый ответ, безусловно, самый простой и самый правильный. Если ваша ветвь отстает / конфликтует с мастером, то это задача ветвления - объединить и заставить все работать, прежде чем снова объединить все с мастером.
StackHola
Это сработало, спасибо.
Kodie Grantham
12

Если вы находитесь на ветке А, сделайте:

git merge -s recursive -X theirs B

Протестировано на git версии 1.7.8

rafalmag
источник
8

Чтобы действительно правильно выполнить слияние, которое принимает только вход из ветви, которую вы объединяете, вы можете сделать

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

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

git commit --amend

В любом сценарии, о котором я знаю, не будет конфликтов, вам не нужно создавать дополнительные ветви, и это действует как обычный коммит слияния.

Однако это не очень хорошо работает с подмодулями.

thoutbeckers
источник
Что здесь делает -R?
П. Майер Нор
Таким образом вы теряете историю файлов "перемещенных и измененных". Я прав?
elysch
1
-R это - обратный, я обновил ответ, чтобы он был более самодокументированным.
Элайджа Линн
Это не работает, если файлы, которые вы пытаетесь объединить, удаляются во входящей ветви.
РешениеJ
7

Почему его не существует?

Хотя я упоминаю в « git command для создания одной ветви как другую », как имитировать git merge -s theirs, обратите внимание, что Git 2.15 (Q4 2017) теперь более понятен:

Документация для ' -X<option>' для слияний была введена в заблуждение, чтобы предположить, что ' -s theirs' существует, а это не так.

Посмотреть коммит c25d98b (25 сентября 2017 г.) Junio ​​C Hamano ( gitster) .
(Объединено Junio ​​C Hamano - gitster- в коммите 4da3e23 , 28 сентября 2017 г.)

стратегии слияния: избегайте намека на то, что-s theirs " существует

Описание -Xoursопции слияния содержит заключенную в скобки заметку, которая говорит читателям, что она сильно отличается от -s ours, что является правильным, но описание, -Xtheirsкоторое следует за ней небрежно, говорит: «Это противоположность ours», создавая ложное впечатление, что читатели также нуждаются быть предупрежденным, что это очень отличается от того -s theirs, чего в действительности даже не существует.

-Xtheirsявляется вариантом стратегии применительно к рекурсивным стратегии. Это означает, что рекурсивная стратегия по-прежнему объединит все, что может, и будет использовать theirs«логику» только в случае конфликтов.

Это дебаты на уместность или нет theirs стратегии слияния были недавно возвращены в этой теме в сентябре 2017 года .
Это признает старые (2008) темы

Короче говоря, предыдущее обсуждение можно резюмировать как «мы не хотим» -s theirs, поскольку оно поощряет неправильный рабочий процесс ».

Упоминается псевдоним:

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

Ярослав Хальченко пытается отстаивать эту стратегию еще раз, но Хунио С. Хамано добавляет :

Причина, по которой наши и их не являются симметричными, заключается в том, что вы - это вы, а не они - контроль и владение нашей историей и их историей не симметричны.

Как только вы решите, что их история является основной, вы бы предпочли рассматривать свою линию развития как побочную ветвь и выполнять слияние в этом направлении, то есть первый родительский результат получающегося слияния является коммитом в их истории, а второй родитель последний плохой в вашей истории. Так что вы бы в конечном итоге использовать "checkout their-history && merge -s ours your-history ", чтобы сохранить разумность первого родителя.

И в этот момент использование « -s ours» больше не является обходным решением из-за отсутствия « -s theirs».
Это правильная часть желаемой семантики, то есть с точки зрения сохранившейся канонической линии истории, вы хотите сохранить то, что она сделала, сводя на нет то, что сделала другая линия истории .

Джунио добавляет, как прокомментировал Майк Битон :

git merge -s ours <their-ref>фактически говорит: «помечать коммиты, сделанные в <their-ref>их ветке, как коммиты, которые постоянно игнорируются»;
и это важно, потому что, если вы впоследствии объединитесь с более поздними состояниями их ветви, их более поздние изменения будут внесены без внесенных игнорируемых изменений .

VonC
источник
1
Это полезный ответ, но в нем не упоминается один ключевой момент, который я только что понял из ответа Хунио С. Хамано: git merge -s ours <their-ref>фактически он говорит: «пометить коммиты, сделанные до <its-ref> на их ветке, как коммиты, которые следует постоянно игнорировать» «; и это важно, потому что, если вы впоследствии объединитесь с более поздними состояниями их ветви, их более поздние изменения будут внесены без внесенных игнорируемых изменений.
MikeBeaton
1
@MikeBeaton Спасибо. Я включил ваш комментарий в ответ для большей наглядности.
VonC
5

См . Широко цитируемый ответ Хунио Хамано : если вы собираетесь отказаться от зафиксированного контента, просто откажитесь от коммитов или, во всяком случае, не допускайте его в основную историю. Зачем беспокоить всех в будущем, читая сообщения коммитов от коммитов, которым нечего предложить?

Но иногда возникают административные требования или, возможно, какая-то другая причина. Для тех ситуаций, когда вам действительно нужно записывать коммиты, которые ничего не вносят, вы хотите:

(правка: вау, мне раньше удалось ошибиться. Это работает.)

git update-ref HEAD $(
        git commit-tree -m 'completely superseding with branchB content' \
                        -p HEAD -p branchB    branchB:
)
git reset --hard
jthill
источник
3

Этот использует дерево чтения команды git plumbing, но сокращает общий рабочий процесс.

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

Это объединит ваш новый филиал с существующим базовым.

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
Паван Махешвари
источник
Я бы предложил добавить git commit --amendв конец вашего примера, иначе пользователи могут увидеть коммит слияния с git logи предположить, что операция завершена.
Майкл Р
0

Я думаю, что вы на самом деле хотите:

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

Это кажется неуклюжим, но это должно работать. Единственное, что мне действительно не нравится в этом решении, это то, что история git будет сбивать с толку ... Но, по крайней мере, история будет полностью сохранена, и вам не нужно будет делать что-то особенное для удаленных файлов.

briemers
источник
0

Эквивалент (сохраняющий родительский порядок) «git merge -s itss branchB»

Перед слиянием:введите описание изображения здесь

!!! Убедитесь, что вы в чистоте!

Сделать слияние:

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

Что мы сделали ? Мы создали новый коммит, у которого два родителя - наш и их, а коннект коммита - BranchB - их

После слияния:введите описание изображения здесь

Точнее:

git commit-tree -m "take theirs" -p HEAD -p 'SOURCE^{commit}' 'SOURCE^{tree}'
Боаз Наум
источник
0

Этот ответ дал Пол Пладийс. Я просто взял его команды и сделал псевдоним для удобства.

Отредактируйте ваш .gitconfig и добавьте следующее:

[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"

Затем вы можете "git merge -ssis A", выполнив:

git checkout B (optional, just making sure we're on branch B)
git mergetheirs A
Brain2000
источник
-1

Я только недавно должен был сделать это для двух отдельных репозиториев, которые имеют общую историю. Я начал с:

  • Org/repository1 master
  • Org/repository2 master

Я хотел, чтобы все изменения repository2 masterбыли применены repository1 master, принимая все изменения, которые вносит репозиторий2. С точки зрения git, это должна быть стратегия, которая называется -s theirsНО, которой не существует. Будьте осторожны, потому что -X theirsназвано так, как будто это то, что вы хотите, но это НЕ то же самое (об этом даже говорится на странице руководства).

Я решил это, чтобы пойти repository2и создать новую ветку repo1-merge. В той ветке я бегал git pull git@gitlab.com:Org/repository1 -s oursи она сливается нормально без проблем. Я тогда толкаю это к пульту.

Затем я возвращаюсь repository1и создаю новую ветку repo2-merge. В этой ветке я бегуgit pull git@gitlab.com:Org/repository2 repo1-merge которая будет завершена с проблемами.

Наконец, вам нужно будет либо выполнить запрос на слияние, repository1чтобы сделать его новым мастером, либо просто оставить его как ветвь.

Trann
источник
-2

Простой и интуитивно понятный (на мой взгляд) двухэтапный способ сделать это

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

с последующим

git merge -s ours branchB

(который отмечает две ветви как объединенные)

Единственным недостатком является то, что он не удаляет файлы, которые были удалены в BranchB из вашей текущей ветви. Простая разница между двумя ветками позже покажет, есть ли такие файлы.

Этот подход также дает понять из журнала изменений после того, что было сделано - и что было задумано.

Ruben
источник
это может быть правильным для исходного вопроса, однако ФП обновил вопрос в 2008 году таким образом, что этот ответ является неправильным.
user3338098
1
@ user3338098 Да, такая жалость , что они оставили в заблуждение название и просто ударил не так заметны обновления на конце тела вопрос .... потому что этот ответ делает ответ правильно в заголовке вопрос.
RomainValeri
Я полностью согласен с @RomainValeri
AppleCiderGuy