Как выборочно объединить или выбрать изменения из другой ветки в Git?

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

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

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

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

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

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

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

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

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

Ответ 1

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

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

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

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

Ответ 2

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

Резюме:

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

    $ 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"
    

Ответ 3

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

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

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

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

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

1) Требуется истинное слияние.

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

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

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

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

git checkout HEAD file1

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

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

Run:

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

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

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

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

Run

git checkout HEAD file1

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

Run

git checkout branchX file2

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

Git уже объединил его в этот момент.


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


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

Ответ 4

Мне не нравятся вышеуказанные подходы. Использование 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

Ответ 5

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

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.

Ответ 6

Хотя некоторые из этих ответов довольно хороши, я чувствую, что никто на самом деле не ответил на исходное ограничение 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.

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

Ответ 7

1800 Информационный ответ полностью верен. Однако, как git noob, "использовать git cherry-pick" было недостаточно для того, чтобы я мог понять это без лишних поисков в Интернете, поэтому я подумал, что опубликую более подробное руководство на случай, если кто-нибудь другой находится в подобной лодке.

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

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

    $ git branch mybranch <base branch>

  • Включите его.

    $ git checkout mybranch

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

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

  • Вытащите все из своей ветки.

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

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

    $ git log

  • Вернитесь к ветке, в которую хотите внести изменения.

    $ git checkout originalbranch

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

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

Совет шляпы: http://www.sourcemage.org/Git_Guide

Ответ 8

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

git checkout master
git checkout feature1 Myclass.java

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

Ответ 9

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

Шаг первый: Разверните ветки

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

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

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

Слэш не требуется экранировать.

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

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

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

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

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

Ответ 10

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

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

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

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

git checkout branchname1 -- file1 file2 etc

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

git checkout branchname2 -- file3 file4 etc

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

git commit

и вам придется много объяснять, что делать в этом сообщении commit.

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

Ответ 11

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

#make a new branch ( this will be temporary)
git checkout -b newbranch
# grab the changes 
git merge --no-commit  featurebranch
# unstage those changes
git reset HEAD
(you can now see the files from the merge are unstaged)
# now you can chose which files are to be merged.
git add -p
# remember to "git add" any new files you wish to keep
git commit

Ответ 12

Я нашел этот пост, чтобы содержать простейший ответ. Просто выполните:

$ #git checkout <branch from which you want files> <file paths>

Пример:

$ #pulling .gitignore file from branchB into current branch
$ git checkout branchB .gitignore

См. сообщение для получения дополнительной информации.

Ответ 13

Самый простой способ - установить ваше репо на ветку, которую вы хотите объединить, после этого запустите

git checkout [branch with file] [path to file you would like to merge]

Если вы запустите

git status

вы увидите, что файл уже поставлен...

Затем запустите

git commit -m "Merge changes on '[branch]' to [file]"

Simple.

Ответ 14

Странно, что git до сих пор не имеет такого удобного инструмента "из коробки". Я использую его сильно, обновляя ветку старой версии (которая по-прежнему имеет много пользователей программного обеспечения) с помощью только некоторых исправлений от текущей ветки версии. В этом случае часто требуется быстро получить только некоторые строки кода из файла в trunk, игнорируя множество других изменений (которые не должны входить в старую версию)... И конечно, в этом случае требуется интерактивное трехстороннее слияние, git checkout --patch <branch> <file path> неприменимо для этой цели селективного слияния.

Вы можете сделать это легко:

Просто добавьте эту строку в раздел [alias] в глобальном файле .gitconfig или local .git/config:

[alias]
    mergetool-file = "!sh -c 'git show $1:$2 > $2.theirs; git show $(git merge-base $1 $(git rev-parse HEAD)):$2 > $2.base; /C/BCompare3/BCompare.exe $2.theirs $2 $2.base $2; rm -f $2.theirs; rm -f $2.base;' -"

Это означает, что вы используете Beyond Compare. Просто при необходимости измените программное обеспечение по вашему выбору. Или вы можете изменить его на трехстороннее автоматическое слияние, если вам не требуется интерактивное избирательное слияние:

[alias]
    mergetool-file = "!sh -c 'git show $1:$2 > $2.theirs; git show $(git merge-base $1 $(git rev-parse HEAD)):$2 > $2.base; git merge-file $2 $2.base $2.theirs; rm -f $2.theirs; rm -f $2.base;' -"

Затем используйте так:

git mergetool-file <source branch> <file path>

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

Ответ 15

Это не совсем то, что вы искали, но мне это было полезно:

git checkout -p <branch> -- <paths> ...

Это смесь некоторых ответов.

Ответ 16

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

Команда по вышеуказанной ссылке:

#You are in the branch you want to merge to
git checkout <branch_you_want_to_merge_from> <file_paths...>

Ответ 17

Я бы сделал

git diff commit1..commit2 filepattern | git -apply -index && & git commit

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

Украдены из: http://www.gelato.unsw.edu.au/archives/git/0701/37964.html

Ответ 18

Мне нравится ответ 'git -interactive-merge', но там проще. Пусть git сделайте это для вас, используя комбинацию rebase из интерактивной и на:

      A---C1---o---C2---o---o feature
     /
----o---o---o---o master

Итак, вы хотите, чтобы C1 и C2 из ветки "feature" (точка ветвления "A" ), но пока что ничего не осталось.

# git branch temp feature
# git checkout master
# git rebase -i --onto HEAD A temp

Который, как указано выше, помещает вас в интерактивный редактор, где вы выбираете строки 'pick' для C1 и C2 (как указано выше). Сохраните и выйдите, а затем он продолжит перебазу и даст вам ветку "temp", а также HEAD в master + C1 + C2:

      A---C1---o---C2---o---o feature
     /
----o---o---o---o-master--C1---C2 [HEAD, temp]

Затем вы можете просто обновить master до HEAD и удалить ветвь temp, и вам будет полезно:

# git branch -f master HEAD
# git branch -d temp

Ответ 19

Я знаю, что этот вопрос старый, и есть много других ответов, но я написал свой собственный script, называемый "pmerge", чтобы частично слить каталоги. Это незавершенная работа, и я все еще участвую в сценариях git и bash.

Эта команда использует git merge --no-commit, а затем не использует изменения, которые не соответствуют предоставленному пути.

Использование: git pmerge branch path
Пример: git merge develop src/

Я не тестировал его широко. Рабочий каталог не должен содержать никаких незафиксированных изменений и невоспроизведенных файлов.

#!/bin/bash

E_BADARGS=65

if [ $# -ne 2 ]
then
    echo "Usage: `basename $0` branch path"
    exit $E_BADARGS
fi

git merge $1 --no-commit
IFS=$'\n'
# list of changes due to merge | replace nulls w newlines | strip lines to just filenames | ensure lines are unique
for f in $(git status --porcelain -z -uno | tr '\000' '\n' | sed -e 's/^[[:graph:]][[:space:]]\{1,\}//' | uniq); do
    [[ $f == $2* ]] && continue
    if git reset $f >/dev/null 2>&1; then
        # reset failed... file was previously unversioned
        echo Deleting $f
        rm $f
    else
        echo Reverting $f
        git checkout -- $f >/dev/null 2>&1
    fi
done
unset IFS

Ответ 20

Вы можете использовать read-tree для чтения или объединения данного удаленного дерева в текущий индекс, например:

git remote add foo [email protected]ample.com/foo.git
git fetch foo
git read-tree --prefix=my-folder/ -u foo/master:trunk/their-folder

Чтобы выполнить слияние, используйте -m вместо этого.

См. также: Как объединить подкаталог в git?

Ответ 21

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

git difftoll <branch-1>..<branch-2>

Ответ 22

Простой подход для выборочного слияния/фиксации по файлу:

git checkout dstBranch git merge srcBranch // make changes, including resolving conflicts to single files git add singleFile1 singleFile2 git commit -m "message specific to a few files" git reset --hard # blow away uncommitted changes

Ответ 23

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

1. Дублирующая ветка временно
$ git checkout -b temp_branch

2. Сброс до последнего желаемого коммита
$ git reset --hard HEAD~n, где n - количество коммитов, которое нужно вернуть

3. Оформите каждый файл из оригинальной ветки
$ git checkout origin/original_branch filename.ext

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

Ответ 24

Если вам нужно только объединить конкретный каталог и оставить все остальное без изменений, но при этом сохранить историю, вы можете попробовать это... создать новую target-branch от master прежде чем экспериментировать.

В следующих шагах предполагается, что у вас есть две ветки: target-branch и source-branch, и каталог dir-to-merge который вы хотите объединить, находится в source-branch. Также предположим, что у вас есть другие каталоги, такие как dir-to-retain в цели, которые вы не хотите изменять и сохранять историю. Кроме того, предполагается, что существуют конфликты слияния в dir-to-merge.

git checkout target-branch
git merge --no-ff --no-commit -X theirs source-branch
# the option "-X theirs", will pick theirs when there is a conflict. 
# the options "--no--ff --no-commit" prevent a commit after a merge, and give you an opportunity to fix other directories you want to retain, before you commit this merge.

# the above, would have messed up the other directories that you want to retain.
# so you need to reset them for every directory that you want to retain.
git reset HEAD dir-to-retain
# verify everything and commit.

Ответ 25

А как насчет git reset --soft branch? Я удивлен, что никто еще не упомянул это.

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