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

StackOverflow https://stackoverflow.com/questions/449541

Вопрос

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

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

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

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

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

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

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

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

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

Это было полезно?

Решение

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

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

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

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

Другие советы

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

Краткие сведения:

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

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

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

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.

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

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

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.

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

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

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

Ответ 1800 INFORMATION абсолютно правильный.Однако, будучи новичком в 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. Выбирайте свои коммиты, один за другим, с хэшами.

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

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

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

git checkout master
git checkout feature1 Myclass.java

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

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

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

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 для получения дополнительных опций.

Шаг третий:третьего шага не существует

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

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

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

и вам придется многое объяснить в этом сообщении о фиксации.

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

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

#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

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

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

Пример:

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

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

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

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

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

git status

вы увидите файл, который уже готов...

Тогда беги

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

Просто.

Странно, что у git до сих пор нет такого удобного инструмента "из коробки".Я активно использую его при обновлении какой-нибудь ветки старой версии (в которой все еще много пользователей программного обеспечения) с помощью просто некоторые исправления ошибок из ветки текущей версии.В этом случае часто бывает необходимо быстро получить просто некоторые строки кода из файла в trunk, игнорируя множество других изменений (которые не должны были войти в старую версию)...И, конечно интерактивный трехсторонний в этом случае необходимо слияние, 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;' -"

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

[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>

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

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

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

Это сочетание нескольких ответов.

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

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

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

Я бы сделал

git diff commit1..commit2 файловый шаблон | git-apply --index && git фиксация

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

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

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

      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', а также заголовок 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

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

О чем git reset --soft branch ?Я удивлен, что никто до сих пор не упомянул об этом.

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

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

git remote add foo git@example.com/foo.git
git fetch foo
git read-tree --prefix=my-folder/ -u foo/master:trunk/their-folder

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

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

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

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

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

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

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

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

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

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

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

Если вам нужно объединить только определенный каталог, оставив все остальное нетронутым и при этом сохранив историю, вы, возможно, могли бы попробовать это...создайте новый 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.
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top