Mentions légales du service

Skip to content
Snippets Groups Projects
Commit dc533b64 authored by MALANDAIN Mathias's avatar MALANDAIN Mathias
Browse files

Add section about moving commits from wrong branch

parent 57c4ce03
No related branches found
No related tags found
No related merge requests found
......@@ -419,11 +419,56 @@ The first rule that applies to the current pipeline will take precedence. In the
# How to fix a commit to the wrong branch? {#wrong-branch}
{: .box-error}
**TODO**
This is something that a lot of people working with Git have done more often than they care to admit. Your project lives on several branches, and juggling between these branches is a process that will eventually have you commit something on the wrong branch. This can happen when you are working on several features, or when you are alternating between developing a new feature and patching a release: at some point, you *will* forget to run `git status` before committing, and your commit will end up on the wrong branch.
Not all occurrences require the exact same solution, in particular because of this Number One Rule that I already wrote, like, 600 times in this tutorial: **Public history must not be rewritten.** (Yes, I am still emphasizing it like it is the first time.) Hence, depending on whether your commit was already pushed on the origin or not, you should not respond in the same way.
## Moving local commits around {#wrong-branch-local}
This is the nice use case: you made your commit to the wrong branch, but it only lives on your computer. Nothing has been pushed yet.
{: .box-note}
This is one of the many reasons why `git commit` and `git push` should not always be used together: you can create several commits without pushing any of them, so that you can safely review your work and local history before pushing anything. Other reasons include the possibility to [rebase on a regular basis]({{'/08-advanced#rebase' | relative_url }}) so as to make the development and merging process more streamlined, and/or [reorganize your branch before pushing it]({{'/08-advanced#irebase' | relative_url }}).
Here is our use case (just change branch names for your use case): the last commit I created is on branch `main`, but it was actually intended for branch `feature`. (Oh, BTW, if `main` is protected, [which it should definitely be]({{'/05-good-practices#protect' | relative_url }}), Git will prevent you from pushing this commit, which will help you realize that you made a mistake and avoid making the situation worse.)
First, copy the hash of your commit: you will need it for what follows. Then, the process is basically in two steps: [cherrypick]({{'/08-advanced#cherrypick' | relative_url }}) the commit to the `feature` branch, and delete it from the `main` branch.
* Step 1: `git switch feature` (switch to the branch on which the commit should go), `git cherry-pick <commit_hash>` (create a new commit that applies the same changes on `feature` as your misplaced commit did on `main`);
* Step 2: `git switch main` (switch back to the branch where you wrongly committed in the first place), then `git reset --hard HEAD~1` (reset branch to its state one commit ago, which actually deletes the last commit).
{: .box-warning}
Remember that `git reset --hard HEAD~1` will undo and delete the changes from the last commit, while `git reset --soft HEAD~1` would just "uncommit" them but keep them in your working copy as untracked changes. We can safely use option `--hard` here, because the changes in question were just applied to another branch *and* have absolutely nothing to do on the current branch: this is one of the rare instances where using `git reset --hard` is "safe".
## Moving pushed commits around {#wrong-branch-pushed}
Now, assume that the situation is pretty much the same, last commit with hash `<commit_hash>` was created on `main` but should have been on `feature`, blah-bidy-blah, but this time, **you pushed this commit on `main`**. This is more serious now.
Step 1 remains basically unchanged: cherrypick the commit on the branch to which it belongs. However, if you were to apply step 2 as is, this would not change the situation on the remote. Instead, this would just result in the `main` branch of your working copy being one commit behind `origin/main`, and your next `git status` from this branch would just tell you that you should pull and it would be a simple fast-forward. Not good.
Some nice people on Stack Overflow would then advise you to force-push: `git push --force` would overwrite `origin/main` with the contents of your local `main` branch, thus effectively erasing the last commit from public history... You may already know why this is terrible advice in most cases, right? If one or several of your collaborators already pulled the branch as it was seconds ago, with your "wrong" commit on it, then you just made the situation worse for everyone. Once again (for the last time, pinky promise): **Do not rewrite public history unless absolutely necessary.**
{: .box-info}
By "absolutely necessary", I basically mean "one of the public commits contains sensitive, private information", and even then, you will have to carefully agree with all members of the project on a specific date at which public history will be rewritten, explicitly tell them what commands they will have to use, make sure that absolutely everyone understands and agrees... Yeah, basically, try and avoid such a terrible situation.
"Okay, so, I cannot just delete my commit, because it is already public. What do I do?" Well, pretty simple: you *revert* it.
{: .box-success}
**Reverting a commit** is the act of inverting/undoing the changes introduced by this commit. It does not remove the commit, but instead creates a fresh commit that applies the inverse changes.
This will work, because you do not have to rewrite public history: instead, you will only be adding a fresh commit on top of it, which is absolutely fine. And all you have to do it to type `git revert <commit_hash>`. Even better: if you want to "undo" the very last commit, then `git revert HEAD` will also work, because `HEAD` is actually a pointer to the last commit on the branch. How nice.
Of course, this means that the history of the branch will now contain two commits that basically cancel each other, the second one bearing the message `Revert "<message of the original commit>"`. There is now evidence that you screwed up at some point. Your collaborators *know*. Your credibility is lost. Nothing will ever be the same again.
...just kidding. Maybe one or two of them will notice, and they will be like "eh, happens to the best of us", or "wait, how do you revert a commit? I didn't know this existed", depending on who you work with.
So, to sum up:
* Step 1: `git switch feature` (switch to the branch on which the commit should go), then `git cherry-pick <commit_hash>` (create a new commit that applies the same changes on `feature` as your misplaced commit did on `main`);
* Step 2: `git switch main` (switch back to the branch where you wrongly committed in the first place), `git revert <commit_hash>` (create a fresh commit that undoes the changes introduced by your misplaced commit), `git push` ("undo" your mistake on the origin).
----
Aaaand you reached the end of this whole mess of a tutorial 🎉
Aaaand you reached the end of this whole mess of a tutorial 🎉 If you feel like something is missing (in this page in particular), please do not hesitate to contact me using the clicky black circles below.
Just in case, [here is the table of contents]({{'/index#toc' | relative_url }}).
\ No newline at end of file
......@@ -94,7 +94,9 @@ If you are already somewhat accustomed to Git and GitLab, you may also directly
* [Searching for bugs with `git-bisect`]({{'/08-advanced#git-bisect' | relative_url }})
* [Forks and pull requests]({{'/08-advanced#fork' | relative_url }})
* [CI pipelines: `.gitlab-ci.yml` examples]({{'/08-advanced#gitlab-ci' | relative_url }})
* [**(WIP)** How to fix a commit to the wrong branch?]({{'/08-advanced#wrong-branch' | relative_url }})
* [How to fix a commit to the wrong branch?]({{'/08-advanced#wrong-branch' | relative_url }})
* [Moving local commits around]({{'/08-advanced#wrong-branch-local' | relative_url }})
* [Moving pushed commits around]({{'/08-advanced#wrong-branch-pushed' | relative_url }})
In any case, [please do not hesitate to contact me](mailto:mathias.malandain@inria.fr) for any (constructive) criticism, requests or suggestions you may have. Enjoy!
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment