Продвинутая работа с ветками и слиянием

Получение помощи решения конфликтов во время слияния

Все изменения которые git способен слить автоматически уже добавлены в индекс, так что git diff показывает только конфликты. Эта команда использует необычный синтаксис:

$ git diff
diff --cc file.txt
index 802992c,2b60207..0000000
--- a/file.txt
+++ b/file.txt
@@@ -1,1 -1,1 +1,5 @@@
++<<<<<<< HEAD:file.txt
 +Hello world
++=======
+ Goodbye
++>>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt

Отменить коммит который будет выполнен после того как мы разрешим этот конфликт будет иметь двух родителей вместо обычного одного: один родитель будет HEAD, конец текущей ветки; другой будет конец другой ветки, которая сохранится временно в MERGE_HEAD.

Во время слияния, индекс содержит три версии каждого файла. Каждый их этих "проиндексированных файлов" представляет разную версию файла:

$ git show :1:file.txt  # файл которой потомок обоих файлов из сливаемых ветвей
$ git show :2:file.txt  # версия из HEAD.
$ git show :3:file.txt  # версия и сливаемой ветки MERGE_HEAD.

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

Команда diff выше показывает отличия между версией рабочего дерева fie.txt и версиями индексов 2 и 3. Таким образом вместо того чтобы предварять каждую строку одним "+" или "-", теперь используются две колонки: первая колонка используется для отличий между первым родителем и копией рабочей директории и вторая для отличий между вторым родителем и копией рабочего дерева. (Просмотрите секцию "COMBINED DIFF FORMAT" git diff-files для получения дополнительных подробностей формата.)

После улаживания конфликта очевидный способ (но перед обновлением индекса), diff будет выглядеть след.образом:

$ git diff
diff --cc file.txt
index 802992c,2b60207..0000000
--- a/file.txt
+++ b/file.txt
@@@ -1,1 -1,1 +1,1 @@@
- Hello world
-Goodbye
++Goodbye world

Это показывает что наша исправленная версия удалила "Hello world" из первого родителя, удалила "Goodbye" из второго родителя, и добавила "Goodbye world", которая до этого отсутствовала в обоих.

Некоторые особые параметры diff позволяют выполнять diff рабочей директории от любых других этих индексов:

$ git diff -1 file.txt      # diff against stage 1
$ git diff --base file.txt  # same as the above
$ git diff -2 file.txt      # diff against stage 2
$ git diff --ours file.txt  # same as the above
$ git diff -3 file.txt      # diff against stage 3
$ git diff --theirs file.txt    # same as the above.

Команды git log и gitk также предлагают специальную помощь для слияний:

$ git log --merge
$ gitk --merge

Это покажет все коммиты которые существуют только в HEAD или в MERGE_HEAD, и которые затрагивают неслитый файл.

Вы можете также использовать git mergetool, которая позволяет вам сливать неслитые файлы используя внешние инструменты такие как emacs или kdiff3.

Каждый раз как только вы разрешили конфликты в файле, обновите индекс:

$ git add file.txt

и тогда разные версии этого файла в индексе будут "разрушены", после чего git-diff не будет более (по умолчанию) показывать diff-ы для этого файла.

Множественное слияние

Вы можете сливать несколько веток одновременно просто перечисляя их в git merge. Например,

$ git merge scott/master rick/master tom/master

что эквивалентно:

$ git merge scott/master
$ git merge rick/master
$ git merge tom/master

Поддерево

Есть ситуации где вы захотите включить содержимое из стороннего проекта в свой собственный. Вы можете просто выполнить pull из другого проекта если нет конфликтов.

Проблемный случай это когда есть конфиликтующие файлы. Потенциальный кандидат это Makefiles и другие стандартные файлы. Вы можете слить эти файлы но скорее всего не захотите этого делать. Лучшее решение для этой проблемы это слить проект как его собственную поддиректорию. Это не поддерживается стратегией рекурсивного слияния, но просто выполнить pull не сработает.

То что вам нужно это стратегия слияния поддерева, которая поможет вам в таких ситуациях.

В этом примере, давайте скажем у вас есть репозиторий в /path/to/B (но это также может быть и URL если вы пожелаете). Вы хотите слить ветку master этого репозитория в поддиректорию dir-B в вашей текущей ветке.

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

$ git remote add -f Bproject /path/to/B (1)
$ git merge -s ours --no-commit Bproject/master (2)
$ git read-tree --prefix=dir-B/ -u Bproject/master (3)
$ git commit -m "Merge B project as our subdirectory" (4)
$ git pull -s subtree Bproject master (5)

Выгода от использования поддерева слияния это то что оно требует меньше административной заботы от пользователей вашего репозитория. Это работает со старшими (до Git v1.5.2) клиентами и у вас есть код сразу после клонирования.

Как бы там ни было если вы используете подмодули, то вы можете выбрать не перемещать объекты подмодулей. Это проблема возможна со слиянием поддерева.

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

(from Using Subtree Merge)



github logo