質問

2つの並列開発ブランチ(現在は実験段階)がある新しいプロジェクトでgitを使用しています:

  • master:既存のコードベースと、私が一般的に確信しているいくつかのmodのインポート
  • exp1:実験ブランチ#1
  • exp2:実験ブランチ#2

git merge --no-commitおよびgit checkoutは、2つの非常に異なるアーキテクチャアプローチを表します。さらに進むまで、どちらが機能するかを知る方法がありません。一方のブランチで作業を進めていると、もう一方のブランチで役立つ編集があり、それらだけをマージしたいことがあります。

ある開発ブランチから別の開発ブランチに選択的な変更をマージし、他のすべてを残す最良の方法は何ですか?

考えたアプローチ:

  1. expに続いて、ブランチ間で共有したくない多数の編集を手動でステージング解除します。

  2. 一般的なファイルを一時ディレクトリに手動でコピーしてからgit-mergeを使用して他のブランチに移動し、さらに一時ディレクトリから作業ツリーに手動でコピーします。

  3. 上記のバリエーション。今のところ<=>ブランチを放棄し、実験のために2つの追加のローカルリポジトリを使用します。これにより、ファイルの手動コピーがはるかに簡単になります。

これらの3つのアプローチはすべて、面倒でエラーが発生しやすいようです。より良いアプローチがあることを望んでいます。 <=>をより選択的にするフィルターパスパラメーターに似たもの。

役に立ちましたか?

解決

cherry-pick コマンドを使用して、個々のコミットを取得します1つのブランチ。

必要な変更が個々のコミットにない場合は、ここに示す方法を使用してを分割します個々のコミットにコミットします。大まかに言うと、git rebase -iを使用して元のコミットを編集し、次にgit reset HEAD^を使用して選択的に変更を元に戻し、git commitを使用してそのビットを履歴の新しいコミットとしてコミットします。

Red Hat Magazineには別の便利なメソッドがありますgit add --patchまたは場合によってはgit add --interactiveを使用します。個々のファイルに変更します(そのページで<!> quot; split <!> quot;を検索します)。

変更を分割して、必要なものだけを選択することができます。

他のヒント

上記で述べた問題とまったく同じ問題がありました。しかし、この回答の説明がより明確になりました。

概要:

  • マージするブランチからパスをチェックアウトします

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

    ヒント:リンクされた投稿で見られるように、--なしでも機能します。

  • またはハンクを選択的にマージする

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

    または、resetを使用してから、オプション-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によって実際にコミットせずにマージされたファイルをステージングします。これにより、マージしたファイルを必要に応じて変更し、自分でコミットする機会が与えられます。

ファイルのマージ方法に応じて、4つのケースがあります:

1)真のマージが必要です。

この場合、Gitが自動的にマージした方法でマージされたファイルを受け入れ、コミットします。

2)マージしたくないファイルがいくつかあります。

たとえば、現在のブランチのバージョンを保持し、マージ元のブランチのバージョンを無視したい場合。

現在のブランチのバージョンを選択するには、次を実行します:

git checkout HEAD file1

これにより、現在のブランチのfile1のバージョンが取得され、Gitによって自動マージされたgit statusが上書きされます。

3)branchXのバージョンが必要な場合(真のマージではない場合)。

実行:

git checkout branchX file1

これにより、file2file3のバージョンが取得され、Gitによって自動マージされたmasterが上書きされます。

4)最後のケースは、git diff --cachedで特定のマージのみを選択する場合です。

この場合、変更したgit commitを直接編集し、file4のバージョンにしたいものに更新してからコミットできます。

Gitがファイルを自動的にマージできない場合、ファイルは<!> quot; unmerged <!> quot;としてレポートされます。競合を手動で解決する必要がある場所にコピーを作成します。



例を使用してさらに説明するために、<=>を現在のブランチにマージするとします。

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
#

次に、<=>コマンドを実行して、変更されたファイルのステータスを表示します。

例:

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

git <=>、<=>、および<=>は、gitが正常に自動マージしたファイルです。

これが意味するのは、これら3つのファイルすべての<=>および<=>の変更が競合することなく結合されていることです。

<=>;を実行すると、マージがどのように行われたかを調べることができます;

git checkout branchX file2

マージが望ましくないと思われる場合は、

  1. ファイルを直接編集
  2. 保存
  3. <=>

マージしない場合<=>、現在のブランチにバージョンを保持する場合

実行

<*>

マージしたくない<=>で、バージョンのみを<=>

にしたい場合

実行

<*>

<=>を自動的にマージする場合は、何もしないでください。

Gitはこの時点で既にマージしています。


上記の

<=>は、Gitによる失敗したマージです。これは、同じ行で発生する両方のブランチに変更があることを意味します。これは、競合を手動で解決する必要がある場所です。ファイルを直接編集するか、<=>にしたいブランチのバージョンに対してcheckoutコマンドを実行することにより、マージ済みを破棄できます。


最後に、<=>を忘れないでください。

上記のアプローチは好きではありません。チェリーピックを使用することは、単一の変更を選択するのに最適ですが、いくつかの悪い変更を除くすべての変更を取り入れたい場合は苦痛です。これが私のアプローチです。

git mergeに渡すことができる--interactive引数はありません。

代替手段は次のとおりです。

ブランチの「機能」にいくつかの変更があり、それらのすべてではなく、いくつかをずさんな方法で「マスター」に持ち込みたい(つまり、それぞれを選択してコミットしたくない)

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

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)

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

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.

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.

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.

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

I found this post to contain the simplest answer. Merely do:

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

Example:

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

See the post for more info.

Easiest way is to set your repo to the branch you want to merge with then run,

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

If you run

git status

you will see the file already staged...

Then run

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

Simple.

It's strange that git still does not have such a convenient tool "out of the box". I use it heavily when update some old version branch (which still has a lot of software users) by just some bugfixes from the current version branch. In this case it is often needed to quickly get just some lines of code from the file in trunk, ignoring a lot of other changes (that are not supposed to go into the old version)... And of course interactive three-way merge is needed in this case, git checkout --patch <branch> <file path> is not usable for this selective merge purpose.

You can do it easily:

Just add this line to [alias] section in your global .gitconfig or local .git/config file:

[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;' -"

It implies you use Beyond Compare. Just change to software of your choice if needed. Or you can change it to three-way auto-merge if you don't need the interactive selective merging:

[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;' -"

Then use like this:

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

This will give you the true selective tree-way merge opportunity of just any file in other branch.

It is not exactly what you were looking for, but it was useful to me:

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

It is a mix of some answers.

I had the exact same problem as mentioned by you above. But I found this git blog clearer in explaining the answer.

Command from the above link:

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

I would do a

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

This way you can limit the range of commits for a filepattern from a branch.

Stolen from: http://www.gelato.unsw.edu.au/archives/git/0701/37964.html

I like the 'git-interactive-merge' answer, above, but there's one easier. Let git do this for you using a rebase combination of interactive and onto:

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

So the case is you want C1 and C2 from 'feature' branch (branch point 'A'), but none of the rest for now.

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

Which, as above, drops you in to the interactive editor where you select the 'pick' lines for C1 and C2 (as above). Save and quit, and then it will proceed with the rebase and give you branch 'temp' and also HEAD at master + C1 + C2:

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

Then you can just update master to HEAD and delete the temp branch and you're good to go:

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

I know this question is old and there are many other answers, but I wrote my own script called 'pmerge' to partially merge directories. It's a work in progress and I'm still learning both git and bash scripting.

This command uses git merge --no-commit and then unapplies changes that don't match the path provided.

Usage: git pmerge branch path
Example: git merge develop src/

I haven't tested it extensively. The working directory should be free of any uncommitted changes and untracked files.

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

What about git reset --soft branch ? I'm surprised that no one have mentioned it yet.

For me, it's the easiest way to selectively pick the changes from another branch, since, this command puts in my working tree, all the diff changes and I can easily pick or revert which one I need. In this way, I have full-control over the committed files.

You can use read-tree to read or merge given remote tree into the current index, for example:

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

To perform merge, use -m instead.

See also: How do I merge a sub directory in git?

A simple approach for selective merging/committing by file:

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

If you don't have too many files that have changed, this will leave you with no extra commits.

1. Duplicate branch temporarily
$ git checkout -b temp_branch

2. Reset to last wanted commit
$ git reset --hard HEAD~n, where n is the number of commits you need to go back

3. Checkout each file from original branch
$ git checkout origin/original_branch filename.ext

Now you can commit and force push (to overwrite remote), if needed.

When only a few files have changed between the current commits of the two branches, I manually merge the changes by going through the different files.

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

If you only need to merge a particular directory and leave everything else intact and yet preserve history, you could possibly try this... create a new target-branch off of the master before you experiment.

The steps below assume you have two branches target-branch and source-branch, and the directory dir-to-merge that you want to merge is in the source-branch. Also assume you have other directories like dir-to-retain in the target that you don't want to change and retain history. Also, assumes there are merge conflicts in the 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