How to move commits to the correct branch

So you made some commits with Git, and then you realised that you were not on the correct branch. Perhaps you made the commits on master, when they were intended for a branch. Or you were on some other unintended branch, it doesn't matter. What to do?

This is a common question when you are learning the ropes of Git. I hope this guide can get you out of that problem, while helping you understand Git better.

Since this is intended for beginners, I'm going to assume that your setup doesn't have anything out of the ordinary. I expect you'll have something like this:

0. Visualize the problem

If you are not using a visual tool to work with Git, get one. For macOS, I really recommend Fork, which also has a Windows version. For Linux, I have used gitg and it does the job well enough.

Fork gitg

If for some reason you can't use any of these tools, you can use the git log command with the --graph option, which will render an ASCII graph. Here's an example of use that will show the last 20 commits:

 1$ git log --all --decorate --oneline --graph -n 20
 2* 3a438eb (upstream/master, master, HEAD) Bump Rails dependencies from to 6.0.3 (#1640)
 3* 5a9abd0 Bump database_cleaner from 1.8.4 to 1.8.5 (#1636)
 4* d040bdc Dry up flashes scss and remove unused variables (#1620)
 5| * fd1b37d (upstream/nc-add-inline-documentation) Add YARD for inline documentation
 7* 15c49a6 Remove jquery ujs (#1618)
 8* 3468644 Support for searching over multiple fields (#1203)
 9* a156352 Add Appraisal install to the setup script (#1621)
10* 043e7c6 Bump factory_bot_rails from 5.1.1 to 5.2.0 (#1625)
11* a7d9964 Bump unicorn from 5.5.4 to 5.5.5 (#1624)
12* d7f0c41 Bump byebug from 11.1.2 to 11.1.3 (#1619)
13| * f3a7c6b (pablobm/jquery3, jquery3) jQuery 1.12.4 is vulnerable. There's no later 1.x
15| *   bd45abf (rinsed-org/master) Merge pull request #1 from rinsed-org/nch/remove-jquery-ujs
16| |\
17|/ /
18| * b6bc3b6 (rinsed-org/nch/remove-jquery-ujs, nch/remove-jquery-ujs) Remove jquery ujs
20* eb9b1b1 (refactor-resolver) Bump byebug from 11.1.1 to 11.1.2 (#1616)
21* 4d9ae55 Update Appraisal Capybara from 3.31.0 to 3.32.1 (#1612)
22* 13db63b Update Appraisal Capybara from 3.31.0 to 3.32.1 (#1612)
23| * dab588b (albertpalka-master) Missing imports
24| * 875b183 (albertpalka/master) Fixed SCSS imports
26* 76b2558 Bump autoprefixer-rails from 9.7.5 to 9.7.6 (#1608)
27* 656987b Bump database_cleaner from 1.8.3 to 1.8.4 (#1607)

Whichever tool you go for, run it now and have a look at the situation.

1. Did you push the changes?

If you pushed the changes to master AND you work in a team, unfortunately there isn't a good solution.

I don't recommend that you do anything that hides the mistake. Instead, tell your team immediately. These changes may have repercussions in your project. Or not. It depends. In any case, it's only human to do this, and more common than you may think. I have done it! This is why repositories often have specific settings that block direct pushes to master. It's too easy to do, and humans will invariably do it by mistake sooner or later. Perhaps your team should think of enabling this setting if available.

If you pushed to a branch that other members of the team are also working on, also tell them. You all may agree that it can be "fixed". Or perhaps it's simpler to create commits that revert the changes, leaving things to be cleaned up later. Or never. It depends, and it's fine.

If you pushed to a branch (including master) that only you are working on, then you can use this guide to rewrite history and pretend that this never happened.

If you didn't push, that's the best scenario. Use this guide to rewrite history. Nobody needs to know.

Finally: don't panic. Projects have ugly Git histories anyway. People make mistakes all the time. Still, I have spoken to people who felt very bad about making this mistake, and they thought that it would reflect badly on their skill in the eyes of employers. Don't worry: it won't. How you handle the situation is what counts here.

And by the way: nobody looks at whether the commit history of your pet GitHub project looks pretty.

2. Label your branch

First, you made commits to the wrong branch. What should the branch have been called? Put a label (a branch name) on it now. Identify the commit with all the changes that you want to move, and create a new branch with a name that describes what it is:

1$ git branch intended-branch

If you are used to creating branches with git checkout -b, you might find git branch strange. It does the same thing, but doesn't "move" you to the newly created branch.

3. Reset the accidented branch to its desired state

Now let's fix the accidented branch. You had a branch and you added the wrong commits to it. Time to remove those commits from the branch. To do this, you reset the branch. This moves the branch label to a different place in the tree.

First, make sure that you are on the branch that you want to reset:

1$ git checkout accidentally-extended

Second, find out the location where you want to move it. Use your visual tool to find something that marks the commit where you want to move the label. This marker will be another branch label, or the SHA of the commit at that location.

When you have this location marker, you can use the command git reset to move the branch label where you currently are to that other location.

For example, if you didn't push the changes, you may have a remote branch label marking the place:

1$ git reset --hard origin/accidentally-extended

Or if you go by the SHA of the destination commit:

1$ git reset --hard a1b2c3d4e5f6

You may wonder what the option --hard stands for. Don't worry for now, as we don't want to make this too complicated. You simply need it here. I'll leave that one for you to research.

4. Push the branch back in place

If you had commited to changes to the branch, and together with your team you decided that it's safe to rewrite the remote history, then you'll want to do this. Skip this step if you didn't push those changes.

Here you fix the remote branch by pushing the changes.

Assuming that your local branch is correctly in place now, push it. As before, you start by making sure that you are on the branch that you are going to push:

1$ git checkout accidentally-extended

Now the actual push. You'll need the option --force-with-lease to do this, as you'll be changing the remote history and this is normally not desired.

1$ git push --force-with-lease origin accidentally-extended

Why --force-with-lease and not --force? The short explanation is that --force-with-lease makes sure that no new changes have happened in origin that you are not aware of. Again, there's only so much complication that we want to add here, but I'll just recommend that you always use --force-with-lease instead of --force.

5. Secure your changes to prevent losing them

We are about to make a destructive change. Things can go wrong now. To prevent any issues, put a new label in your changes to avoid losing them. This is: create a new branch alongside the one you just created a couple of steps ago. This one will stay here, while the other one moves. It will work as a "backup copy" of sorts.

Again make sure that you are on the right location:

1$ git checkout intended-branch

And now create a new branch label with a descriptive name:

1$ git branch backup

6. Rebase the commits onto the right place

We will "cut" the branch with the commits, and we'll "transplant" it to a new location. It'll be a bit like gardening.

To do this, you need to know:

  1. The name of the branch to move. Eg: intended-branch.
  2. The location from where it stems; typically a branch label or a SHA. Eg: accidentally-extended.
  3. The location where we will transplant it; also a branch label or a SHA. Eg: master.

We will use the git rebase command for this. This is how to use it:

1$ git rebase --onto master accidentally-extended intended-branch

Note that accidentally-extended points to the commit right before the first commit that you want to transplant. In other words: you don't want to move the commit at accidentally-extended; you want to move the commits after it.

If this command resolves cleanly: congratulations! You have rebased the branch. If it did not rebase cleanly, and it complains about conflicts… that can potentially get hairier.

Conflicts happen when the changes that you are trying to add (in this case via a rebase) are in conflict with other changes that have been added to the target branch in the mean time. Git can't tell automatically which changes should "win", and therefore relies on you to do it.

If you know how to resolve conflicts, you can try. If anything goes wrong, remember that you have that backup label that still has your changes. They haven't been lost.

If you tried to rebase but then didn't feel comfortable resolving the conflicts, you can abort the rebase with git rebase --abort. It will bring things back to where they were before the initial git rebase command.

Unfortunately, resolving conflicts is a whole new topic, and therefore it's out of scope for this guide.

7. Push your rebased branch

If the rebase went well, have a look again with your visual tool of choice. Do things look correct now? If they do, don't forget to push your newly rebased branch:

1$ git push --force-with-lease origin intended-branch

Using --force-with-lease may not be necessary here, depending on your specific scenario.