Объединить ряд ревизий с Git или TortoiseGit

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

Если бы я использовал SVN или TortoiseSVN, я мог бы просто объединить изменения из основного ствола немного за раз, а не все сразу, использование диапазона ревизий для слияния. Могу ли я сделать что-то подобное с git или TortoiseGit? То есть, могу ли я объединить ряд изменений в моей ветви разработки вместо слияния всех изменений сразу?

3 ответов


резюме

использовать Git из командной строки, вы можете перебазировать feature на master:

git rebase master feature

# Now force push the changes to your remote
git push <remote> feature --force

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

вы также можете использовать --onto флаг, чтобы указать ряд, где <start commit> является эксклюзивным началом диапазона:

git rebase --onto master <start commit> feature

наконец, cherry-pick может фактически принять ряд аргументов. С master филиала выезжали:

# Cherry-pick all commits from `<start commit>`
# exclusive, not included) to feature:
git cherry-pick <start commit>..feature

Подробное Объяснение

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

Enter git rebase

лучший способ сделать это на самом деле git rebase. Вообще-то, это идеально. Что он будет делать, это взять все коммиты, которые я сделал в feature и сделать копии!--46--> из тех, кто совершает в том же порядке, за исключением того, что он возобновляет их поверх последней редакции целевой ветви, что в моем случае было master.

это важно по двум причинам:

  1. это эквивалентно слиянию master на feature. Почему? Потому что feature в основном воссоздан поверх самых последних версий masterвсе...masterтеперь коммиты существуют в история feature, в том числе совершает в master это feature еще не было.

  2. Git повторно применяет коммиты один раз, для того, чтобы, если есть какие-либо конфликты, они вводятся в процесс в нескольких меньших, более управляемых частях, по одному за раз, что именно то, что я надеялся сделать!

вот как это выглядит визуально (примеры адаптированы из официальный Linux ГИТ документации к ядру):

      A---B---C feature
     /
D---E---F---G master

в приведенном выше примере commits F и G, где совершил на master так как я ветвистый feature от него. Я хочу синхронизировать эти изменения с feature:

git rebase master feature

теперь мои ветви выглядят так:

              A'--B'--C' feature
             /
D---E---F---G master
     \
      A---B---C (no branch)

совершает A через C были воссозданы как A' через C' поверх последней версии master. Старые коммиты все еще существуют в репозитории Git, но поскольку нет указателя ветви, который ссылается на них, они в конечном итоге будут мусором, собранным Git.


Да, вы можете сделать это. Предположим, что ваш репозиторий выглядит следующим образом:

      master
 A---[B]
  \
   \                       feature
    (c1)---(c2)---(...)---(c100)

вы хотите объединить feature филиала в master, но там много коммитов. Вместо этого, давайте сделаем новую ветку tmp это указывает на более раннюю фиксациюfeature:

 git branch tmp c2

 A---[B]
  \
   \        tmp               feature
    (c1)---[(c2)]---(...)---(c100)

теперь tmp указывает на c2. Теперь мы можем объединить только коммиты c1...c2 в master без учета c3...c100:

 git checkout master
 git merge tmp

сейчас,tmp на следующий пакет коммитов (нам нужно -f to force, поскольку tmp уже существует). Например, если мы хотим перейти к c6 теперь использовать это:

 git branch -f tmp c6

повторите это, пока все коммиты, которые вы хотите объединить в.


Примечание: ответ Джона правильный; это просто дополнительное объяснение, основанное на последующих вопросах в комментариях – мне просто нужно было немного больше места:)

мне нравится идея не создавать временную ветвь для слияния в коммитах, но проблема с использованием git merge <commit-hash> это только слияние в одной редакции, а не их диапазон (правильно?). Если бы у меня было 100 ревизий для слияния, разве мне не пришлось бы использовать эту команду 100 раз?

нет, в Git, история всегда связана. Поэтому, если вы объедините фиксацию в master и между общим предком master и что фиксация больше фиксаций, они полностью сохранены. В отличие от SVN, Git сохраняет полные ссылки на предыдущие коммиты (с неограниченным количеством указателей, поэтому вы можете объединить сразу несколько ветвей). Так что в итоге вы всегда будете видеть, где начиналась ветка, что на ней происходило, что было объединено между собой и где было объединено назад в главную ветку-только название (вернее метка) ветви не сохраняется (за исключением текста автоматического слияния, если это считается ^^).

так что ваша история может, например, выглядеть так:

* -- A -- * ---------- * ----- * -- * -- M [master]
      \
       \
        B1 -- B2 -- B3 -- B4 -- B5 -- B6 -- B7 -- B8 -- B9 [br]

Предположим, вы хотите объединить B9 (руководитель совершает на ветке br) в M где master бранч указывает на. При прямом слиянии вы получите это (# слияния коммитов):

* -- A -- * ---------- * ----- * -- * -- M ---------------- # [master]
      \                                                    /
       \                                                  /
        B1 -- B2 -- B3 -- B4 -- B5 -- B6 -- B7 -- B8 -- B9 [br]

так даже если вы удалите указатель ветви br, вы все еще можете видеть все коммиты, которые произошли в этой отдельной ветви.

или если вы хотите объединить в несколько шагов, вы можете легко объединить его такой:

* -- A -- * -- * -- * -- * -- M -- #---------- # --------------- # --- # [master]
      \                           /           /                 /     /
       \                         /           /                 /     /
        B1 -- B2 ------------ - B3 -- B4 -- B5 -- B6 -- B7 -- B8 -- B9 [br]

и снова вы всегда можете оглянуться на все дерево и все отдельные коммиты, которые были сделаны на ветке-даже если вы удалите ветку (которая снова просто удаляет указатель).

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