by

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.

gitg Fork

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*   5c32b78566 (origin/master, origin/HEAD, master) Merge pull request #40280 from rails/no-rg-dep
 3|\
 4| * 6fca0f31f1 Try calling clock_gettime rather than testing the platform
 5* |   7cc563427d Merge pull request #40281 from kamipo/add_dependency_rexml
 6|\ \
 7| * | c23533ee0b rexml is no longer default gem in Ruby 3.0
 8|/ /
 9* | 94ea7281b8 Use master branch for `listen` and `redis-namespace`
10* | 8ecb0e22d7 Fix typo s/inherit_from/inherit_all/ [ci skip]
11* |   20423ef455 Merge pull request #39320 from trevorrjohn/i18n_key
12|\ \
13| * | 6fba3c3be0 Allow ActiveModel::Name fields to be overriden
14* | |   fc2a1ed24f Merge pull request #40240 from adrianna-chang-shopify/subscriber-attach-to-with-inherit-option
15|\ \ \
16| |_|/
17|/| |
18| * | 387aa8c373 Subscriber.attach_to with inherit_all option
19* | |   19eabad014 Merge pull request #40279 from the-spectator/corrects_require
20|\ \ \
21| * | | b314ab555e Move require `active_support/core_ext/string/filters` to `active_job/log_subscriber` from `active_job/logging`
22* | | | b6d86add73 Remove unused require
23|/ / /
24* | |   5abe09bba7 Merge pull request #40213 from dbussink/allow-setting-digest-class
25|\ \ \
26| * | | 0f6c9bc786 Add an ActiveSupport option to allow setting a digest class
27| | |/
28| |/|
29* | |   33cd36b898 Merge pull request #40278 from kddeisz/gitattributes
30|\ \ \
31| |/ /
32|/| |
33| * | da39688e26 Create a gitattributes file for templated apps
34|/ /
35* |   fb852668df Merge pull request #40276 from ghiculescu/fix-bad-formatting
36|\ \
37| * | 3457d227c1 Fix formatting error in concerns doc
38|/ /
39* |   f8dd68e131 Merge pull request #40275 from composerinteralia/approvals
40|\ \

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.