Git: объединение поддерева из одной ветки в другую

У меня возникли проблемы с объединением поддерева ветки разработки обратно в ветвь интеграции.

У меня две ветки, одна из них была использована для разработки, а одна - для интеграции. Обширная разработка была выполнена в ветке разработки, и я хочу объединить ее часть. Конкретная часть, которую я хочу объединить, содержится в одном поддереве ветки разработки.

Моя структура каталогов такова:

[Branch A]              [Branch B]
    |                       |
    +--Dir1                 +--Dir1
    +--Dir2                 +--Dir2
        |                   |   |
        +--DirA             |   +--DirA
            |               |       |
            +--File1        |       +--File1
            +--File2        |       +--File2
                            |       +--File3
                            |       +--File4
                            +--Dir3

Я хочу объединить Branch B/Dir2/DirA в Branch A/Dir2/DirA. Я хочу, чтобы File1 и File2 были объединены, и File3 и File4 должны быть созданы в Branch A. Я не хочу выбирать Dir3 или любые изменения в Dir1.

Я пробовал шаги, описанные kernel.org для слияния поддеревьев, но они терпят неудачу, когда я делаю дерево git read-tree с:

error: Entry 'Dir1/DirA/File1' overlaps with 'Dir1/DirA/File1'.  Cannot bind.

Я пробовал использовать поддерево script, которое размещено на github, но мне не очень повезло с ним. Когда я это сделаю:

git checkout Branch_A
git subtree merge -P Dir2/DirA Branch_B

Я вижу доказательства того, что Dir3 был объединен, и слияние с ошибками не выполняется.

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

Ответы

Ответ 1

То, что вы хотите, может быть просто:

git merge Branch_B -s ours  [OPTIONAL]
git checkout Branch_B Dir2/DirA

Подробнее

Этот рецепт копирует поддерево из Branch_B в Branch_A, а также (OPTIONALLY) также создает транзакцию слиянием в истории проекта git:

  • Переключитесь на Branch_A, если вас там уже нет.

    git checkout Branch_A

  • (ДОПОЛНИТЕЛЬНО) Установите фиксацию слияния, не меняя никаких файлов в вашей рабочей области (merge стратегия "наш" ). Эта "пустая" фиксация просто добавляет другую ветку в качестве второго родителя, создавая ссылку истории. Если это неважно для вас, вы можете пропустить этот шаг; это не требуется для последующего шага.

    git merge Branch_B -s ours

    ПРИМЕЧАНИЕ. Делайте это только в чистой рабочей области; эта команда может выйти из строя, если у вас есть какие-либо файлы, измененные или поставленные.

  • Скопируйте содержимое поддерева Dir2/DirA из Branch_B в рабочее пространство, не меняя активной ветки.

    git checkout Branch_B Dir2/DirA

  • Зафиксируйте файлы из Branch_B в Branch_A. Если вы опустите --amend, то для этого шага создается новый фиксатор; используя это, вместо этого сделайте новые файлы частью более раннего слияния.

    git commit --amend

Форма checkout на шаге 3 может показаться незнакомой, но официальная документация утверждает, что обычное поведение переключения ветвей на самом деле просто удобство, когда эта форма не используется:

Если никакие пути не указаны, git checkout также будет обновлять HEAD, чтобы установить указанную ветку как текущую ветвь.

Итак, если вы предоставляете явный путь, он не будет переключать ветки, просто скопируйте файлы с этого пути.

Ответ 2

Проблема заключается в том, что git не предназначен для такой работы. Рассмотрим следующую историю, которая может привести к тому, что ваши две ветки A и B из вопроса:

C - D - E - F - G - H
 \
  H - I

с веткой A, указывающей на commit I и веткой B, указывающей на commit H. Теперь вы хотите перенести некоторые изменения в B на A.

Например, коммит D и F изменен Dir2, E, G и H сделал что-то до Dir1 и Dir3. Поэтому на самом деле вы хотите, чтобы в вашу интеграционную ветвь A добавили D и F.

Это означает, что ветвь B была создана недостаточно, и вы действительно хотите ее переделать (по крайней мере) двумя чистыми ветвями. Что-то вроде:

git checkout -b topic_I_want_to_merge_touching_Dir2 C
git cherry-pick D
git cherry-pick F
git checkout -b topic_I_dont_want_to_merge_now C
git cherry-pick E
git cherry-pick G
git cherry-pick H
git checkout B
git merge topic_I_want_to_merge_touching_Dir2

Если есть коммиты, которые касаются обоих, Dir1 и Dir2, тогда эти коммиты являются "плохими", так как они не адресуют ни одну тему, но выполняют сразу несколько действий. В этом случае вам может потребоваться не только перетасовка коммитов, но и изменение фиксации на хорошие, используя git-rebase -i.

Конечно, вы можете просто объединить ветвь A и исправить полученное дерево. Но это вряд ли позволит продолжить работу над "не объединенными" изменениями в A, потому что когда вы объедините потомок из A, вам придется снова исправить полученное дерево, потому что изменения E, G и H не будут включены в дерево, которое git готовит для вас, так как эти коммиты уже объединены.

Ответ 4

Я бы просто сделал dirA подмодулем и представил для него 3-е репо.