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:
- You only have one remote, and it is called
- There's only one "main" branch, and it is called
0. Visualize the problem
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 18.104.22.168 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 6|/ 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 14|/ 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 19|/ 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 25|/ 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:
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:
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:
Or if you go by the SHA of the destination commit:
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:
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.
--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
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:
And now create a new branch label with a descriptive name:
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:
- The name of the branch to move. Eg:
- The location from where it stems; typically a branch label or a SHA. Eg:
- The location where we will transplant it; also a branch label or a SHA. Eg:
We will use the
git rebase command for this. This is how to use it:
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:
--force-with-lease may not be necessary here, depending on your specific scenario.