Создание двух ветвей идентично

У меня две ветки - A и B. B был создан из A.

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

Теперь я хочу, чтобы ветвь A была идентичной ветке B. Я не могу использовать git revert, потому что мне нужно вернуть слишком много коммитов - я хочу только вернуть фиксации, которые были сделаны на ветке A, но не как результат объединения ветки B.

Решение, которое я нашел, это клонировать ветвь B в другую папку, удалить все файлы из рабочей папки ветки A, скопировать файлы из папки temp branch B и добавить все необработанные файлы.

Есть ли команда git, которая делает то же самое? Некоторый git отключить переключатель, который я пропустил?

Ответ 1

Существует много способов сделать это, и какой из них вы должны использовать, зависит от того, какой результат вы хотите, и тем, что вы и кто-то, кто сотрудничает с вами (если это общий репозиторий), ожидают увидеть в будущем.

Три основных способа сделать это:

  • Не беспокойтесь. Отключить ветвь A, создать новый A2 и использовать это.
  • Используйте git reset или эквивалент для повторной точки A в другом месте.

    Методы 1 и 2 фактически одинаковы в долгосрочной перспективе. Например, предположим, что вы начинаете с отказа от A. На некоторое время все развиваются на A2. Затем, когда все используют A2, вы полностью удаляете имя A. Теперь, когда A ушел, вы даже можете переименовать A2 в A (все остальные, использующие его, должны будут сделать то же самое переименование, конечно). На данный момент, что, если что-либо, выглядит по-другому между тем случаем, когда вы использовали метод 1 и случай, когда вы использовали метод 2? (Есть одно место, которое вы все еще можете увидеть разницу, в зависимости от того, как долго длится ваш "длинный пробег", и когда вы истекаете старые рефлоги.)

  • Сделайте слияние, используя специальную стратегию (см. ниже "альтернативные стратегии слияния" ). Здесь вы хотите -s theirs, но не существует, и вы должны подделать его.

Сторона примечания: там, где ветвь "создана из", в основном не имеет значения: git не отслеживает такие вещи, и в общем случае ее невозможно обнаружить позже, поэтому, возможно, это не имеет значения для вас, (Если это действительно имеет значение для вас, вы можете наложить несколько ограничений на то, как вы манипулируете веткой, или пометить ее "начальную точку" тегом, чтобы вы могли восстановить эту информацию механически позже. Но это часто является признаком того, что вы делаете что-то неправильно в первую очередь - или, по крайней мере, ожидаете что-то из git, которое git не предоставляет. См. также сноску 1 ниже.)

Определение ветки (см. также Что именно мы подразумеваем под веткой?)

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

Например, предположим, что у нас есть граф фиксации, который выглядит примерно так, где o узлы (а также отмеченные *) представляют committ и A и B являются именами ветвей:

o <- * <- o <- o <- o <- o   <-- A
      \
       o <- o <- o           <-- B

A и B каждая точка для наибольшей фиксации ветки ветки или, точнее, структуры данных ветвления, сформированной путем запуска с некоторого фиксации и обработки всех достижимых коммитов, причем каждая фиксация указывает на некоторые parent commit (s).

Если вы используете git checkout B, так что вы находитесь в ветке B и делаете новый коммит, новый фиксатор устанавливается с предыдущим концом B в качестве его (одиночного) родителя и git изменяет идентификатор, хранящийся под именем ветки B, так что B указывает на новый отзыв: наибольшая фиксация:

o <- * <- o <- o <- o <- o   <-- A
      \
       o <- o <- o <- o      <-- B

Фиксированная метка * является базой слияния двух советов ветки. Эта фиксация и все предыдущие коммиты находятся в обеих ветвях. 1 База слияния имеет значение, например, для слияния (duh:-)), но также и для таких вещей, как git cherry и других релизов типа.

Вы упомянули, что ветвь B иногда сливалась обратно в A:

git checkout A; git merge B

Это делает новое слияние, которое является фиксацией с двумя родителями 2. Первый родительский элемент является предыдущим концом текущей ветки, то есть предыдущим концом A, а вторым родителем является именованное коммит, то есть наибольшая фиксация ветки B. Перерисовывая выше немного более компактно (но добавляя еще немного -, чтобы линия o улучшалась), мы начинаем с:

o--*--o--o--o--o      <-- A
    \
     o---o--o---o     <-- B

и в итоге:

o--*--o--o--o--o--o   <-- A
    \            /
     o---o--o---*     <-- B

Переместим * в новую базу слияния A и B (которая на самом деле является концом B). Предположительно, мы добавим еще несколько коммитов в B и, возможно, сможем объединить несколько раз:

...---o--...--o--o    <-- A
     /       /
...-o--...--*--o--o   <-- B

Что git делает по умолчанию с git merge <thing>

Чтобы выполнить слияние, вы проверяете какую-либо ветвь (A в этих случаях), а затем запустите git merge и укажите хотя бы один аргумент, обычно другое имя ветки, например B. Команда слияния начинается с включения имени в идентификатор фиксации. Имя ветки превращается в идентификатор наибольшей фиксации подсказки на ветке.

Затем git merge находит базу слияния. Это те коммиты, которые мы маркировали с помощью *. Техническое определение базы слияния является самым низким общим предком в графе фиксации (а в некоторых случаях может быть более одного), но мы просто перейдем к "фиксации помечены *" здесь для простоты.

Наконец, для обычных слияний git выполняется две команды git diff. 3 Первый git diff сравнивает commit * с фиксацией HEAD, то есть кончик текущая ветвь. Второй diff сравнивает commit * с аргументом commit, то есть кончиком другой ветки (вы можете назвать конкретную фиксацию, и она не должна быть концом ветки, но в нашем случае мы объединяем B в A, поэтому мы получим те две концы с кончиками ветвей).

Когда git находит какой-либо файл, модифицированный по сравнению с версией слияния, в обеих ветвях, git пытается объединить эти изменения в полу-умном (но не очень умном) способе: если оба изменения добавляют тот же текст в том же регионе, git хранит одну копию сложения. Если оба изменения удаляют один и тот же текст в том же регионе, git удаляет этот текст только один раз. Если оба изменения изменяют текст в том же регионе, вы получаете конфликт, если точно не совпадают изменения (тогда вы получаете одну копию модификаций). Если одна сторона делает одно изменение, а другая сторона делает другое изменение, но изменения, похоже, не перекрываются, git принимает оба изменения. В этом суть трехстороннего слияния.

Наконец, если все идет хорошо, git создает новую фиксацию, в которой есть два (или более, как мы уже отмечали в сноске 2) родителя. Рабочее дерево, связанное с этим новым фиксацией, представляет собой git, когда оно выполнило свое трехстороннее слияние.

Альтернативные стратегии слияния

В то время как git merge стратегия по умолчанию recursive имеет -X параметры ours и theirs, они не делают то, что мы хотим здесь. Они просто говорят, что в случае конфликта git должен автоматически разрешить этот конфликт, выбрав "наше изменение" (-X ours) или "их изменение" (-X theirs).

Команда merge имеет другую стратегию полностью, -s ours: в ней говорится, что вместо того, чтобы различать базу слияния с двумя коммитами, просто используйте наше исходное дерево. Другими словами, если мы находимся на ветке A, и мы запускаем git merge -s ours B, git сделает новое слияние с вторым родителем, являющимся концом ветки B, но исходное дерево, соответствующее версии в предыдущий кончик ветки A. То есть код для нового коммита будет точно соответствовать коду для его родителя.

Как указано в этом другом ответе, существует несколько способов заставить git эффективно внедрить -s theirs. Я думаю, что проще всего объяснить эту последовательность:

git checkout A
git merge --no-commit -s ours B
git rm -rf .         # make sure you are at the top level!
git checkout B -- .
git commit

Первый шаг - убедиться, что мы находимся на ветке A, как обычно. Второй - запустить слияние, но не допустить результата (--no-commit). Чтобы упростить слияние для git - это не нужно, это просто делает вещи более быстрыми и более тихими - мы используем -s ours, чтобы git мог полностью пропустить шаги diff, и мы избегаем всех конфликтов конфликтов слияния.

На этом этапе мы добираемся до мяса трюка. Сначала мы удаляем весь результат слияния, так как он фактически бесполезен: мы не хотим, чтобы дерево работы было получено от команды tip A, а от вершины B. Затем мы проверяем каждый файл с кончика B, делая его готовым к фиксации.

Наконец, мы завершаем новое слияние, которое имеет в качестве первого родителя старый кончик A и как его второй родитель - кончик B, но имеет дерево из commit B.

Если график перед фиксацией был:

...---o--...--o--o    <-- A
     /       /
...-o--...--*--o--o   <-- B

то новый график теперь:

...---o--...--o--o--o   <-- A
     /       /     /
...-o--...--o--o--*     <-- B

Новая база слияния является концом B, как обычно, и с точки зрения графика фиксации это слияние выглядит точно так же, как и любое другое слияние. Что необычно в том, что исходное дерево для нового слияния на кончике A точно совпадает с деревом источника для кончика B.


1 В этом конкретном случае, поскольку две ветки остаются независимыми (никогда не были объединены), это также, вероятно, точка, в которой была создана одна из двух ветвей (или, возможно, даже там, где они были созданы), хотя вы не можете доказать, что на данный момент (потому что кто-то мог использовать git reset или различные другие трюки, чтобы перемещать метки ветвей в прошлом). Однако, как только мы начинаем слияние, база слияния, очевидно, уже не является отправной точкой, и разумная стартовая точка становится сложнее найти.

2Технически, объединение слиянием - это любое совершение с двумя или более родителями. git звонки сливаются с более чем двумя родителями "осьминог сливается". По моему опыту, они не распространены, кроме как в репозитории git для самого git, и, в конце концов, они достигают того же, что и несколько обычных двух родительских слияний.

3 Диффы обычно выполняются внутри до некоторой степени, а не выполняются фактические команды. Это позволяет много коротких оптимизаций. Это также означает, что если вы пишете пользовательский драйвер слияния, этот пользовательский драйвер слияния не запускается, если git не обнаружит, что файл изменен в обоих diff. Если он изменен только в одном из двух различий, объединение по умолчанию просто принимает измененный.

Ответ 2

Оформить заказ вашей целевой ветки:

git checkout A;

Чтобы удалить все в ветки A и сделать его таким же, как B:

git reset B --hard;

Если вам нужно отменить все локальные изменения (отменить, но не испортить git, как git revert):

git reset HEAD --hard;

Когда вы закончите, не забудьте обновить удаленную ветвь (используйте --force или -f на случай, если вам нужно переопределить историю).

git push origin A -f;

Ответ 3

Вот крайняя идея (это грязный и не мерзкий способ делать вещи)...

  1. Перейти к ветки B (Git Checkout B).
  2. Скопируйте все содержимое этой ветки (Ctrl + C)
  3. Перейти к ветки A (git checkout A)
  4. Удалить все из ветки A (выделите все мышью и удалите)
  5. Скопируйте все содержимое из ветки B в папку, где находились все материалы ветки A. (Ctrl + V)
  6. Поставьте все новые изменения (git add.)
  7. Зафиксируйте поэтапные изменения (git commit -m "Ветвь A теперь такая же, как B")

Ответ 4

Вот хороший способ, которым я пользуюсь, чтобы сделать это для меня. Идея состоит в том, чтобы получить diff из A и B, зафиксировать этот diff, отменить его, чтобы создать противоположный коммит, и чем вишню, который в последний раз возвращает коммит в A

вот команды

git checkout B
git checkout -b B_tmp 
git merge --squash A
git commit -a -m 'diff'
git revert ⬆_commit_sha(paste here 'diff' commit sha, get from 'git log -1')
git checkout A                    
git cherry-pick revert_commit_sha (B_tmp branch last commit)
git branch -d B_tmp

и вот вы здесь, A - это то же самое, что и B, и у вас есть только один коммит в истории A, который сбрасывается так же, как B-ветвь.

Ответ 5

Это то, что работало для меня с TortoiseGit

Шаги:

  1. ЭКСПОРТИРУЙТЕ branch в формате ZIP, с которым вы хотите достичь идентичности, в ваше предпочтительное местоположение:enter image description here

  2. Распакуйте архив /zip файл.

  3. Скопируйте папку .git из исходной кодовой базы в эту unzipped-folder (УБЕДИТЕСЬ, что target branch выбран в исходной кодовой базе)

  4. git add .

  5. git commit -m "Achieving identicality with abc-branch"

  6. git push подтолкнуть изменения к репо. (--up-stream при необходимости)

TortoiseGit: https://tortoisegit.org

Удачи...

Ответ 6

Вам нужно git reset отделить А до ветки B. См. docs.