tags:

views:

10700

answers:

5

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 mods that I'm generally sure of
  • exp1: experimental branch #1
  • exp2: 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 files from one development branch to another while leaving behind everything else?

Approaches I've considered:

  1. git merge --no-commit followed by manual unstaging of a large number of edits that I don't want to make common between the branches.

  2. Manual copying of common files into a temp directory followed by git checkout to move to the other branch and then more manual copying out of the temp directory into the working tree.

  3. A variation on the above. Abandon the exp branches 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.

+13  A: 

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.

1800 INFORMATION
Did you intend to write `HEAD^` instead of `^HEAD`, or does the latter have a distinct meaning?
akaihola
I think it was a typo
1800 INFORMATION
+3  A: 

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

  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

  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

Cory
Tip: first use the `git cherry` command (see manual first) to identify commits you haven't yet merged.
akaihola
+7  A: 

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, outlined on www.angrylibertarian.com/node/53

Edit: stackoverflow apparently can't handle the hash character, please see the linked site for an easier to read version.

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
nosatalian
Please edit your answer, select your code snippet and hit the "101 010" icon (3rd icon in the 2nd group of the toolbar) to indent it by four spaces. This will fix formatting.
akaihola
I fixed up the formatting - this is a pretty nice method, if you want to do a selection of commits
1800 INFORMATION
I'm using this technique now and it seems to have worked really well.
dylanfm
+28  A: 

I had the exact same problem as mentioned by you above. But I found this 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...>
Bart J
Bart J's linked article is the best approach. Clear, simple, one command. It's the one I'm about to use. :)
Pistos
This is clearly the way. Big help; thanks.
Jay
Thanks, I think it's also the easiest way to do.
Maxime
This isn't a real merge. You're picking changes by file instead of by commit, and you'll lose any existing commit information (author, message). Granted, this is good if you want to merge all changes in some files and it's ok that you must re-do all commits. But if files contain both changes to merge and others to discard, one of the methods provided in other answers will serve you better.
akaihola
seems to work, unfortunately, i don't see the changes with `git diff`
mykhal
this is great if you've created changes that shouldn't be a part of the topical branch your working on, but of some other.
kRON
+1  A: 

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
Felix