Joy
Joy

Reputation: 4463

Merging commits together in a branch

I am new to Git.

I have created a new branch new_branch. I have done all changes and commits in a different branch old_branch. Now I want to pick some commits from the branch old_branch to new_branch by git cherry-pick. For that I first need to know which commits are in that branch and then pick them and merge with new_branch.

How can I do this?

Upvotes: 2

Views: 128

Answers (3)

torek
torek

Reputation: 487755

The method given by @naomi will work fine, but there's a much easier way. You have your old_branch branch starting from some commit on some other branch, like this:

A --- B --- C --- D      <-- devel (let's say it's branch devel, anyway)
        \
          E - F - G - H  <-- old_branch

You've made a branch new_branch coming off some commit (B or C or D, perhaps) and you want to pick out some of the commits E, F, G, and/or H (maybe a much longer string of commits, but this should illustrate things).

Normally, if you wanted to take an unpublished sequence of work (perhaps old_branch is just such a sequence) and "move it" on top of the latest (commit D), you'd just do:

$ git checkout old_branch   # get onto old_branch
$ git rebase devel          # and rebase it onto commit D in "devel"

What that does is make "copies" of commits E through H, adding each one after "D". Then it peels off the label old_branch (which used to name commit H) and pastes it onto the copy-of-H, giving this:

A --- B --- C --- D                    <-- devel
       \           \
        \           E' - F' - G' - H'  <-- old_branch
         \
          E - F - G - H                    [abandoned, see footnote]

What you want is to pick some commit, let's say C (wherever you created your new_branch above) and do the same thing, but not "peel off the label" old_branch. Furthermore, you want to pick and choose which of E, F, G, and H go there. When it's all done you want the label new_branch added. That's actually really easy. Instead of creating branch new_branch at commit C, create it at commit H, the head of old_branch:

$ git checkout old_branch; git checkout -b new_branch

Now new_branch and old_branch are identical content-wise, but have different names.

Now you can simply rebase -i (interactive, lets you pick and choose) new_branch onto the point you want it to go. I've been assuming commit C here, which I have been assuming is one step back from the branch-tip named devel, so I will run with that:

$ git rebase -i --onto devel~1 devel new_branch

After you pick and choose the way rebase -i allows (and resolve any conflicts from deleting some commits and git rebase --continue to continue after resolving), you'll end up with something like this, depending on which commits you take and which you delete. I'll assume you delete F but keep the rest:

A --- B --- C --- D                    <-- devel
       \      \
        \       E' - G' - H'           <-- new_branch
         \
          E - F - G - H                <-- old_branch

You can actually do this all with just two commands (vs 3 above), as git branch can create new_branch at the same point as old_branch. Also, it's likely you want to rebase -i onto the tip of whatever branch, i.e., rather than rebasing --onto devel~1 you just want to rebase onto (the tip of) devel. In this case you don't need the --onto part after all:

$ git branch new_branch old_branch
$ git rebase -i devel new_branch

The result here is almost the same as before except now commit E' comes off D instead of C:

A --- B --- C --- D                    <-- devel
       \           \
        \            E' - G' - H'      <-- new_branch
         \
          E - F - G - H                <-- old_branch

This is basically what I do when I am reworking unpublished changes. If I'm on (say) branch revise I rename it to revise-0, create a new revise at the same place, then rebase -i and clean it up a bit. After one pass of that I may decide to revise it again, so I rename revise to revise-1 and create a new revise at the same place as revise-1, then rebase -i again, etc. All my old stabs at whatever-it-is are still there if I want them, until I decide I don't want them; then I delete all the -0 -1 -2 ... names.


Footnote: The commits marked "abandoned" above are still there—they're held against garbage collection via the reflog—but eventually the reflog entry expires and they go away in a git gc. Until then they're mostly invisible: git log --all and gitk --all won't show them, for instance. You can run these commands with --reflog, but even then, with no branch labels, they're still a little tricky to find. I like hanging on to them for a while, hence the multiple branch name workflow above.

Upvotes: 0

Chris
Chris

Reputation: 8656

There are quite a few ways to accomplish this, here are a couple for the commit listing:

git log --cherry new_branch..old_branch --oneline

will show you all the commits in old_branch that can't be reached from old_branch.

Or another way:

git checkout old_branch
git cherry new_branch --abbrev=6 -v

which will show you commits within the old_branch (without commits that introduce the same change), and will mark commits to indicate if they have -, or have not '+' yet been cherry-picked into the new_branch E.g:

+2bdcd1 1st commit     (Has not been cherry picked)
-8de6cc 2nd Commit     (Has been cherry picked)
+8ac1ee 3nd Commit     (Has not been cherry picked)

In either case, you can then use

git cherry-pick <commit>

Although you'd want to checkout the 'new_branch' in the second case.

Upvotes: 0

naomi
naomi

Reputation: 1984

git checkout new_branch
git log old_branch

The log will show each commit in old_branch, with a long string like "cf5e845a13866239eb87f2593d6edc6e273decc5", just above the commit message. This is the commit hash. You can then do

git cherry-pick <commit hash>

for each commit you want. I would recommend doing them in chronological order (working up the log from the bottom).

Upvotes: 1

Related Questions