If I want to merge into a Git branch the changes made only to some of the files changed in a particular commit which includes changes to multiple files, how can this be achieved?
Suppose the Git commit called stuff
has changes to files A
, B
, C
, and D
but I want to merge only stuff
's changes to files A
and B
. It sounds like a job for git cherry-pick
but cherry-pick
only knows how to merge entire commits, not a subset of the files.
This question is related to
git
github
cherry-pick
The situation:
You are on your branch, let's say master
and you have your commit on any other branch. You have to pick only one file from that particular commit.
The approach:
Step 1: Checkout on the required branch.
git checkout master
Step 2: Make sure you have copied the required commit hash.
git checkout commit_hash path\to\file
Step 3: You now have the changes of the required file on your desired branch. You just need to add and commit them.
git add path\to\file
git commit -m "Your commit message"
The other methods didn't work for me since the commit had a lot of changes and conflicts to a lot of other files. What I came up with was simply
git show SHA -- file1.txt file2.txt | git apply -
It doesn't actually add
the files or do a commit for you so you may need to follow it up with
git add file1.txt file2.txt
git commit -c SHA
Or if you want to skip the add you can use the --cached
argument to git apply
git show SHA -- file1.txt file2.txt | git apply --cached -
You can also do the same thing for entire directories
git show SHA -- dir1 dir2 | git apply -
Perhaps the advantage of this method over Jefromi's answer is that you don't have to remember which behaviour of git reset is the right one :)
# Create a branch to throw away, on which we'll do the cherry-pick:
git checkout -b to-discard
# Do the cherry-pick:
git cherry-pick stuff
# Switch back to the branch you were previously on:
git checkout -
# Update the working tree and the index with the versions of A and B
# from the to-discard branch:
git checkout to-discard -- A B
# Commit those changes:
git commit -m "Cherry-picked changes to A and B from [stuff]"
# Delete the temporary branch:
git branch -D to-discard
I usually use the -p
flag with a git checkout from the other branch which I find easier and more granular than most other methods I have come across.
In principle:
git checkout <other_branch_name> <files/to/grab in/list/separated/by/spaces> -p
example:
git checkout mybranch config/important.yml app/models/important.rb -p
You then get a dialog asking you which changes you want in "blobs" this pretty much works out to every chunk of continuous code change which you can then signal y
(Yes) n
(No) etc for each chunk of code.
The -p
or patch
option works for a variety of commands in git including git stash save -p
which allows you to choose what you want to stash from your current work
I sometimes use this technique when I have done a lot of work and would like to separate it out and commit in more topic based commits using git add -p
and choosing what I want for each commit :)
For the sake of completness, what works best for me is:
git show YOURHASH --no-color -- file1.txt file2.txt dir3 dir4 | git apply -3 --index -
It does exactly what OP wants. It does conflict resolution when needed, similarly how merge
does it. It does add
but not commit
your new changes, see with status
.
I found another way which prevents any conflicting merge on cherry-picking which IMO is kind of easy to remember and understand. Since you are actually not cherry-picking a commit, but part of it, you need to split it first and then create a commit which will suit your needs and cherry-pick it.
First create a branch from the commit you want to split and checkout it:
$ git checkout COMMIT-TO-SPLIT-SHA -b temp
Then revert previous commit:
$ git reset HEAD~1
Then add the files/changes you want to cherry-pick:
$ git add FILE
and commit it:
$ git commit -m "pick me"
note the commit hash, let's call it PICK-SHA and go back to your main branch, master for example forcing the checkout:
$ git checkout -f master
and cherry-pick the commit:
$ git cherry-pick PICK-SHA
now you can delete the temp branch:
$ git branch -d temp -f
Use git merge --squash branch_name
this will get all changes from the other branch and will prepare a commit for you.
Now remove all unneeded changes and leave the one you want. And git will not know that there was a merge.
Cherry pick is to pick changes from a specific "commit". The simplest solution is to pick all changes of certain files is to use
git checkout source_branch <paths>...
In example:
$ git branch
* master
twitter_integration
$ git checkout twitter_integration app/models/avatar.rb db/migrate/20090223104419_create_avatars.rb test/unit/models/avatar_test.rb test/functional/models/avatar_test.rb
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: app/models/avatar.rb
# new file: db/migrate/20090223104419_create_avatars.rb
# new file: test/functional/models/avatar_test.rb
# new file: test/unit/models/avatar_test.rb
#
$ git commit -m "'Merge' avatar code from 'twitter_integration' branch"
[master]: created 4d3e37b: "'Merge' avatar code from 'twitter_integration' branch"
4 files changed, 72 insertions(+), 0 deletions(-)
create mode 100644 app/models/avatar.rb
create mode 100644 db/migrate/20090223104419_create_avatars.rb
create mode 100644 test/functional/models/avatar_test.rb
create mode 100644 test/unit/models/avatar_test.rb
Sources and full explanation http://jasonrudolph.com/blog/2009/02/25/git-tip-how-to-merge-specific-files-from-another-branch/
UPDATE:
With this method, git will not MERGE the file, it will just override any other change done on the destination branch. You will need to merge the changes manually:
$ git diff HEAD filename
You can use:
git diff <commit>^ <commit> -- <path> | git apply
The notation <commit>^
specifies the (first) parent of <commit>
. Hence, this diff command picks the changes made to <path>
in the commit <commit>
.
Note that this won't commit anything yet (as git cherry-pick
does). So if you want that, you'll have to do:
git add <path>
git commit
I would just cherry-pick everything, then do this:
git reset --soft HEAD^
Then I would revert the changes I don't want, then make a new commit.
Merge a branch into new one (squash) and remove the files not needed:
git checkout master
git checkout -b <branch>
git merge --squash <source-branch-with-many-commits>
git reset HEAD <not-needed-file-1>
git checkout -- <not-needed-file-1>
git reset HEAD <not-needed-file-2>
git checkout -- <not-needed-file-2>
git commit
Source: Stackoverflow.com