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

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

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

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

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

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

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

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

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

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


person David Joyner    schedule 16.01.2009    source источник
comment
Если изменения в ваших экспериментальных ветках хорошо организованы в виде отдельных коммитов, лучше подумать о слиянии выборочных коммитов вместо выборочных файлов. Большинство ответов ниже предполагают, что это так.   -  person akaihola    schedule 13.02.2010
comment
Разве комбинация git merge -s ours --no-commit, за которой следуют некоторые git read-tree, не будет хорошим решением для этого? См. stackoverflow.com/questions/1214906/   -  person VonC    schedule 04.03.2011
comment
На более свежий вопрос есть однострочный, хорошо написанный ответ: stackoverflow.com/questions/10784523/   -  person brahn    schedule 15.08.2013
comment
Посетите этот блог, чтобы объединить только определенные файлы jasonrudolph.com/blog/2009/02/25/   -  person Ashutosh Chamoli    schedule 28.01.2019


Ответы (26)


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

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

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

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

person 1800 INFORMATION    schedule 16.01.2009
comment
Насколько я понимаю, это бесполезно более запутанно, чем ответ, получивший более высокую оценку. - person Alexander Bird; 03.02.2012
comment
Технически это правильный ответ, правильный ответ кажется запутанным. --- Ответ с более высокими оценками - это просто быстрый и грязный ответ на уловку, который для большинства людей - это все, о чем они говорят (: - person Jay; 09.03.2012
comment
@akaihola: HEAD ^ правильно. См. Man git-rev-parse: суффикс ^ параметра редакции означает первого родителя этого объекта фиксации. Обозначение префикса ^ используется для исключения коммитов, достижимых из коммита. - person Tyler Rick; 13.02.2013
comment
Чтобы разбить коммит на отдельные коммиты, вы должны находиться в ветке, где находится коммит. В противном случае вы потерпите неудачу на шаге rebase -i, потому что вы не можете найти фиксацию. Со мной случилось, надеюсь, не случится с другими. - person Paul Weber; 11.08.2014
comment
Я просто хотел поделиться другим подходом, который кажется самым чистым и менее запутанным из всех: jasonrudolph.com/blog/2009/02/25/ полная простота и великолепие - person superuseroi; 15.09.2014
comment
@Torek - Еще одно ужасное решение git для синхронизации dev-ветки .... Я буду использовать либо (1) резервное копирование вручную; или (2) отдельный клон, чтобы восстановить вещи там, где я хочу, после полного слияния. - person jww; 29.07.2016
comment
Не знаете, какой из подходов «правильный»? Обратите внимание на разницу между файлами и коммитами (см. Примечания внизу). OP хочет объединить ФАЙЛЫ и не упоминает COMMITS. Ответ с более высоким рейтингом относится к файлам; в принятом ответе используется вишневый выбор, который характерен для коммитов. Cherry-pick может быть ключом к выборочному слиянию коммитов, но может быть очень болезненным при перемещении файлов из одной ветки в другую. Хотя коммиты - это сила git, не забывайте, что файлы по-прежнему играют важную роль! - person Kay V; 26.08.2016
comment
@ownsourcingdevtraining спасибо за разъяснения - я действительно больше не использую git, и когда я писал этот ответ (7 лет назад?), я, вероятно, действительно не знал о различиях - person 1800 INFORMATION; 29.08.2016
comment
@ 1800INFORMATION благодарим за ответ. Всегда интересно наблюдать, как наше понимание со временем развивается, а? :) Было здорово, что вы дали решение, которое сработало для OP и многих других. Если вы чувствуете, что различие между коммитами и файлами стоит выделить заранее, я предложил формулировку для редактирования вашего ответа, которая может помочь людям выбрать решение. Пожалуйста, отредактируйте дальше, как считаете нужным, и еще раз спасибо! - person Kay V; 29.08.2016
comment
@ownsourcingdevtraining Я думаю, что OP, возможно, принял ответ, потому что, возможно, они относительно новички в git и просто думали о файлах, когда действительно хотели объединить коммит. Я пришел сюда искать, как выборочно объединять коммиты, но в поиске использовал файлы;) - person Doktor J; 20.09.2016
comment
Не может git использовать логику, чтобы отличить то, что вы разместили, от устаревших файлов в вашей рабочей ветке? Устаревшие файлы не следует объединять, более новый файл из ветки по умолчанию следует просто скопировать в рабочую ветку IMO. - person Vance McCorkle; 28.11.2017

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

Резюме:

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

     $ git checkout source_branch -- <paths>...
    
    Hint: It also works without `--` like seen in the linked post.
    
  • или выборочно объединять скряга

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

В качестве альтернативы используйте сброс, а затем добавьте с опцией -p,

    $ git reset <paths>...
    $ git add -p <paths>...
  • Наконец совершить

     $ git commit -m "'Merge' these changes"
    
person Community    schedule 31.08.2009
comment
Связанная статья Барта Дж. - лучший подход. Понятно, просто, одна команда. Это тот, который я собираюсь использовать. :) - person Pistos; 07.10.2009
comment
Это не настоящее слияние. Вы выбираете изменения по файлу, а не по фиксации, и вы потеряете любую существующую информацию о фиксации (автор, сообщение). Конечно, это хорошо, если вы хотите объединить все изменения в некоторых файлах, и нормально, что вы должны повторно выполнить все коммиты. Но если файлы содержат как изменения, подлежащие слиянию, так и другие, подлежащие отмене, один из методов, представленных в других ответах, подойдет вам лучше. - person akaihola; 13.02.2010
comment
вроде работает, к сожалению, изменений с git diff не вижу - person mykhal; 04.03.2010
comment
это замечательно, если вы создали изменения, которые должны быть не частью тематической ветки, над которой вы работаете, а какой-то другой. - person Filip Dupanović; 18.09.2010
comment
@mykhal и другие: это автоматически помещает файлы в индекс, поэтому, если вы отметили foo.c, сделайте git reset HEAD foo.c, чтобы отключить этот файл, и затем вы сможете сравнить его. Я выяснил это, попробовав и вернувшись сюда, чтобы найти ответ на этот вопрос. - person michiakig; 15.02.2011
comment
чтобы увидеть изменения, вы также можете использовать: git diff --cached - person OderWat; 21.06.2011
comment
Если вы собираетесь использовать diff для ручного слияния файлов, вы можете просто сделать diff в первую очередь и вручную внести изменения в своей локальной ветке, а не сначала проверять, затем сравнивать, а затем вручную вносить изменения, которые у вас уже были. . - person Hazok; 19.04.2012
comment
У @spacemanaki есть то, что я считаю лучшим ответом. git checkout feature-branch foo.c, затем git reset HEAD foo.c, затем git add -p, чтобы выявить нужные вам различия или исключить плохие, затем git commit foo.c, чтобы сохранить изменения, и git checkout foo.c, чтобы сбросить индекс. Работал как шарм. - person radicand; 19.08.2012
comment
У меня сработал совет @Zach. Чтобы расширить его, если вы используете difftool, вы можете использовать git difftool origin develop src/main/path/to/file.ext и перенести нужные строки кода. Это особенно хорошо работает, если вы просто хотите выделить несколько строк. - person gMale; 03.11.2012
comment
Неужели только я не вижу здесь ответа? Все, что я вижу, это то, что у меня была точно такая же проблема, как упомянуто вами выше. Но я нашел это более ясным, объясняя ответ. - person Adam Grant; 30.03.2013
comment
Насколько я понимаю, в случае конфликта это «слияние» просто заменяет конфликтующие строки в текущей ветке строками из source_branch, что не является слиянием, это замена. - person MiamiBeach; 13.11.2014
comment
Согласно этому ответ git checkout -p <revision> -- <path> будет таким же, как выполнение первых трех описанных вами команд :) - person 7hi4g0; 23.01.2015
comment
@Pistos, я не могу найти на этой странице "Bart J". Может быть, с октября 2009 года имена пользователей и т. Д. Изменились? - person Johnny Utahh; 04.05.2015
comment
@JohnnyUtahh Многое может случиться за 6 лет. :) Может быть, ответ был удален или удален, или пользователь удалил свою учетную запись и т.д. Кто знает. :) - person Pistos; 05.05.2015
comment
Это не настоящее слияние, но это именно то, что я искал! - person Felipe; 09.02.2016
comment
Я восстановил ответ. Поскольку он служил той цели, которую я искал. - person Susheel Javadi; 09.02.2016
comment
Ссылка в ответе сделала все. Спасибо :) - person node_saini; 09.05.2017
comment
Это НЕ слияние, это замена. Более близким действием к фактическому слиянию по файлу может быть git diff branch1 branch2 filepath > 1.patch, а затем git apply 1.patch. Конечно, это принесет только чистые изменения файлов и ничего больше. - person Arijoon; 29.01.2018
comment
В связанном сообщении используется $ git checkout source_branch <paths>..., а не $ git checkout source_branch -- <paths>.... Что правильно?! - person Black; 13.12.2018
comment
@Pistos Где мы можем найти эту легендарную ссылку на простой ответ Барта? - person rolfedh; 22.07.2020
comment
@rolfedh За 6 лет многое может произойти. :) Может быть, ответ был удален или удален, или пользователь удалил свою учетную запись и т.д. Кто знает. :) - Пистос 5 мая '15 в 16:49 - person Pistos; 23.07.2020
comment
@Pistos: Нет, нет никаких удаленных ответов (вы можете видеть это с вашими 10 000+ репутационными точками) или удаленных пользователей (у них были бы серые значки пользователей). Скорее всего, пользователь изменил свое отображаемое имя. Было бы неплохо, если бы это было решено (поскольку к нему относятся комментарии к нескольким ответам). - person Peter Mortensen; 28.07.2020
comment
Если это не относится к этому ответу, то Барт Джей теперь Кори, а ссылка - sourcemage.org/Git_Guide (поскольку 1800 ИНФОРМАЦИЯ не изменила имя (это было 1800 ИНФОРМАЦИЯ 07.05.2009 - до Пистоса) комментарий) и ответ nosatalian не содержит ссылок). - person Peter Mortensen; 28.07.2020
comment
Хотя носителей ответ имел ссылку в предыдущих версиях. Он был удален (из-за поломки и опасности). - person Peter Mortensen; 28.07.2020
comment
Но Ссылка sourcemage.org также не работает. - person Peter Mortensen; 28.07.2020
comment
Как упоминалось другими, на самом деле это не слияние, и из графика вообще не будет очевидно, что это слияние, даже частичное. - person Eric Duminil; 28.11.2020

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

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 не может объединить файл автоматически, он сообщит о файле как «не объединенный» и создаст копию, в которой вам нужно будет разрешить конфликты вручную.



Для дальнейшего объяснения на примере предположим, что вы хотите объединить 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 и хотите сохранить версию в текущей ветке

Запустить

git checkout HEAD file1

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

Запустить

git checkout branchX file2

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

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


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


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

person alvinabad    schedule 03.09.2011
comment
Однако будьте осторожны: если git merge --no-commit branchX - это просто перемотка вперед, указатель будет обновлен, и, таким образом, --no-commit игнорируется. - person cfi; 12.03.2012
comment
@cfi Как насчет добавления --no-ff, чтобы предотвратить такое поведение? - person Eduardo Costa; 16.08.2013
comment
Я определенно предлагаю обновить этот ответ с помощью опции Эдуардо --no-ff. Я прочитал все это (что в остальном было здорово) только для того, чтобы мое слияние было ускорено. - person Funktr0n; 02.05.2014
comment
Это решение дает лучшие результаты и гибкость. - person Thiago Macedo; 24.07.2014
comment
В отличие от ответа, набравшего наибольшее количество голосов, это решение сохранило мою историю слияний, что важно для меня, поскольку я переплетаю частичные коммиты между ветвями. Я не пробовал все другие предлагаемые решения, поэтому, возможно, некоторые из них тоже делают это. - person ws_e_c421; 19.10.2014
comment
Хороший Альвинабад; но, к сожалению, Android Studio говорит, что частичная фиксация во время слияния недопустима! Использовал вашу команду, а затем просмотрел 106 этапов фиксации файлов в Android Studio. Там я проверил около 30, которые хочу поместить в эту ветку. Наш вариант использования состоит в том, что у нас есть этап процесса проверки кода (PR) от нескольких дней до недели, поэтому я делаю всю работу в прототипе, а затем добавляю изменения в обзор кода. Таким образом, я могу добиться прогресса и не ждать рецензентов кода. - person maxweber; 16.11.2015
comment
Лучшее решение в компании, где кодеры регулярно переписывают / переделывают модули. Этот метод слияния дает максимальный контроль и (п) обзор. - person Michel; 25.05.2016
comment
Если вы обнаружите, что file1 не существует в currentBranch, и вы не хотите фиксировать его как часть слияния, удалите его вручную перед фиксацией. Попытка сделать git checkout HEAD file1 потерпит неудачу. - person Pablo Adames; 23.09.2017
comment
БОЛЬШОЕ ВНИМАНИЕ. Это слияние работает в одном направлении, но файлы, которые вы не включили в слияние, будут отображаться как УДАЛЕННЫЕ, если вы решите слить свой восходящий мастер обратно в исходную ветвь. Я использую процесс, подобный git-flow, с использованием основной ветки (производственная основная ветка), промежуточной ветки (промежуточной основной ветки сервера) и тематических веток, основанных на промежуточной ветке. Использование этой стратегии привело к тому, что мое обратное слияние от мастера обратно к постановке окончательно провалилось, думая, что все, что я не слил из постановки в мастер, удалено. Сюда входят целые файлы и фрагменты. ВАС ПРЕДУПРЕЖДЕНЫ - person danielricecodes; 08.02.2018
comment
Чтобы расширить комментарий @PabloAdames: обратите внимание, что file1 уже был подготовлен, поэтому, чтобы удалить файл из постановки, я бы либо git reset file1 перед удалял его вручную, либо git add file1 после удаление вручную. - person djvg; 19.04.2018
comment
YEEEEEEEAAAAAAAAAAAAAAAAHHHHHHHHHHHHH - person Gaspa79; 22.04.2020

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

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

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

У вас есть некоторые изменения в «функции» ветки, и вы хотите перенести некоторые, но не все из них, в «мастер» небрежным способом (т.е. вы не хотите выбирать и фиксировать каждое из них)

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
person nosatalian    schedule 21.05.2009
comment
Я исправил форматирование - это довольно хороший метод, если вы хотите сделать выборку коммитов - person 1800 INFORMATION; 04.09.2009
comment
Я использую эту технику сейчас, и, похоже, она действительно хорошо сработала. - person dylanfm; 10.02.2010
comment
Возможно, вы захотите изменить git rebase -i $to на git rebase -i $to || $SHELL, чтобы пользователь мог вызывать git --skip и т. Д., Если это необходимо, в случае сбоя перебазирования. Также стоит объединить строки вместе с && вместо символов новой строки. - person sircolinton; 24.02.2011
comment
К сожалению, похоже, что ссылка в ответе мертва. - person ThomasW; 06.04.2012
comment
Мало того, что ссылка мертва, у нее есть предупреждение о плохой репутации WOT. Поэтому я удалил его. - person Jean-François Corbett; 10.01.2013

Есть еще один способ:

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.
person Chronial    schedule 28.08.2012
comment
Это, безусловно, самый легкий и простой метод, если у вас есть только управляемое количество изменений, которые нужно объединить. Я надеюсь, что больше людей заметят этот ответ и проголосуют за него. Пример: git checkout --patch exp1 file_to_merge - person Tyler Rick; 13.02.2013
comment
Аналогичный ответ опубликован на этот вопрос: stackoverflow.com/a/11593308/47185 - person Tyler Rick; 14.02.2013
comment
О, я не знал, что у Checkout есть патч! Вместо этого я сделал checkout / reset / add -p. - person Daniel C. Sobral; 26.02.2013
comment
Поистине самый простой способ. git checkout -p featurebranch filename. И лучше всего, когда команда запускается, она дает вам y / n / e /? / ... и т. Д. возможность решить, как объединить файл. Я пробовал с e и даже мог отредактировать патч перед применением ... Как это круто. Настоящий лайнер для слияния выборочных файлов из других веток. - person infoclogged; 02.03.2016
comment
это должен быть правильный ответ - person Nikita; 26.06.2021

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

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

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

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

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

person Eric Hu    schedule 25.08.2011
comment
Также обратите внимание: вы можете сделать несколько файлов из данной ветки встроенными, например git checkout exp1 path/to/file_a path/to/file_x - person EMiller; 05.10.2011
comment
Это прекрасно. Я сделал git checkout feature <path>/*, чтобы получить группы файлов. - person isherwood; 30.01.2014
comment
Это работает нормально, но добавлены два дополнительных объекта фиксации. Не большое дело, но немного грязно - person MightyPork; 20.09.2015
comment
@MightyPork, ты прав. К сожалению, с тех пор, как я написал это так давно, я больше не уверен, почему шаги git stash и git merge stash там вместо git commit. - person Eric Hu; 22.09.2015
comment
Я думаю, это ясно. Таким образом, он объединяет один файл, не обязательно перезаписывая предыдущие изменения в целевой ветви. - person MightyPork; 23.09.2015
comment
@EricHu, как вы прокомментировали, не нужно возиться с тайником и слиянием. Просто сделайте коммит после проверки файлов из других веток. На этом этапе изменения уже внесены. - person Mark Edington; 05.03.2016
comment
Похоже, что он вообще не выполняет слияние. Он просто заменил файл HEAD файлом тайника. - person Keith Tyler; 26.09.2018

ИНФОРМАЦИЯ 1800 ответ совершенно правильный. Однако, как новичку в Git, мне было недостаточно использовать 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. Cherry выбирает ваши коммиты один за другим с помощью хешей.

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

person Cory    schedule 07.05.2009
comment
Совет: сначала используйте команду git cherry (сначала см. Руководство), чтобы определить коммиты, которые вы еще не объединили. - person akaihola; 13.02.2010
comment
Это работает .. 1. создал новую ветку 2. создал несколько файлов / внес некоторые изменения 3. зафиксировал 4. проверил главную ветку 5. Запустите git cherry-pick -x hash-of-commit и разрешите конфликты слияния, вы в порядке идти. - person RamPrasadBismil; 12.05.2016
comment
Ваша ссылка больше не работает. Можете ли вы обновить его? - person creep3007; 16.04.2018
comment
Ссылка (фактически) не работает: Требуется аутентификация ... https://www.sourcemage.org запрашивает ваше имя пользователя и пароль. На сайте написано: «Зона ограниченного доступа». (Также выполняется перенаправление на HTTPS.) - person Peter Mortensen; 28.07.2020
comment
спасибо, я просто удалил его, так как не знаю, где сейчас найти оригинал - person Cory; 29.07.2020

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

git checkout master
git checkout feature1 Myclass.java

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

person maestr0    schedule 29.07.2013
comment
Это не сольется. Он просто перезапишет изменения на мастере изменениями из ветки feature1. - person Skunkwaffle; 07.09.2013
comment
Идеально, я искал такое слияние, где theirs перезаписывают ours = ›+1 Ура;) - person oHo; 25.09.2013
comment
Иногда все, что вам нужно сделать, это заменить весь файл, так что это то, что я хотел, но вам нужно убедиться, что вы хотите потерять все изменения, внесенные в этот файл. - person MagicLAMP; 10.09.2014
comment
Самое чистое решение, учитывая, что OP специально хотел заменить весь файл эквивалентом в другой ветке: 2. Manual copying of common files into a temp directory followed by ...copying out of the temp directory into the working tree. - person Brent Faust; 26.09.2014

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

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

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, чтобы узнать о дополнительных параметрах.

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

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

person masukomi    schedule 27.02.2012
comment
Это было очень полезно, когда в current_branch было много дополнительных изменений, которые нужно было сохранить. Получил различие только изменений, внесенных branch_b как: git diff HEAD ... branch_b (да - три периода делают волшебный трюк). - person Saad Malik; 16.08.2015
comment
@masukomi, на шаге 2 нельзя добавить в качестве аргумента патч-файл, созданный на шаге 1? - person Spiralis; 20.01.2016
comment
Для меня все изменения отклонены. Есть идеи, почему? - person LinusGeffarth; 01.02.2020
comment
Первоначальная мысль @LinusGeffarth заключается в том, что, возможно, вы перевернули ветки при создании патча? будет следить за пределами SO, чтобы узнать, сможем ли мы это выяснить. - person masukomi; 05.02.2020

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

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

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

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

git checkout branchname1 -- file1 file2 etc.

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

git checkout branchname2 -- file3 file4 etc.

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

git commit

И в этом сообщении о фиксации вам будет много объяснений.

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

person Community    schedule 27.09.2012
comment
Ваша первая команда (git merge --no-ff --no-commit -s outs branchname1) - это именно то, что я искал! Спасибо! - person RobM; 23.11.2012
comment
С несколькими ветвями, требуемой историей, необходимостью слияния одного файла (ов) и необходимостью изменения содержимого файла перед нажатием, это кажется достойной альтернативой. Например, dev = ›master, но вы хотите изменить определение хоста или подобное, прежде чем нажимать на master. - person timss; 11.05.2014
comment
Это сработало отлично. Единственное изменение, которое я сделал, - это сделать git merge --continue вместо последнего git commit. Спасибо! - person Johann; 14.09.2020

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

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

Если ты бежишь

git status

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

Тогда беги

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

Простой.

person dsieczko    schedule 29.10.2013
comment
Это почти лучший ответ, который я нашел. см. jasonrudolph.com/blog/2009/02/25/ Так ясно, кратко и просто работает! - person superuseroi; 15.09.2014
comment
который полностью заменит содержимое файлов из исходной ветки, а не объединит - person Amare; 13.09.2018
comment
Я как раз собирался вот так ответить, я думал, что изобретаю новые вещи, на которые еще не ответили! Но это самый простой способ сделать это. Это должно быть на высоте! - person Irfandy Jip; 11.12.2019

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

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

Пример

Вытягивание файла .gitignore из ветки B в текущую ветку:

git checkout branchB .gitignore

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

person Stunner    schedule 04.11.2013
comment
На самом деле это не слияние, а перезапись файла в текущей ветке. - person Igor Ralic; 12.02.2014
comment
@igrali Это полезный комментарий, но по сравнению со сложностью правильных способов сделать это, это хороший обходной путь. Просто нужно быть очень осторожным. - person owensmartin; 16.08.2014

Это мой рабочий процесс для слияния выборочных файлов.

# 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
person Felix    schedule 18.02.2010
comment
Я использовал небольшую вариацию этого. Вместо слияния я собрал вишню. Это делает свою работу. Единственным недостатком этого подхода является потеря ссылки на исходный хеш фиксации. - person Matt Florence; 24.06.2011

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

Это легко сделать:

Просто добавьте эту строку в раздел [alias] вашего глобального .gitconfig или локального .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>

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

person JimStar    schedule 03.07.2014

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

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

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

person Felipe    schedule 09.02.2016
comment
Это действительно полезно и может быть добавлено к тому, что для меня является лучшим ответом, ответом @alvinabad. При выполнении: git checkout HEAD file1 для сохранения текущей версии и удаления файла file1, можно использовать параметр -p для выбора части файла для объединения. спасибо за выходку! - person Simon C.; 14.02.2018

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

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

# You are in the branch you want to merge to
git checkout <branch_you_want_to_merge_from> <file_paths...>
person Susheel Javadi    schedule 19.06.2012
comment
ты это тестировал? я уверен, что файлы будут заменены из ‹branch_you_want_to_merge_from›, а не объединены - person Amare; 13.09.2018

Я бы сделал

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

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

Он украден с Re: как вытащить только несколько файлов из одной ветки в другую?

person lumpidu    schedule 26.01.2011
comment
В некоторых ситуациях это может быть очень удобно. Однако, если изменения находятся в другой ветке, вы можете просто оформить заказ с кончика этой ветки, как в ответе Барта Дж. Выше. - person cdunn2001; 22.06.2011
comment
Кто такой Bart Js? - person Black; 13.12.2018
comment
Это работает так же, как git checkout branch ... ниже - локальные изменения, отсутствующие в branch, удаляются (!). Они помечены как удаленные в файле патча, поскольку простой diff не проверяет историю коммитов. - person Jan Stefanides; 07.08.2020

Для меня git reset --soft branch - это самый простой способ выборочно выбрать изменения из другой ветки, поскольку эта команда помещает в мое рабочее дерево все изменения различий, и я могу легко выбрать или отменить то, что мне нужно.

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

person GarryOne    schedule 01.04.2019

Мне нравится предыдущий ответ "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

Что, как и в предыдущем ответе, переводит вас в интерактивный редактор, где вы выбираете строки «выбора» для 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 и удалить временную ветку, и все готово:

# git branch -f master HEAD
# git branch -d temp
person Wade    schedule 09.12.2011
comment
Можете ли вы связать напрямую с другим ответом, о котором вы говорите (имена пользователей недостаточно стабильны, поскольку они могут измениться в любое время)? (Сортируйте по самому старому, чтобы ограничить количество возможных ответов (перед вашим).) - person Peter Mortensen; 28.07.2020
comment
@PeterMortensen хороший замечание, я почти уверен, что теперь у меня там правильный. - person Wade; 02.08.2020

Я написал свой собственный сценарий 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 with 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
person Andy    schedule 27.05.2011

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

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

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

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

person kenorb    schedule 25.02.2016

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

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
person Dave C    schedule 22.11.2017
comment
Разве git reset --hard не избавляется от зафиксированных изменений? - person Hashim Aziz; 05.09.2020
comment
@Prometheus согласно документации: сбрасывает индекс и рабочее дерево. Любые изменения в отслеживаемых файлах в рабочем дереве после ‹commit› отменяются. Так что сама фиксация безопасна. - person pepoluan; 21.12.2020

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

1. Временно дублируется ветка
$ git checkout -b temp_branch

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

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

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

person JBaczuk    schedule 16.10.2018

Если вам нужно только объединить конкретный каталог и оставить все остальное нетронутым, но при этом сохранить историю, вы можете попробовать это ... создать новый 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.
person code4kix    schedule 06.11.2018

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

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

см. также https://sites.google.com/site/icusite/setup/git-difftool

person raratiru    schedule 07.10.2017

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

(Я говорю «псевдо-слияние», потому что мне не нужна и не нужна фиксация слияния; я просто хочу объединить вклады обеих версий файла так, как я считаю нужным.)

Мой подход основан на подходе, принятом в https://stackoverflow.com/a/39916536/341994. К сожалению, этот вопрос закрыт как дубликат (ошибочно, на мой взгляд: это не дубликат этого вопроса, и неправильно отвечать и закрывать как дубликат, что и сделал там ответчик). Но в этом ответе есть некоторые ошибки, поэтому я модернизировал и очистил подход. Вместо checkout и reset я использую restore, и я не беспокоюсь о том, чтобы зафиксировать то, что мне не нужно.

Хорошо, представьте, что у меня есть три файла:

$ ls
a   b   f

Но я хочу псевдо-объединить только одно из них, a, из otherbranch. Давайте посмотрим на них, чтобы увидеть, как будет выглядеть ситуация. Вот моя версия:

$ cat a
line one
line two
line three
line four
line five

Вот версия другого филиала:

$ git show otherbranch:a
line one
line two edited
line three
line four
line five
line six

Уловка здесь в том, что мы собираемся использовать индекс как блокнот (в конце концов, для чего он нужен). Итак, мы начинаем (ШАГ 1), убедившись, что наша версия скопирована в индекс:

$ git add a

Теперь (ШАГ 2) мы можем использовать restore, чтобы получить версию из otherbranch (в настоящее время restore лучше, чем checkout, поскольку это позволяет нам говорить более ясно):

$ git restore --source otherbranch a

На первый взгляд это выглядит плохо. Теперь мы полностью перезаписали наш a версией из otherbranch, как вы можете видеть:

$ cat a
line one
line two edited
line three
line four
line five
line six

Но не волнуйтесь! Предыдущая версия a все еще находится в индексе, как вы можете видеть:

$ git diff a
diff --git a/a b/a
index abf51fa..333614b 100644
--- a/a
+++ b/a
@@ -1,6 +1,7 @@
 line one
-line two
+line two edited
 line three
 line four
 line five
+line six

Хорошо, теперь мы готовы к ключевому ходу (ШАГ 3). Мы выполняем интерактивное исправление add нашего файла из рабочего дерева в индекс.

Мы могли бы сказать git add -p a, чтобы запустить интерактивный процесс исправления, и в этом случае мы будем получать блоки по одному. Но в этом случае есть только один кусок, и я все равно хочу его отредактировать, поэтому я говорю:

$ git add --e a

В результате мы открываем патч-файл различий в нашем редакторе! Это выглядит так:

 line one
-line two
+line two edited
 line three
 line four
 line five
+line six

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

 line one
 line two
 line three
 line four
 line five
+line six

Мы закрываем редактор, и патч применяется к индексной версии файла. Но мы еще не закончили! otherbranch версия a все еще находится в рабочем дереве:

$ cat a
line one
line two edited
line three
line four
line five
line six

Версия, которая нам нравится, есть в указателе, помните? Чтобы получить его (ШАГ 4), мы просто вызываем git restore простой и простой (опять же, это современный способ; restore лучше, чем reset, и может применяться к одному файлу):

$ git restore a

Теперь наш a правильный, и мы все закончили:

$ cat a
line one
line two
line three
line four
line five
line six

На этом этапе мы могли бы выполнить фиксацию, но это не обязательно; мы выполнили то, что намеревались достичь.

person matt    schedule 02.05.2021