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


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

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

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

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

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

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

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

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

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


1133


источник


Ответы:


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

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

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

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


375



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

Резюме:

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

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

791



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

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чтобы быть объединены автоматически, ничего не делайте.

Гит уже слил его в этот момент.


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


Наконец, не забывайте git commit,


231



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

Здесь нет --interactiveаргумент, вы можете перейти к объединению git.

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

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

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

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

#! /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

82



There is another way do go:

git checkout -p

It is a mix between git checkout and git add -p and might quite be exactly what you are looking for:

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

70



While some of these answers are pretty good, I feel like none actually answered OP's original constraint: selecting particular files from particular branches. This solution does that, but may be tedious if there are many files.

Lets say you have the master, exp1, and exp2 branches. You want to merge one file from each of the experimental branches into master. I would do something like this:

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

This will give you in-file diffs for each of the files you want. Nothing more. Nothing less. It's useful you have radically different file changes between versions--in my case, changing an app from Rails 2 to Rails 3.

EDIT: this will merge files, but does a smart merge. I wasn't able to figure out how to use this method to get in-file diff information (maybe it still will for extreme differences. Annoying small things like whitespace get merged back in unless you use the -s recursive -X ignore-all-space option)


47



1800 INFORMATION's answer is completely correct. As a git noob, though, "use git cherry-pick" wasn't enough for me to figure this out without a bit more digging on the internet so I thought I'd post a more detailed guide in case anyone else is in a similar boat.

My use case was wanting to selectively pull changes from someone else's github branch into my own. If you already have a local branch with the changes you only need to do steps 2 and 5-7.

  1. Create (if not created) a local branch with the changes you want to bring in.

    $ git branch mybranch <base branch>

  2. Switch into it.

    $ git checkout mybranch

  3. Pull down the changes you want from the other person's account. If you haven't already you'll want to add them as a remote.

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

  4. Pull down everything from their branch.

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

  5. View the commit logs to see which changes you want:

    $ git log

  6. Switch back to the branch you want to pull the changes into.

    $ git checkout originalbranch

  7. Cherry pick your commits, one by one, with the hashes.

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

Hat tip: http://www.sourcemage.org/Git_Guide


40



Here is how you can replace Myclass.java file in master branch with Myclass.java in feature1 branch. It will work even if Myclass.java doesn't exist on master.

git checkout master
git checkout feature1 Myclass.java

Note this will overwrite - not merge - and ignore local changes in the master branch rather.


34



The simple way, to actually merge specific files from two branches, not just replace specific files with ones from another branch.

Step one: Diff the branches

git diff branch_b > my_patch_file.patch

Creates a patch file of the difference between the current branch and branch_b

Step two: Apply the patch on files matching a pattern

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

useful notes on the options

You can use * as a wildcard in the include pattern.

Slashes don't need to be escaped.

Also, you could use --exclude instead and apply it to everything except the files matching the pattern, or reverse the patch with -R

The -p1 option is a holdover from the *unix patch command and the fact that the patch file's contents prepend each file name with a/ or b/ ( or more depending on how the patch file was generated) which you need to strip so that it can figure out the real file to the path to the file the patch needs to be applied to.

Check out the man page for git-apply for more options.

Step three: there is no step three

Obviously you'd want to commit your changes, but who's to say you don't have some other related tweaks you want to do before making your commit.


21



Here's how you can get history to follow just a couple files from another branch with a minimum of fuss, even if a more "simple" merge would have brought over a lot more changes that you don't want.

First, you'll take the unusual step of declaring in advance that what you're about to commit is a merge, without git doing anything at all to the files in your working directory:

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

. . . where "branchname" is whatever you claim to be merging from. If you were to commit right away, it would make no changes but it would still show ancestry from the other branch. You can add more branches/tags/etc. to the command line if you need to, as well. At this point though, there are no changes to commit, so get the files from the other revisions, next.

git checkout branchname1 -- file1 file2 etc

If you were merging from more than one other branch, repeat as needed.

git checkout branchname2 -- file3 file4 etc

Now the files from the other branch are in the index, ready to be committed, with history.

git commit

and you'll have a lot of explaining to do in that commit message.

Please note though, in case it wasn't clear, that this is messed up thing to do. It is not in the spirit of what a "branch" is for, and cherry-pick is a more honest way to do what you'd be doing, here. If you wanted to do another "merge" for other files on the same branch that you didn't bring over last time, it will stop you with an "already up to date" message. It's a symptom of not branching when we should have, in the "from" branch should be more than one different branch.


19



I know I am a little late but this is my workflow for merging selective files.

#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

13