web-dev-qa-db-ja.com

Gitの別のブランチから選択的にマージまたは変更を選択する方法

私は2つの並行した(しかし現在実験的な)開発ブランチを持つ新しいプロジェクトにgitを使っています。

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

exp1exp2は、2つの非常に異なるアーキテクチャ上のアプローチを表します。私がさらに進んでいくまでは、どちらがうまくいくのかを知る方法がありません。私はあるブランチで進歩するにつれて、他のブランチで役立つような編集があり、それらをマージしたいと思うことがあります。

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

私が検討したアプローチ:

  1. git merge --no-commitの後に、私がブランチ間で共通にしたくないような多数の編集の手動アンステージングが続きます。

  2. 共通ファイルを一時ディレクトリに手動でコピーし、続いて他のブランチに移動するためのgit checkoutを実行してから、一時ディレクトリから作業ツリーに手動でコピーします。

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

これらの3つのアプローチはすべて退屈でエラーが発生しやすいようです。もっと良いアプローチがあるといいのですが。 git-mergeをより選択的にするフィルタパスパラメータに似たもの。

1300
David Joyner

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

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

ここにはもう一つ別のNiceメソッドがあります 個々のファイルに異なる変更を分割したい場合は、git add --patchまたは場合によってはgit add --interactiveを使うことができます(そのページで検索してください)。 "スプリット")。

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

420

私は上であなたによって言及されたのと全く同じ問題を抱えていました。しかし、私は答えを説明する際に this をより明確に見つけました。

概要:

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

    $ 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"
    
888
Susheel Javadi

あるブランチから別のブランチにファイルを選択的にマージするには、次のコマンドを実行します。

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

ここでbranchXは、マージしたいブランチから現在のブランチへの分岐です。

--no-commitオプションはGitによってマージされたファイルを実際にコミットせずにステージングします。これにより、マージしたファイルを変更して自分でコミットすることができます。 

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

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

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

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

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

現在のブランチのバージョンを選択するには、次のコマンドを実行してください。

git checkout HEAD file1

これは現在のブランチのfile1のバージョンを取得し、Gitによって自動化されたfile1を上書きします。 

3)あなたがbranchXのバージョンが欲しい(そして本当のマージではない)場合。

実行します。

git checkout branchX file1

これはbranchX内のfile1のバージョンを検索し、Gitによって自動マージされたfile1を上書きします。

4)最後のケースは、file1で特定のマージのみを選択したい場合です。

この場合、修正したfile1を直接編集し、file1のバージョンを希望するものに更新してからコミットすることができます。

Gitがファイルを自動的にマージできない場合は、ファイルを " unmerged "として報告し、手動で競合を解決する必要がある場所にコピーを作成します。



例を使ってさらに説明するために、branchXを現在のブランチにマージしたいとしましょう。

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

次にgit statusコマンドを実行して、変更されたファイルの状況を表示します。

例えば:

git status

# On branch master
# Changes to be committed:
#
#       modified:   file1
#       modified:   file2
#       modified:   file3
# Unmerged paths:
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#
#       both modified:      file4
#

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

これが意味するのは、これら3つのファイルすべてに対するmasterbranchXの変更が、矛盾なくまとめられているということです。

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


最後に、git commitを忘れないでください。

276
alvinabad

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

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

これが代替案です:

ブランチの '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
91
nosatalian

別の方法があります:

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

これらの答えのいくつかはかなり良いものですが、実際にはOPの元の制約である特定のブランチから特定のファイルを選択することに答えた人はいないように感じます。このソリューションはそれを行いますが、多くのファイルがある場合は退屈かもしれません。

masterexp1、およびexp2のブランチがあるとします。各実験ブランチから1つのファイルをマスターにマージします。私はこのようなことをします:

git checkout master
git checkout exp1 path/to/file_a
git checkout exp2 path/to/file_b

# save these files as a stash
git stash
# merge stash with master
git merge stash

これにより、必要なファイルごとにファイル内差分が得られます。これ以上何もない。それ以下。バージョン間で根本的に異なるファイル変更があると便利です。私の場合は、アプリをRails 2からRails 3に変更します。

EDIT:これはファイルをマージしますが、スマートマージを行います。ファイル内の差分情報を取得するためにこのメソッドを使用する方法を見つけることができませんでした(おそらくそれは極端な違いのためかもしれません。-s recursive -X ignore-all-spaceオプションを使用しない限り、空白のような厄介な小さなものがマージされます)

50
Eric Hu

1800情報の答えは完全に正しいです。しかし、gitの新参者として、 "use 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. チェリーは、ハッシュを使用してコミットを1つずつ選択します。

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

ハットチップス: http://www.sourcemage.org/Git_Guide

45
Cory

これがmasterブランチのMyclass.JavaファイルをMyclass.Javaブランチのfeature1に置き換える方法です。 masterMyclass.Javaが存在しなくても機能します。

git checkout master
git checkout feature1 Myclass.Java

これは上書きされ(マージではなく)、マスターブランチでのローカルの変更は無視されます。

39
maestr0

2つのブランチからの特定のファイルを実際に merge 特定のファイルに置き換えるのではなく、特定のファイルを別のブランチからのファイルに置き換えるのではありません。

ステップ1:ブランチを比較する

git diff branch_b > my_patch_file.patch

現在のブランチとbranch_bの間の違いのパッチファイルを作成します

ステップ2:パターンに一致するファイルにパッチを適用する

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

オプションに関する便利なメモ

Includeパターンでは*をワイルドカードとして使用できます。 

スラッシュはエスケープする必要はありません。

また、代わりに--excludeを使用してパターンに一致するファイルを除くすべてのものに適用するか、または-Rでパッチを元に戻すことができます。 

-p1オプションは* unix patchコマンドと、パッチファイルの内容に各ファイル名の先頭にa/またはb/(またはパッチファイルの生成方法に応じてそれ以上)を追加したものです。パッチが適用される必要があるファイルへのパスに実際のファイルを見つけ出してください。

その他のオプションについてはgit-applyのmanページをチェックしてください。 

ステップ3:ステップ3はありません

明らかにあなたはあなたの変更をコミットしたいと思うでしょう、しかしあなたがあなたがあなたのコミットをする前にしたいと思う他のいくつかの関連する微調整がないと言うのは誰です。 

25
masukomi

たとえ「単純な」マージが、あなたが望まないもっと多くの変更をもたらしていたとしても、他のブランチからのほんの2、3のファイルを最小限の手間で追跡することができるのです。

最初に、あなたがコミットしようとしているのは作業ディレクトリ内のファイルに対して何もしないで、コミットしようとしていることがマージであることを事前に宣言するという珍しいステップを踏みます。

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

。 。 。 "branchname"は、あなたがマージしていると主張するものなら何でもです。あなたがすぐにコミットしようとした場合、それは変更を加えることはありませんが、それはまだ他のブランチからの祖先を示すでしょう。もっとブランチ/タグ/ etcを追加することができます。必要ならコマンドラインにも。ただし、現時点では、コミットする変更はありませんので、次に他のリビジョンからファイルを入手してください。

git checkout branchname1 -- file1 file2 etc

他の複数のブランチからマージしている場合は、必要に応じて繰り返します。

git checkout branchname2 -- file3 file4 etc

もう一方のブランチからのファイルは、履歴付きでコミットされる準備ができているインデックスにあります。

git commit

そのコミットメッセージで説明することがたくさんあります。

それがはっきりしていない場合には、注意してください、これはやるべきことがめちゃくちゃになることです。それは「ブランチ」が何のためにあるのかという精神ではありません、そしてここでチェリーピックはあなたがしていることをするためのもっと誠実な方法です。前回持っていなかった同じブランチ上の他のファイルに対して別の「マージ」を実行したい場合は、「既に最新」のメッセージが表示されて停止します。 「from」ブランチには複数の異なるブランチがあるはずです。

21
jejese

この記事 は最も単純な答えを含んでいます。ただしてください:

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

例:

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

詳細については投稿を参照してください。

14
Stunner

私は少し遅れていることを知っていますが、これは選択的ファイルをマージするための私のワークフローです。

#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
14
Felix

最も簡単な方法は、リポジトリをマージしたいブランチに設定してから実行することです。

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

走れば 

git status

ファイルがすでにステージングされているのがわかります。

それから走りなさい 

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

簡単です。 

13
dsieczko

Gitがまだそのような便利なツールを持っていないのは不思議です。現在のバージョンブランチからの ほんの一部の バグ修正によって、(まだ多くのソフトウェアユーザがいる)古いバージョンブランチを更新するとき、私はそれを頻繁に使用します。この場合、トランク内のファイルから ほんの数 - 行のコードをすばやく取得する必要があります(他の多くの変更を無視します(古いバージョンに入ることは想定されていません)...そしてもちろん 対話型の3方向 mergeがこの場合必要です。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;' -"

それはあなたが比較を超えて使用することを意味します。必要ならばあなたの選んだソフトウェアに変えるだけです。対話型選択マージを必要としない場合は、3方向自動マージに変更することもできます。 

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

これにより、他のブランチにあるすべてのファイルの真の選択的な tree-way mergeの機会が得られます。

11
JimStar

私は上であなたによって言及されたのと全く同じ問題を抱えていました。しかし、私は このgitブログ /答えを説明することでより明確に - を見つけた。

上記のリンクからのコマンド:

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

それはあなたが探していたものではありませんが、私にとっては役に立ちました。

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

それはいくつかの答えの組み合わせです。

7
Felipe

私は上記の 'git-interactive-merge'の回答が好きですが、もっと簡単なものがあります。対話式および以下のリベースの組み合わせを使用して、gitにこれをさせてください。

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

そのため、 'feature'ブランチ(ブランチポイント 'A')のC1とC2が必要ですが、現時点ではそれ以外は必要ありません。

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

これは、上記のように、対話型エディタに移動し、そこでC1とC2の 'pick'行を選択します(上記のとおり)。保存して終了すると、リベースが続行され、master + C1 + C2のブランチ[temp]とHEADが表示されます。

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

それから、masterをHEADに更新してtempブランチを削除すればいいのです。

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

私はやります

git diff commit1..commit2ファイルパターン| git-apply --index && git commit

これにより、ブランチからのファイルパターンのコミット範囲を制限できます。

から盗まれた: http://www.gelato.unsw.edu.au/archives/git/0701/37964.html

6
lumpidu

私はこの質問が古く、他にもたくさんの答えがあることを知っていますが、私は自分自身でディレクトリを部分的にマージするための '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
5
Andy

git reset --soft branchはどうですか?まだ誰も言及していないことに驚きました。

私にとっては、他のブランチから選択的に変更を選択するのが最も簡単な方法です。なぜなら、このコマンドは私の作業ツリーにすべてのdiffの変更を入れ、必要なものを簡単に選択または元に戻すことができるからです。このようにして、私はコミットされたファイルを完全に制御することができます。

3
GarryOne

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にマージするにはどうすればよいですか。

2
kenorb

ファイルによる選択的なマージ/コミットのための簡単なアプローチ:

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
Dave C

2つのブランチの現在のコミットの間にいくつかのファイルだけが変更された場合は、異なるファイルを調べて手動で変更をマージします。

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

1
raratiru

特定のディレクトリをマージして他のものをそのまま残して履歴を保存するだけでよい場合は、おそらくこれを試すことができます。実験する前にmasterから新しいtarget-branchを作成します。

以下の手順では、target-branchsource-branchの2つのブランチがあり、マージしたいディレクトリdir-to-mergesource-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.
0
code4kix

変更されたファイルがそれほど多くない場合は、追加のコミットは不要です。

1.一時的にブランチを複製する
$ git checkout -b temp_branch

2.最後に必要なコミットにリセット
$ git reset --hard HEAD~n、ここでnは戻る必要があるコミットの数です。

3.元のブランチから各ファイルをチェックアウト
$ git checkout Origin/original_branch filename.ext

必要に応じて、今すぐコミットして強制的にプッシュ(リモートを上書き)することができます。

0
JBaczuk