Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Transfer most recent commit to another branch

Tags:

git

I had created an extra branch for some testing purpose.

Before starting to work, I switched back to master branch and, after having a tea, I started adding files and modifying some other files in master branch.

Only after I committed, I remembered that I was in master branch, while I had to switch to my second-branch before starting the changes.

could you let me know if it is possible to send this commit to the second-branch and remove it from master branch?

thanks

like image 825
rahman Avatar asked Feb 09 '14 06:02

rahman


2 Answers

If I understood you right you can do such things:

git checkout master
git log
now check hash commit you want to move - for example: 0123456
git checkout branch
git cherry-pick 0123456
git checkout master
git revert 0123456

that's all.

like image 111
user2699113 Avatar answered Sep 21 '22 19:09

user2699113


The answer to the question is "sort of, but maybe you don't have to".

To copy a commit, use git cherry-pick commit. This essentially tells git: "Go look at the commit I've asked you to cherry-pick. Whatever changes it made, make those same changes again, on the current branch. Then make a new commit1 with the same commit message."

Thus, if you've already made a commit on master and you want it on newbranch instead:

$ git checkout newbranch
$ git cherry-pick master

At this point, you'll be on newbranch and the commit will have been copied. You now need to do something about the old commit on master.

If you have not published (pushed, or let someone else pull) this commit anywhere, you need only "roll it back":

$ git checkout master
$ git reset --hard HEAD^  # be careful here! I always run "git status" first

Note that git reset --hard will wipe out any in-progress work.

If you have published the commit, you generally should revert it instead (see user2699113's answer). A "revert" is a lot like a cherry-pick, copying a commit, except that it reverses the changes: whatever was added then is removed now, and whatever was removed, is added back. (And, as with cherry-pick, revert will auto-commit unless you tell it not to.)

(Note that you can specify a commit by SHA-1 ID, but if there's a name—branch or tag name, or other reference name—for it, you can just write the name.)


1Unless you use -n or --no-commit to suppress it, or git is not able to make the same changes and tells you that there was a conflict and makes you fix it up yourself.


What's this about the "maybe you don't have to" part?

Here's a thing about git and its commits and branches. Git is different from most other version-control systems, in that while you do a commit "on a branch", the branch name is not actually part of the commit.2You assign branch names (or labels, whatever you want to call these), but the actual branch structures—the "structural branches"—are formed by each commit's history.

When a commit is made, that commit records its parent-commit(s). Branch names are just labels, that give you (and git) a place to start, and then git follows the commit parent IDs. This means we can draw the commit graph (the directed acyclic graph or DAG of commits) by hand:

A <-- B <-- C     <-- master
            ^
             \
              D   <-- branch

Here commit D is the tip of branch branch, and it points back to commit C as its parent. Commit C points back to B, which points to A, which is the initial (root, parent-less) commit.

When you are "on branch master" and make a new branch, that just makes a new label that points to the same place you are now. So suppose you had A through C before (I won't draw these with as many arrows this time; just assume arrows generally point leftwards):

A - B - C      <-- HEAD=master

This time I added the HEAD= to show how git keeps track of which branch you're "on". (The HEAD file in the .git directory just contains the name of the branch: cat .git/HEAD and you generally see ref: refs/heads/master or whatever.) If you now do git checkout -b branch you get this:

A - B - C      <-- master, HEAD=branch

(now .git/HEAD contains branch instead of master).

When you make a new commit (any new commit, including the ones from cherry-pick and revert), git looks at HEAD to see which branch you're on, and what that branch has for its SHA-1 ID. That's the "tip of the branch". Git then makes a new commit whose parent is the old branch-tip, and writes the new commit ID into the branch file, so that the branch name points to the new commit. (And of course HEAD still just has the branch name in it.)

So, if you're "on branch branch" and make a new commit D, this is what you get:

A - B - C      <-- master
          \
            D  <-- HEAD=branch

If you goof it up a bit though, and are "on branch master", you get this instead:

A - B - C      <-- branch
          \
            D  <-- HEAD=master

But wait, that's just what you wanted, except for the branch labels. All you need to do is make the name master point where branch does, and branch point where master does, and also switch HEAD so that it says branch instead of master.

As it turns out, that's easy ... well, pretty easy. :-) You need a temporary name since git won't let you have two branches with the same name. First, rename the branch branch to let you change master. Then rename master to branch, and rename branch's new name to master:

$ git branch -m branch temp
$ git branch -m master branch
$ git branch -m temp master

The first step changes the label branch to temp. The second changes master to branch (and you're still on it, so rewrites HEAD too). The last one changes temp to master, giving you the commit DAG and label setup you wanted originally.

As with the other section, you should only attempt this kind of "label swapping" on unpublished (un-pushed) commits. (The reason for this is simple enough: when you publish commits, you do it by name-to-commit-ID mapping. If you shuffle labels around and "re-publish", the commit IDs do not change but the name-to-ID mappings do, so anyone who picked up your previous advertising, that master meant bb71dde... for instance, now gets a new mapping that is not "fast-forward related" to the old one. People, and git, expect branch mappings to change, but usually only "fast-forward style".)


2For instance, in Mercurial, the branch name is actually recorded inside the commit-data.

like image 34
torek Avatar answered Sep 21 '22 19:09

torek