How Can I Selectively Merge or Pick Changes from Another Branch in Git?
I'm Using Git on a New Project That Has Two Parallel -- but Currently Experimental -- Development Branches: Master: Import of Existing Codebase Plus a Few...
I'm using Git on a new project that has two parallel -- but currently experimental -- development branches:
master: import of existing codebase plus a few modifications that I'm generally sure ofexp1: experimental branch #1exp2: experimental branch #2
exp1 and exp2 represent two very different architectural approaches. Until I get further along I have no way of knowing which one (if either) will work. As I make progress in one branch I sometimes have edits that would be useful in the other branch and would like to merge just those.
What is the best way to merge selective changes from one development branch to another while leaving behind everything else?
Approaches I've considered:
git merge --no-commitfollowed by manual unstaging of a large number of edits that I don't want to make common between the branches.Manual copying of common files into a temporary directory followed by
git checkoutto move to the other branch and then more manual copying out of the temporary directory into the working tree.A variation on the above. Abandon the
expbranches for now and use two additional local repositories for experimentation. This makes the manual copying of files much more straightforward.
All three of these approaches seem tedious and error-prone. I'm hoping there is a better approach; something akin to a filter path parameter that would make git-merge more selective.
28 Answers
I had the exact same problem as mentioned by you above. But I found this clearer in explaining the answer.
Summary:
Check out the path(s) from the branch you want to merge,
$ git checkout source_branch -- <paths>... Hint: It also works without `--` like seen in the linked post.or to selectively merge hunks
$ git checkout -p source_branch -- <paths>...
Alternatively, use reset and then add with the option -p,
$ git reset <paths>...
$ git add -p <paths>...
Finally commit
$ git commit -m "'Merge' these changes"
You use the cherry-pick command to get individual commits from one branch.
If the change(s) you want are not in individual commits, then use the method shown here to split the commit into individual commits. Roughly speaking, you use git rebase -i to get the original commit to edit, then git reset HEAD^ to selectively revert changes, then git commit to commit that bit as a new commit in the history.
There is another nice method here in Red Hat Magazine, where they use git add --patch or possibly git add --interactive which allows you to add just parts of a hunk, if you want to split different changes to an individual file (search in that page for "split").
Having split the changes, you can now cherry-pick just the ones you want.
To selectively merge files from one branch into another branch, run
git merge --no-ff --no-commit branchX
where branchX is the branch you want to merge from into the current branch.
The --no-commit option will stage the files that have been merged by Git without actually committing them. This will give you the opportunity to modify the merged files however you want to and then commit them yourself.
Depending on how you want to merge files, there are four cases:
1) You want a true merge.
In this case, you accept the merged files the way Git merged them automatically and then commit them.
2) There are some files you don't want to merge.
For example, you want to retain the version in the current branch and ignore the version in the branch you are merging from.
To select the version in the current branch, run:
git checkout HEAD file1
This will retrieve the version of file1 in the current branch and overwrite the file1 automerged by Git.
3) If you want the version in branchX (and not a true merge).
Run:
git checkout branchX file1
This will retrieve the version of file1 in branchX and overwrite file1 auto-merged by Git.
4) The last case is if you want to select only specific merges in file1.
In this case, you can edit the modified file1 directly, update it to whatever you'd want the version of file1 to become, and then commit.
If Git cannot merge a file automatically, it will report the file as "unmerged" and produce a copy where you will need to resolve the conflicts manually.
To explain further with an example, let's say you want to merge branchX into the current branch:
git merge --no-ff --no-commit branchX
You then run the git status command to view the status of modified files.
For example:
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
#
Where file1, file2, and file3 are the files git have successfully auto-merged.
What this means is that changes in the master and branchX for all those three files have been combined together without any conflicts.
You can inspect how the merge was done by running the git diff --cached;
git diff --cached file1
git diff --cached file2
git diff --cached file3
If you find some merge undesirable then you can
- edit the file directly
- save
git commit
If you don't want to merge file1 and want to retain the version in the current branch
Run
git checkout HEAD file1
If you don't want to merge file2 and only want the version in branchX
Run
git checkout branchX file2
If you want file3 to be merged automatically, don't do anything.
Git has already merged it at this point.
file4 above is a failed merge by Git. This means there are changes in both branches that occur on the same line. This is where you will need to resolve the conflicts manually. You can discard the merged done by editing the file directly or running the checkout command for the version in the branch you want file4 to become.
Finally, don't forget to git commit.
I don't like the above approaches. Using cherry-pick is great for picking a single change, but it is a pain if you want to bring in all the changes except for some bad ones. Here is my approach.
There is no --interactive argument you can pass to git merge.
Here is the alternative:
You have some changes in branch 'feature' and you want to bring some but not all of them over to 'master' in a not sloppy way (i.e. you don't want to cherry pick and commit each one)
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
So just wrap that in a shell script, change master into $to and change feature into $from and you are good to go:
#!/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
There is another way to 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.
While some of these answers are pretty good, I feel like none actually answered the OP's original constraint: selecting particular files from particular branches. This solution does that, but it may be tedious if there are many files.
Let’s 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 application from Ruby on Rails 2 to Ruby on Rails 3.
This will merge files, but it 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 someone new to Git, 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.
Create (if not created) a local branch with the changes you want to bring in.
$ git branch mybranch <base branch>Switch into it.
$ git checkout mybranchPull 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>Pull down everything from their branch.
$ git pull repos-w-changes branch-i-wantView the commit logs to see which changes you want:
$ git logSwitch back to the branch you want to pull the changes into.
$ git checkout originalbranchCherry pick your commits, one by one, with the hashes.
$ git cherry-pick -x hash-of-commit
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