команда git, чтобы сделать одну ветку похожей на другую

81

Я пытаюсь взять ветку с изменениями и вернуть ее, чтобы она была идентичной тому апстриму, от которого она расходилась. Изменения как местные , так и не были вытеснены на GitHub, так что ни один git resetили git rebaseдействительно жизнеспособны, так как они меняют историю, что это плохо с ответвлением , которая уже была нажата.

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

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

Арне Клаассен
источник
9
Если вы не заботитесь о сохранении изменений, почему бы просто не удалить и не создать ветку заново? «История проекта» не должна быть священной. Git - это инструмент, помогающий разработчикам общаться. Если эти изменения не помогают, выбросьте их.
wnoise
+100 @wnoise - особенно если изменения уже были объединены.
wuputah
7
Я забочусь о сохранении истории, потому что она опубликована для совместной работы, и я, возможно, захочу вернуться к ней. Зачем использовать контроль версий, если вы держите только самую последнюю версию?
Арне Клаассен
1
Это субъективный аргумент, но для меня цель VCS не в том, чтобы записывать каждую мелочь из истории проекта, а только в том, чтобы записывать изменения в контенте (коммиты), чтобы вы могли управлять деревом / историей на основе по этим коммитам (ветвление, слияние, перебазирование, сброс и т. д.), и позволяют просматривать отчеты на основе истории (различия, журналы, обвинения и т. д.). git - это «трекер глупого контента» - я рассматриваю его как инструмент для управления исходным кодом, а не машину времени.
wuputah
5
Как вы сказали, это субъективно. Я забочусь о том, чтобы иметь возможность проанализировать оставленные подходы и увидеть, какие решения были приняты в какой-то момент в прошлом. И меня волнует, что мое решение отказаться от чего-то не разрушает точки слияния, на которые могут указывать другие.
Арне Клаассен

Ответы:

107

Вы можете объединить свою восходящую ветку со своей devветкой с помощью специального драйвера слияния «keepTheirs» :
см. « « git merge -s theirs»Необходимо, но я знаю, что его не существует ».
В вашем случае .gitattributesпотребуется только один и такой keepTheirsсценарий:

mv -f $3 $2
exit 0

git merge --strategy=theirs Симулятор # 1

Отображается как слияние с восходящим потоком в качестве первого родителя.

Джефроми упоминает (в комментариях) merge -s ours, объединив вашу работу в восходящем потоке (или во временной ветке, начиная с восходящего потока), а затем переместив вашу ветку вперед к результату этого слияния:

git checkout -b tmp origin/upstream
git merge -s ours downstream         # ignoring all changes from downstream
git checkout downstream
git merge tmp                        # fast-forward to tmp HEAD
git branch -D tmp                    # deleting tmp

Это дает преимущество записи вышестоящего предка в качестве первого родителя, так что слияние означает «поглощение этой устаревшей тематической ветки», а не «уничтожение этой тематической ветки и замена ее вышестоящей» .

(Изменить 2011):

OP сообщил об этом рабочем процессе в этом сообщении в блоге :

Зачем мне это снова?

Пока мое репо не имело ничего общего с общедоступной версией, все было в порядке, но поскольку теперь я хотел бы иметь возможность сотрудничать по WIP с другими членами команды и внешними участниками, я хочу убедиться, что мои общедоступные ветки надежный для других, чтобы разветвляться и извлекать из него, т.е. больше не нужно переустанавливать и сбрасывать вещи, которые я помещал в удаленную резервную копию, поскольку теперь он находится на GitHub и общедоступен.

Так что мне остается только решить, как мне поступить.
В 99% случаев моя копия попадает в основной сервер восходящего потока, поэтому я хочу работать со своим мастером и большую часть времени продвигаться в восходящий поток.
Но время от времени то, что у меня есть wip, становится недействительным из-за того, что идет вверх по течению, и я откажусь от некоторой части своего wip.
На этом этапе я хочу вернуть своего мастера обратно в синхронизацию с восходящим потоком, но не уничтожать какие-либо точки фиксации на моем публично выдвинутом мастере. Т.е. я хочу слияние с восходящим потоком, которое закончится набором изменений, который сделает мою копию идентичной исходной .
И вот что git merge --strategy=theirsнадо делать.


git merge --strategy=theirs Симулятор # 2

Отображается как слияние с нашим в качестве первого родителя.

(предложено jcwenger )

git checkout -b tmp upstream
git merge -s ours thebranch         # ignoring all changes from downstream
git checkout downstream
git merge --squash tmp               # apply changes from tmp but not as merge.
git rev-parse upstream > .git/MERGE_HEAD #record upstream 2nd merge head
git commit -m "rebaselined thebranch from upstream" # make the commit.
git branch -D tmp                    # deleting tmp

git merge --strategy=theirs Симулятор # 3

В этом сообщении в блоге упоминается :

git merge -s ours ref-to-be-merged
git diff --binary ref-to-be-merged | git apply -R --index
git commit -F .git/COMMIT_EDITMSG --amend

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


git merge --strategy=theirs Симулятор # 4

(тот же пост в блоге)

В качестве альтернативы, если вы хотите поддерживать быструю пересылку локальных ветвей восходящего потока, потенциальный компромисс заключается в том, чтобы работать с пониманием того, что для sid / unstable ветвь восходящего потока может время от времени сбрасываться / перемещаться (на основе событий, которые в конечном итоге отсутствуют вашего контроля на стороне восходящего проекта).
Это не имеет большого значения, и работа с этим предположением означает, что легко поддерживать локальную ветвь восходящего потока в состоянии, при котором она принимает только быстрые обновления.

git branch -m upstream-unstable upstream-unstable-save
git branch upstream-unstable upstream-remote/master
git merge -s ours upstream-unstable
git diff --binary ref-to-be-merged | git apply -R --index --exclude="debian/*"
git commit -F .git/COMMIT_EDITMSG --amend

git merge --strategy=theirs Симулятор # 5

(предложено Бараком А. Перлмуттером ):

git checkout MINE
git merge --no-commit -s ours HERS
git rm -rf .
git checkout HERS -- .
git checkout MINE -- debian # or whatever, as appropriate
git gui # edit commit message & click commit button

git merge --strategy=theirs Симулятор # 6

(предложено все тем же Майклом Гебецройтером ):

Вмешался Майкл Гебетсройтер, заявив, что я «жульничал»;) и предложил другое решение с низкоуровневыми командами сантехники:

(это не было бы git, если бы это было невозможно с командами git only, все в git с diff / patch / apply не является настоящим решением;).

# get the contents of another branch
git read-tree -u --reset <ID>
# selectivly merge subdirectories
# e.g superseed upstream source with that from another branch
git merge -s ours --no-commit other_upstream
git read-tree --reset -u other_upstream     # or use --prefix=foo/
git checkout HEAD -- debian/
git checkout HEAD -- .gitignore
git commit -m 'superseed upstream source' -a

git merge --strategy=theirs Симулятор # 7

Необходимые шаги можно описать так:

  1. Замените свое рабочее дерево на восходящее
  2. Применить изменения к индексу
  3. Добавить восходящий поток в качестве второго родителя
  4. Зафиксировать

Команда git read-treeперезаписывает индекс другим деревом, выполняя второй шаг , и имеет флаги для обновления рабочего дерева, выполняя первый шаг . При фиксации git использует SHA1 в .git / MERGE_HEAD в качестве второго родителя, поэтому мы можем заполнить его для создания фиксации слияния. Следовательно, этого можно добиться с помощью:

git read-tree -u --reset upstream                 # update files and stage changes
git rev-parse upstream > .git/MERGE_HEAD          # setup merge commit
git commit -m "Merge branch 'upstream' into mine" # commit
VonC
источник
7
Вы всегда можете просто использовать нашу вместо их: проверьте другую ветку, слейте в нее свою, а затем перемотайте свою к слиянию. git checkout upstream; git merge -s ours downstream; git checkout downstream; git merge upstream. (При необходимости используйте временную ветвь в восходящем направлении.) Это дает преимущество записи вышестоящего предка в качестве первого родителя, так что слияние означает «поглотить эту устаревшую ветвь темы», а не «уничтожить эту ветвь темы и заменить это с апстримом ».
Cascabel
@Jefromi: как всегда, отличное замечание. Я включил это в свой ответ.
VonC
Другой вариант - например, git merge --strategy = theirs Simulation # 1 - за исключением того, что этот сохраняет бренд в качестве первого родителя слияния: git checkout -b tmp origin / upstream git merge -s ours downstream # игнорируя все изменения, сделанные нижестоящим git checkout downstream git merge --squash tmp # применить изменения из tmp, но не как слияние. git rev-parse upstream> .git / MERGE_HEAD # записать вверх по течению как второй заголовок слияния git commit -m "переназначить наш из восходящего потока" # сделать коммит. git branch -D tmp # удаление tmp
jcwenger
2
Ого, кто бы мог подумать, что --strategy = их стратегия может быть реализована множеством способов. Теперь, если бы это могло быть просто в следующей версии git
Арне Клаассен
VonC и его знания поразительны. Он как Джонскит из мерзавцев. :)
sjas
13

Мне кажется, вам просто нужно сделать:

$ git reset --hard origin/master

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

** На самом деле изменения все еще присутствуют, если вы зафиксировали их локально, так как коммиты все еще будут в вашем git reflog, обычно в течение как минимум 30 дней.

Wuputah
источник
это работает, если вам нужно это изменение любой ценой, потому что это может изменить историю ветки (вы должны нажать -f). которые можно настроить на блокировку, поэтому на практике он будет работать только для ваших личных репозиториев.
ribamar
12

Теперь это можно сделать довольно легко:

$ git fetch origin
$ git merge origin/master -s recursive -Xtheirs

Это синхронизирует ваше локальное репо с источником и сохраняет историю.


источник
5
git merge -s recursive -Xtheirsне объединяет двоичные файлы автоматически, поэтому вы попадаете в конфликтную ситуацию, которую нужно разрешать вручную. Рабочие процессы на основе git merge -s oursэтого не страдают.
Стефан
Похоже, это создает пустую фиксацию.
Робин Грин
Мои извинения - это действительно работает, но git showпри фиксации слияния отображаются только разрешения конфликтов, и -Xtheirs, очевидно, нет разрешения конфликтов, если они используются.
Робин Грин
5

Еще одна симуляция для git merge -s theirs ref-to-be-merged:

git merge --no-ff -s ours ref-to-be-merged         # enforce a merge commit; content is still wrong
git reset --hard HEAD^2; git reset --soft HEAD@{1} # fix the content
git commit --amend

Альтернативой двойному сбросу будет применение обратного патча:

git diff --binary ref-to-be-merged | git apply -R --index
Михас
источник
Интересное использование обратного патча. +1
VonC
Сброс у меня не сработал, я получил «фатальный: неоднозначный аргумент 'HEAD2': неизвестная ревизия или путь не в рабочем дереве». . (Да, я напечатал HEAD^2) Метод исправления сработал.
user247702
@Stijn: Скорее всего, вы действительно напечатали ^неправильно. Иногда другие нажатия клавиш, такие как «ctrl-c», отображаются как «^ C». - Если вы действительно набрали правильный "^", вы обнаружили серьезную ошибку в своей версии git.
michas
@michas В Windows символ ^ используется для экранирования, поэтому, чтобы использовать его как литерал, вы должны экранировать его с самим собой. git reset --hard HEAD^^2; git reset --soft HEAD@{1}
Джошуа Уолш
4

Также есть способ с небольшой помощью команды сантехников - ИМХО, самый простой. Скажем, вы хотите подражать «их» для случая с двумя ветвями:

head1=$(git show --pretty=format:"%H" -s foo)
head2=$(git show --pretty=format:"%H" -s bar)
tree=$(git show --pretty=format:"%T" -s bar)
newhead=$(git commit-tree $tree -p $head1 -p $head2 <<<"merge commit message")
git reset --hard $newhead

Это объединяет произвольное количество голов (2 в приведенном выше примере), используя дерево одной из них (полоса в приведенном выше примере, предоставляющая дерево «их»), игнорируя любые проблемы с различиями / файлами (дерево фиксации - это команда низкого уровня, поэтому она не заботится о них). Обратите внимание, что голова может быть только 1 (что эквивалентно выбору вишни с «их»).

Обратите внимание, что родительский заголовок указывается первым, может влиять на некоторые вещи (см., Например, --first-parent команды git-log), так что имейте это в виду.

Вместо git-show можно использовать все, что может выводить дерево и хэши фиксации - все, что используется для синтаксического анализа (cat-file, rev-list, ...). Вы можете следить за всем с помощью git commit --amend, чтобы интерактивно украсить сообщение фиксации.

Михал Солтыс
источник
На мой взгляд, это самый простой вариант. Вы используете команды сантехники, чтобы сказать: «Создайте новый объект фиксации с этим деревом, этим первым родителем, этим вторым родителем. Затем укажите заголовок на этот новый коммит». Это именно то, что мы хотим «git merge -s theirs» делать. Недостатком является то, что вам нужно сохранить 4 разных хэша, чтобы эта операция работала.
Michael R
2

Тяжелая рука, но черт возьми, что может пойти не так?

  • Посмотрите на ветку X, которую вы хотите выглядеть как Y
  • cp -r .git /tmp
  • Проверить ветку Y git checkout y
  • rm -rf .git && cp -r /tmp/.git .
  • Зафиксируйте и подтолкните любую разницу
  • СДЕЛАННЫЙ.
Sscarduzio
источник
Это самый простой способ сделать две ветки идентичными с помощью грубой силы при условии, что вы не заботитесь о ведении истории слияния.
Damon D
1

перейдите в удаленную ветвь восходящего потока и выполните операцию git mergeсо стратегией слияния, установленной на ours.

git checkout origin/master
git merge dev --strategy=ours
git commit ...
git push

Вся история по-прежнему будет присутствовать, но у вас будет дополнительная фиксация слияния. Здесь важно начать с версии, в которой вы хотите быть, и слиться oursс веткой, в которой находится github.

келлоти
источник
1
Мне нужно обратное. Это займет мою ветку и интегрирует ее в восходящий поток, но оставит исходный заголовок без изменений. Но мне нужно взять восходящий поток и интегрировать его в свою ветку, оставив мою голову похожей на восходящий поток. В основном что-то вроде --strategy=theirs, за исключением того, что самое близкое --strategy=recursive -X=theirsне совсем так.
Арне Клаассен
--strategy=theirsпрямо противоположно --strategy=ours. Вы начинаете с противоположного конца (так что начните с github и выполните слияние другим способом).
kelloti
нет --strategy=theirs, в чем проблема. Ближайший из --strategy=recursive -X theirsних не совсем противоположный, поскольку он не удалит посторонние локальные изменения, если они не конфликтуют.
Арне Клаассен
Эти двое - противоположности: git checkout dev; git merge origin/master --strategy=oursиgit checkout origin/master; git merge dev --strategy=ours
wuputah
2
@Arne: См. Мой комментарий к ответу VonC. Наличие oursстратегии дает возможность полностью реализовать theirsстратегию.
Cascabel
1

Используйте git reset НАЗАД!

Вы можете сделать ветку похожей на любую другую фиксацию git reset, но вы должны делать это круговым способом.

Чтобы ветка при фиксации <old>выглядела как фиксация <new>, вы можете сделать

git reset --hard <new>

чтобы сделать <new>содержимое рабочего дерева.

Тогда сделай

git reset --mixed <old> 

чтобы вернуть ветку к исходной фиксации, но оставив рабочее дерево в <new> состоянии .

Затем вы можете добавить и зафиксировать изменения, чтобы ваша ветка точно соответствовала содержимому <new>фиксации.

Это противоречит интуиции, что для перехода из <old>состояния в состояние <new>вам нужно выполнить git reset от <new> до <old> . Однако с этой опцией --mixedрабочее дерево остается, <new>а указатель ветки установлен на <old>, так что, когда изменения зафиксированы, ветка выглядит так, как мы хотим.

Предупреждение

Не теряйте отслеживание своих коммитов, например, забывайте, что <old>делаете, когда делаете git reset --hard <new>.

Иван
источник
0

Я следил за этими ролями:

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

НА СВОЙ СОБСТВЕННЫЙ РИСК

git fetch origin
git reset --hard origin/<branch>
git merge origin/<branch> -s recursive -Xtheirs
git push -f <remote> <branch>
эссо
источник