857

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.

Jacek Laskowski
  • 72,696
  • 27
  • 242
  • 420
Tobias Kienzler
  • 25,759
  • 22
  • 127
  • 221

15 Answers15

977

I'd do it with cherry-pick -n (--no-commit) which lets you inspect (and modify) the result before committing:

git cherry-pick -n <commit>

# unstage modifications you don't want to keep, and remove the
# modifications from the work tree as well.
# this does work recursively!
git checkout HEAD <path>

# commit; the message will have been stored for you by cherry-pick
git commit

If the vast majority of modifications are things you don't want, instead of checking out individual paths (the middle step), you could reset everything back, then add in what you want:

# unstage everything
git reset HEAD

# stage the modifications you do want
git add <path>

# make the work tree match the index
# (do this from the top level of the repo)
git checkout .
Cascabel
  • 479,068
  • 72
  • 370
  • 318
  • 25
    In addition to `git checkout .` I would recommend also `git clean -f` to remove any new but unwanted files introduced by the cherry-picked commit. – rlat Jul 05 '15 at 17:05
  • 7
    Additional note for the latter method: I use `git add -p` which lets you decide interactively which changes you want to add to the index _per file_ – matthaeus Oct 13 '15 at 10:58
  • 7
    This is not so great in the case that the cherry-picked commit doesn't apply to the current working copy because it's so different, but that *one* file *would* apply cleanly. – lmat - Reinstate Monica Apr 22 '16 at 00:12
  • for sourcetree users it's possible to add [custom action to cherry pick without commit](https://jira.atlassian.com/browse/SRCTREE-1026?focusedCommentId=728086&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-728086) – Shahar Nov 22 '16 at 10:23
  • 12
    You can also unstage selectively with `git reset -p HEAD`. It's the equivalent of `add -p` but very few know that it exists. – Patrick Schlüter Mar 14 '17 at 09:09
  • @rlat good point, but if any of those files are in new directories, you'll need `git clean -rf` instead. – M_M May 22 '18 at 10:03
  • 2
    Very useful trick. I've put it in a gist in case someone needs it as a quick script https://gist.github.com/PiDayDev/68c39b305ab9d61ed8bb2a1195ee1afc – Damiano Oct 13 '18 at 16:07
  • 3
    This doesn't seem to preserve authorship (at all). In some cases that won't matter, in other cases it will. – AlanSE Sep 13 '19 at 17:42
  • 2
    To those of you git beginners getting here while trying to learn: you need to be on the *target branch* when you enter the `git cherry-pick` command, and `` is the short or long hash of the *source commit* - because I know that a few years ago I had many "where do I need to be to do this?" kind of problems with git. Also if you want to add a note into the commit message about this file having been cherry-picked, use `git cherry-pick -xn` instead of just `-n`. – sisisisi Jul 10 '20 at 04:35
  • Awesome! Thanks for this tip. All these years of using git and I never knew about the --no-commit option. – Dominique Jan 22 '21 at 18:15
  • Thanks, your answer actually made me learn something about Git :+1: – Tobias Mar 01 '21 at 17:42
  • When I do this, `git commit` opens an editor with an empty commit message because `git reset HEAD` deletes `.git/MERGE_MSG`. I ended up using `git commit -C ` in order to preserve the original commit message. – user1129682 May 18 '21 at 19:39
  • Instead of `git clean -f`, I use `git clean -fd`. The `d` means: `Remove untracked directories in addition to untracked files. If an untracked directory is managed by a different Git repository, it is not removed by default. Use -f option twice if you really want to remove such a directory.` – wayneseymour Aug 25 '21 at 10:53
  • I've successfully used a variant of the above that retains the original commit metadata and works well in case there are files to exclude that would lead to merge conflicts. `git cherry-pick # assuming this fails with merge conflicts` `git reset ` `git cherry-pick --continue` `git checkout -- ` ``` – dvo Jul 20 '22 at 10:52
  • -n did it for me. When picking a commit from another branch, I have noticed that a regular cherry-pick would pick files from unwanted preceding branch commits into the history, they would later appear as deletions when you modify such a file. – Maciej Krawczyk Oct 13 '22 at 09:29
  • Works but I had to use the `-m 1` option of `git cherry-pick` – Bruno Jun 02 '23 at 16:18
261

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 -
kubanczyk
  • 5,184
  • 1
  • 41
  • 52
Michael Anderson
  • 70,661
  • 7
  • 134
  • 187
  • 2
    Interesting method, thanks. But doesn't `show SHA -- file | apply` basically do the same as `checkout SHA -- file` as in [Mark Longair's answer](http://stackoverflow.com/a/5717140/321973)? – Tobias Kienzler Apr 22 '15 at 10:09
  • 11
    Nope, `checkout SHA -- file` will checkout exactly the version at SHA, while `show SHA -- file | apply` will apply only the changes in SHA (just like cherry-pick does). It matters if (a) there are more than one commit changing the given file in the source branch, or (b) there is a commit changing the file in your current target branch. – Michael Anderson Apr 22 '15 at 23:59
  • Simple, precise, and beautiful; this is the best answer. Exactly what I've been looking for :-) – cmaster - reinstate monica Jun 30 '15 at 09:36
  • 19
    Just found another great use for this: selective revert, for when you only want to revert one file (since `git revert` undoes the entire commit). In that case just use `git show -R SHA -- file1.txt file2.txt | git apply -` – Michael Anderson Oct 30 '15 at 02:22
  • I used git diff instead of git show: `git diff SHA -- file1.txt file2.txt | git apply -` – Roei Bahumi Dec 14 '15 at 09:14
  • 4
    @RoeiBahumi that has quite a different meaning. `git diff SHA -- file1.txt file2.txt | git apply -` means apply all the differences between the current version of the file and the version at SHA to the current version. In essence it is the same as `git checkout SHA -- file1.txt file2.txt`. See my earlier comment for why that is different to what the `git show` version. – Michael Anderson Dec 14 '15 at 23:35
  • 12
    In case you have to resolve conflicts, use `git apply -3 -` instead of just `git apply -`, then if a conflict occurs, you can use your standard conflict resolution technique, including using `git mergetool`. – qwertzguy Apr 04 '16 at 16:52
  • 2
    This answers the OP's question in the most direct way. Thanks! – lmat - Reinstate Monica Apr 22 '16 at 14:49
  • Really great answer :) Apart from adding `-3`, one caveat is git will complain if you try this with removal of binary files, for reasons I'm not quite clear on. However, that's very easy & quick to do manually. – Benji XVI Aug 19 '16 at 10:11
  • 2
    note that before committing you have to do a `git add` cherry-picked files. I use --cached to git apply to bypass that, make sure you have added your files to the index. – KenIchi May 16 '18 at 08:34
  • 1
    @KenIchi that's definitely worth noting - I've added it to the answer. Thanks! – Michael Anderson May 17 '18 at 04:14
  • 2
    I feel like this is the most clean approach to the problem. However, git config settings may interfere with this. I had to add the arguments `--src-prefix=a/`, `--dst-prefix=b/` and `--no-color` to `git show` for this to work. Otherwise, `git apply` could not interpret the input (which contained colour escape codes). Probably, this is just because I am using a somewhat weird git configuration. (I am using `diff.noprefix=true` and `color.ui=always`). – Alderath Oct 16 '18 at 12:11
  • @Alderath Those certainly will cause havok with doing this, and in a few other git based scripts I would expect. I'll add a bit to the answer outlining this, but I'd love to know _why_ you use those settings. – Michael Anderson Oct 17 '18 at 00:44
  • @MichaelAnderson The `diff.noprefix=true` setting is neat because I often find myself doing `git diff` and then double clicking the name of the file in order to copy it and open it in a text editor. If the name is preceded by a `a/` prefix, I need to remove that part, which is tedious. For the coloring part, I use `cat` as `core.pager` because most often, that seems sufficient for me. But if I ever want to pipe `git diff` to `more`, I want the colouring to be preserved. – Alderath Oct 17 '18 at 08:47
  • I used this answer just fine a week ago, now it says unrecognized input, can anyone help me ? – Chestera May 04 '22 at 10:14
  • 2
    @Chestera I'd look at the output of the part of the command before the `|` symbol and see if it makes sense as a diff. – Michael Anderson May 05 '22 at 02:22
170

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 :)

Tyrone Wilson
  • 4,328
  • 2
  • 31
  • 35
  • 5
    I regularly use `git-add -p`, but I didn't know `git-checkout` also has a `-p` flag - does that fix the merging problems [the non-`-p` answer](https://stackoverflow.com/a/20376394/3219739) has? – Tobias Kienzler Mar 02 '16 at 11:45
  • @TobiasKienzler, I guess it would if you don't choose lines that are going to conflict but there would still be code to untangle if your file has changed too much. – Tyrone Wilson Mar 02 '16 at 12:24
  • 1
    at least `-p` would allow for a manual edit for such a conflicting section, which `cherry-pick` would probably also yield anyway. I'll test this next time I need it, definitely an interesting approach – Tobias Kienzler Mar 02 '16 at 12:54
  • 4
    One of the two best answers that don't kill concurrent changes to the branches. – akostadinov Nov 17 '16 at 19:20
  • 1
    See this answer for how to select which hunks to apply: http://stackoverflow.com/a/10605465/4816250 The 's' option in particular was very helpful. – jvd10 Dec 09 '16 at 16:16
  • @jvd10, Thanks for that, very helpful indeed! – Tyrone Wilson Dec 10 '16 at 08:01
  • 1
    `git reset -p HEAD` also allows the `-p` which can be quit handy when you only want to remove some patches from the index. – Patrick Schlüter Mar 14 '17 at 09:33
  • 1
    What is interesting here is that you can use glob names for "files to grab" like in '*foo*' and that will be expanded in context of the *other* branch - that way you can easily point to a file that doesn't exist in the current branch/working tree (btw, it doesn't work without -p). – Grigory Entin Jan 19 '19 at 14:38
  • This is the way – aelgn Jun 08 '23 at 10:03
58

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"
techdreams
  • 5,371
  • 7
  • 42
  • 63
  • 2
    Awesome! Also worked for all changes in a directory with \path\to\directory\ for me – zaggi Apr 03 '20 at 22:16
  • 2
    Thank you so much! This is way simpler than cherry-pick – menecio Oct 21 '20 at 08:12
  • 3
    Overwrites any changes made to the target branch. – Tanveer Badar Jun 10 '21 at 08:52
  • 2
    The best solution so far!! – AouledIssa Oct 04 '21 at 08:47
  • best for the situation described in the answer. – Banee Ishaque K Mar 05 '23 at 19:47
  • This works if that commit is _exactly_ the file you need (that's what checkout gets you), but does not work if you have any changes (on `master` in your example) to that file (which is what using `cherry-pick` usually solves), as it'll completely replace the file instead of just pick the changes into it. – Jimbly Apr 21 '23 at 16:29
  • If you've just [committed to the wrong branch](https://stackoverflow.com/q/2941517/584940), this solution by @techdreams is simpler than [using a cherry-pick to pull it into the correct branch](https://stackoverflow.com/a/2941524/584940), at least for me. – Randall May 17 '23 at 17:41
53

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
Community
  • 1
  • 1
Mark Longair
  • 446,582
  • 72
  • 411
  • 327
  • 3
    thanks for your answer. Now that inspired me to think, why not skip the `cherry-pick` and directly use `git checkout stuff -- A B`? And with `git commit -C stuff` the commit message would remain the same as well – Tobias Kienzler Apr 19 '11 at 14:21
  • 12
    @Tobias: That would work only if the files modified on `stuff` have not been modified on your current branch or anywhere between the common ancestor of `HEAD` and `stuff` and the tip of `stuff`. If they have, then `cherry-pick` creates the correct result (essentially the result of a merge), while your method would throw away the changes in the current branch, and keep all of the changes from the common ancestor up to `stuff` - not just the ones in that single commit. – Cascabel Apr 19 '11 at 14:32
  • 2
    @Tobias Kienzler: I was assuming that your starting point was sufficiently different from the parent of `stuff` that the result of the cherry pick would leave `A` and `B` with different content from their content in the commit `stuff`. However, if it would just be the same, you're right - you could just do as you say. – Mark Longair Apr 19 '11 at 14:34
  • @Jeromi, @Mark: thanks for your feedback, in my case I'm treating branches with entirely disjunct files which led me to my suggestion. But indeed I'd had run into trouble with it sooner or later, so thank you for bringing this up – Tobias Kienzler Apr 19 '11 at 14:47
  • I think [my answer in this other thread](http://stackoverflow.com/a/32082356/2063546) may be what you're after. – Ian Sep 08 '15 at 14:34
  • `git checkout to-discard -- A B` will overwrite the current A and B, or merge with the current A and B? – Yves Dec 03 '21 at 07:23
48

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

cminatti
  • 4,596
  • 2
  • 19
  • 8
  • 8
    [I thought so too](http://stackoverflow.com/questions/5717026/how-to-git-cherry-pick-only-changes-to-certain-files/20376394?noredirect=1#comment6537524_5717140), but this fails horribly if the files have changed on _both_ branches since it discards the changes of your current branch – Tobias Kienzler Dec 04 '13 at 13:24
  • You are right, it is a must to clarify that this way git doesn't MERGE, it just override. You can then do "git diff HEAD filename" to see what changed and do the merge manually. – cminatti Dec 18 '13 at 14:12
  • This worked well for me. A colleague had made a comment on a pull request, that some changes should be split into a separate request in order to separate the issues clearly. Your method worked well for this case. – 4thex Feb 02 '22 at 07:58
  • You can also use it to grab the specific file(s) from a particular commit `git checkout e0032947bd37f44044962ae2f7229d339944f83c example.txt` – Rishi Kulshreshtha Jun 28 '22 at 08:38
30

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.

kubanczyk
  • 5,184
  • 1
  • 41
  • 52
19

Sometimes it could be easier to use checkout to bring specific files from a commit. In my opinion it gives you more control, and it is not necessary to be checking and unstaging after a cherrypick.

I would do it like this:

git checkout <branch|hash> -- path/to/file1 path/to/filen

Then unstage the necessary changes to adapt the code and test it before do commit. If everything works as expected, then commit.

eliastg
  • 449
  • 1
  • 4
  • 14
18

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.

funroll
  • 35,925
  • 7
  • 54
  • 59
12

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.

Indomitable
  • 808
  • 9
  • 9
  • Thanks, I didn't know about that merge option. It's a viable alternative if you want to cherry-pick most of an entire branch (but in contrast to cherry-pick it won't work if there is no common ancestor) – Tobias Kienzler Oct 31 '17 at 07:32
  • "git will not know that there was a merge"? I thought git knew everything? :\ – jtlz2 Jul 19 '23 at 08:02
7

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
Alexey
  • 71
  • 1
  • 1
5

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
Garogolun
  • 323
  • 2
  • 11
  • this way you can specify any valid range if the resulting doesn't apply, you can opt-in to 3-way merge with `-3` or `--3way` switch: `... | git apply --3way` – voiger Sep 29 '21 at 15:28
2

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
nvd
  • 2,995
  • 28
  • 16
2

Automating a bit more:

#!/usr/bin/env bash

filter_commit_to_files() {
    FILES="$1"
    SHA="$2"
    git show "$SHA" -- $FILES | git apply --index -
    git commit -c "$SHA"
}

Example usage:

filter_commit_to_files "file1.txt file2.txt" 07271c5e

I define it via copy and paste from here right into my shell. You don't need a here document.

davidvandebunte
  • 1,286
  • 18
  • 25
0

Checkout with Patch Flag

An easy way to accomplish what you are describing is through a checkout --patch. Modify the four variables I provided at the top. The script does the following:

  1. Clones the branch that contains your commit ( source branch )
  2. Checkout the branch you wish to move the file to ( target branch )
  3. Checkout the source branch at desired commit revision, supply the patch flag and relative location to the file as a parameter.
  4. Add, commit, push

This has always been the easiest method for me. Step #3 does create an interactive shell though, but you can default option the entire thing if you want to clobber the destination.

source_branch=branch_with_file_you_want
destination_branch=file_go_here
rev=757c47d4
relative_file_path=structures/serializers/node.py
message="Patching a file from $source_branch to $destination_branch"

git clone https://github.com/yourepo/app.git -b $source_branch $source_branch
cd $source_branch
git checkout $destination_branch
git checkout $rev --patch $source_branch $relative_file_path
git add $relative_file_path
git commit -m "$message"
git push

If you're remote is GitLab you can use git push -o merge_request.create if you need an MR

Chris Maggiulli
  • 3,375
  • 26
  • 39