Как выборочно объединить или выбрать изменения из другой ветки в Git? - PullRequest
1311 голосов
/ 16 января 2009

Я использую git для нового проекта, который имеет две параллельные - но в настоящее время экспериментальные - ветви разработки:

  • master: импорт существующей кодовой базы плюс несколько модов, в которых я, как правило, уверен
  • exp1: экспериментальная ветка № 1
  • exp2: экспериментальная ветка № 2

exp1 и exp2 представляют два очень разных архитектурных подхода. Пока я не продвинусь дальше, у меня нет никакого способа узнать, какой из них (если любой) будет работать. Когда я делаю успехи в одной ветке, у меня иногда есть правки, которые были бы полезны в другой ветке, и я хотел бы объединить только те.

Каков наилучший способ объединения выборочных изменений из одной ветви разработки в другую, оставляя после себя все остальное?

Подходы, которые я рассмотрел:

  1. git merge --no-commit с последующей ручной постановкой большого количества правок, которые я не хочу делать общими для ветвей.

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

  3. Вариант вышеизложенного. Оставьте пока ветки exp и используйте два дополнительных локальных репозитория для экспериментов. Это значительно упрощает ручное копирование файлов.

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

Ответы [ 25 ]

913 голосов
/ 31 августа 2009

У меня была точно такая же проблема, как упомянуто вами выше. Но я нашел это более ясным в объяснении ответа.

Резюме:

  • Извлечение путей из ветви, которую вы хотите объединить,

    $ git checkout source_branch -- <paths>...
    

    Подсказка: также работает без --, как показано в связанном посте.

  • или выборочно объединить куски

    $ git checkout -p source_branch -- <paths>...
    

    Либо используйте сброс, а затем добавьте с параметром -p,

    $ git reset <paths>...
    $ git add -p <paths>...
    
  • Окончательно зафиксировать

    $ git commit -m "'Merge' these changes"
    
432 голосов
/ 16 января 2009

Вы используете команду cherry-pick для получения отдельных коммитов из одной ветви.

Если изменения, которые вы хотите, не относятся к отдельным фиксациям, используйте метод, показанный здесь, чтобы разделить фиксацию на отдельные фиксации . Грубо говоря, вы используете git rebase -i для получения исходной фиксации для редактирования, затем git reset HEAD^ для выборочного возврата изменений, затем git commit для фиксации этого бита в качестве новой фиксации в истории.

Здесь есть еще один приятный метод в журнале Red Hat, где они используют git add --patch или, возможно, git add --interactive, который позволяет добавлять только части фрагмента, если вы хотите разделить различные изменения на отдельный файл (поиск на этой странице для «split»).

Разделив изменения, теперь вы можете выбрать те, которые хотите.

284 голосов
/ 03 сентября 2011

Чтобы выборочно объединить файлы из одной ветви в другую, запустите

git merge --no-ff --no-commit branchX

где branchX - ветвь, из которой вы хотите объединиться в текущую ветвь.

Опция --no-commit будет размещать файлы, которые были объединены Git, без их фиксации. Это даст вам возможность модифицировать объединенные файлы так, как вы хотите, а затем зафиксировать их самостоятельно.

В зависимости от того, как вы хотите объединить файлы, существует четыре случая:

1) Вы хотите истинное слияние.

В этом случае вы принимаете объединенные файлы так, как Git автоматически объединяет их, а затем фиксируете их.

2) Некоторые файлы вы не хотите объединять.

Например, вы хотите сохранить версию в текущей ветви и игнорировать версию в ветви, из которой вы объединяете.

Чтобы выбрать версию в текущей ветке, выполните:

git checkout HEAD file1

Эта команда извлечет версию file1 в текущей ветке и перезапишет file1, автоматически добавленную Git.

3) Если вы хотите версию в BranchX (а не истинное слияние).

Пробег:

git checkout branchX file1

Это позволит получить версию file1 в branchX и перезаписать file1, автоматически объединенную с Git.

4) Последний случай, если вы хотите выбрать только определенные слияния в file1.

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

Если Git не может объединить файл автоматически, он сообщит о файле как « unmerged » и создаст копию, в которой вам нужно будет разрешить конфликты вручную.



Для дальнейшего объяснения на примере, скажем, вы хотите объединить branchX с текущей веткой:

git merge --no-ff --no-commit branchX

Затем вы запускаете команду git status для просмотра состояния измененных файлов.

Например:

git status

# On branch master
# Changes to be committed:
#
#       modified:   file1
#       modified:   file2
#       modified:   file3
# Unmerged paths:
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#
#       both modified:      file4
#

Где file1, file2 и file3 - файлы, которые git успешно объединил автоматически.

Это означает, что изменения master и branchX для всех этих трех файлов были объединены без каких-либо конфликтов.

Вы можете проверить, как было выполнено слияние, запустив git diff --cached;

git diff --cached file1
git diff --cached file2
git diff --cached file3

Если вы обнаружите, что слияние нежелательно, вы можете

  1. редактировать файл напрямую
  2. сохранение
  3. git commit

Если вы не хотите объединять file1 и хотите сохранить версию в текущей ветке

Run

git checkout HEAD file1

Если вы не хотите объединять file2 и хотите только версию в branchX

Run

git checkout branchX file2

Если вы хотите автоматически объединить file3, ничего не делайте.

Git уже слил его на этом этапе.


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


Наконец, не забудьте git commit.

97 голосов
/ 21 мая 2009

Мне не нравятся вышеуказанные подходы. Использование cherry-pick отлично подходит для выбора одного изменения, но это неприятно, если вы хотите внести все изменения, кроме некоторых плохих. Вот мой подход.

Нет аргумента --interactive, который вы можете передать git merge.

Вот альтернатива:

У вас есть некоторые изменения в ветке 'feature', и вы хотите, чтобы некоторые, но не все, перешли к 'master' не небрежным образом (т. Е. Вы не хотите выбирать и фиксировать каждую)

git checkout feature
git checkout -b temp
git rebase -i master

# Above will drop you in an editor and pick the changes you want ala:
pick 7266df7 First change
pick 1b3f7df Another change
pick 5bbf56f Last change

# Rebase b44c147..5bbf56f onto b44c147
#
# Commands:
# pick = use commit
# edit = use commit, but stop for amending
# squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

git checkout master
git pull . temp
git branch -d temp

Так что просто оберните это в сценарии оболочки, измените master на $ to и измените функцию на $ from, и все готово:

#!/bin/bash
# git-interactive-merge
from=$1
to=$2
git checkout $from
git checkout -b ${from}_tmp
git rebase -i $to
# Above will drop you in an editor and pick the changes you want
git checkout $to
git pull . ${from}_tmp
git branch -d ${from}_tmp
84 голосов
/ 28 августа 2012

Есть и другой путь:

git checkout -p

Это смесь между git checkout и git add -p и может быть именно тем, что вы ищете:

   -p, --patch
       Interactively select hunks in the difference between the <tree-ish>
       (or the index, if unspecified) and the working tree. The chosen
       hunks are then applied in reverse to the working tree (and if a
       <tree-ish> was specified, the index).

       This means that you can use git checkout -p to selectively discard
       edits from your current working tree. See the “Interactive Mode”
       section of git-add(1) to learn how to operate the --patch mode.
50 голосов
/ 25 августа 2011

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

Допустим, у вас есть ветви master, exp1 и exp2. Вы хотите объединить один файл из каждой экспериментальной ветви в мастер. Я бы сделал что-то вроде этого:

git checkout master
git checkout exp1 path/to/file_a
git checkout exp2 path/to/file_b

# save these files as a stash
git stash
# merge stash with master
git merge stash

Это даст вам различия в файлах для каждого файла, который вы хотите. Ничего более. Не меньше. Полезно, чтобы у вас были радикально отличающиеся изменения файлов между версиями - в моем случае, изменение приложения с Rails 2 на Rails 3.

РЕДАКТИРОВАТЬ : объединяет файлы, но выполняет интеллектуальное объединение. Я не смог выяснить, как использовать этот метод для получения различий в файле (возможно, он все еще будет использоваться для экстремальных различий. Раздражающие мелочи, такие как пробелы, объединяются, если вы не используете опцию -s recursive -X ignore-all-space)

46 голосов
/ 07 мая 2009

1800 Ответ ИНФОРМАЦИИ полностью правильный. Однако, как git noob, «использовать git cherry-pick» было недостаточно для того, чтобы я понял это, не копаясь в Интернете, поэтому я решил опубликовать более подробное руководство на тот случай, если кто-то еще похожая лодка.

В моем сценарии использования было желание выборочно перенести изменения из чужой ветки github в мою собственную. Если у вас уже есть локальный филиал с изменениями, вам нужно только выполнить шаги 2 и 5-7.

  1. Создание (если не создано) локальной ветки с изменениями, которые вы хотите внести.

    $ git branch mybranch <base branch>

  2. Переключитесь на него.

    $ git checkout mybranch

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

    $ git remote add repos-w-changes <git url>

  4. Сноси все с их ветки.

    $ git pull repos-w-changes branch-i-want

  5. Просмотрите журналы фиксации, чтобы увидеть, какие изменения вы хотите:

    $ git log

  6. Переключитесь обратно на ветку, в которую хотите перенести изменения.

    $ git checkout originalbranch

  7. Черри выбирает ваши коммиты, один за другим, с помощью хешей.

    $ git cherry-pick -x hash-of-commit

Наконечник шляпы: http://www.sourcemage.org/Git_Guide

40 голосов
/ 29 июля 2013

Вот как вы можете заменить Myclass.java файл в master ветке на Myclass.java в feature1 ветке. Это будет работать, даже если Myclass.java не существует на master.

git checkout master
git checkout feature1 Myclass.java

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

27 голосов
/ 28 февраля 2012

Простой способ объединить определенных файлов из двух ветвей, а не просто заменить определенные файлы файлами из другой ветви.

Шаг первый: разветвить ветви

git diff branch_b > my_patch_file.patch

Создает файл исправления разницы между текущей веткой и branch_b

Шаг второй: применить исправление к файлам, соответствующим шаблону

git apply -p1 --include=pattern/matching/the/path/to/file/or/folder my_patch_file.patch

полезные замечания по опциям

Вы можете использовать * в качестве шаблона в шаблоне включения.

Бегам не нужно избегать.

Кроме того, вместо этого вы можете использовать --exclude и применять его ко всем, кроме файлов, соответствующих шаблону, или отменить патч с помощью -R

Параметр -p1 является удержанием команды * unix patch и того факта, что содержимое файла исправления предшествует каждому имени файла с a/ или b/ (или более в зависимости от того, как был создан файл исправления), которое вы нужно раздеть, чтобы он мог определить реальный файл с указанием пути к файлу, к которому необходимо применить исправление.

Посетите справочную страницу для git-apply, чтобы узнать больше вариантов.

Шаг третий: третьего шага нет

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

22 голосов
/ 27 сентября 2012

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

Во-первых, вы сделаете необычный шаг, объявив заранее, что вы собираетесь совершить слияние, без мерзавца, который вообще ничего не делает с файлами в вашем рабочем каталоге:

git merge --no-ff --no-commit -s ours branchname1

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

git checkout branchname1 -- file1 file2 etc

Если вы объединялись из более чем одной ветви, повторите при необходимости.

git checkout branchname2 -- file3 file4 etc

Теперь файлы из другой ветки находятся в индексе, готовые к фиксации, с историей.

git commit

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

Пожалуйста, обратите внимание, на случай, если неясно, что это неправильно. Это не в духе того, для чего нужна "ветка", и "вишня" - это более честный способ сделать то, что вы будете делать здесь. Если вы захотите сделать еще одно «слияние» для других файлов в той же ветке, которую вы не перенесли в прошлый раз, это остановит вас сообщением «уже в курсе». Это признак отсутствия ветвления, когда у нас должно быть, в ветке «from» должно быть больше, чем одна другая ветвь.

...