In Git we can change the base commit of a branch by using a technique called rebasing. Suppose we have a repository that looks like the one on the diagram. The way rebasing seems to work is by moving the base commit of a branch (that is the parent commit of F1). The problem with rebasing is that this is not true. Commits are immutable. So what Git actually does is to create new commits (in this example, F1* and F2*) that come after the master pointer, and moves the branch pointer from F2 to F2*. Now there are no pointers or commits pointing to F2 so Git will delete them on the next Garbage Collection cycle. This might be OK if this branch is local (only you have it on your computer), but if other people created commits on top of F2 their work will be lost too!! So be very careful when rebasing.
Rebasing
To see an example of rebasing, I've created a new branch called feature. In this branch, I've updated the toc.txt. Back in the master branch, I've updated the main.js file. If we look at the log we can see that both branches have diverged. The new feature branch is at 9795f10, master is at 4104ec7, and their last common ancestor is 6166e28.
When we rebase the feature branch we will change its base commit from 6166e28 (which was the commit that our master branch was at when we created the feature branch), to commit 4104ec7 (which is the commit where master is now).
To rebase, make sure that you are on the target branch, in this case, feature. Now we run
git rebase master
This is telling Git to change the base of the current branch (in this case feature) with the tip of the branch that we supply (in this case master). Usually, when rebasing there will be conflicts that we must resolve the same way that we resolve merge conflicts.
Now if we check our log we can see that there's a linear path from master to our feature branch. If we merge these branches, Git will do a simple fast-forward merge. But take a close look at the commit IDs. Before the rebase, the tip of our branch was at commit 9795f10, now it's at commit 6f25ebf. This is a new commit!
We can checkout to the old commit 9795f10 and use the log to see the graph. The new tip of the rebased branch is in fact an entirely new commit. The old one still exists. But there are no pointers pointing to it, Git will Garbage Collect it.
Back on the master branch, we can now merge the feature branch onto it and Git will resolve it with a fast-forward merge. If we check out the log again, we'll see that both branches are now pointing to the same commit.
A few comments about rebasing conflicts. When Git tries to rebase, it will start by taking the oldest commit in the current branch and adding it to the tip of the target branch. If there's a conflict we'll have to open VS Code and solve it the same way we would solve a merge conflict. But because Git does a rebase one commit at a time, when we are done, we need to run
git rebase --continue
This will let Git know that it can continue to the next commit. If we wish to skip one commit, we run
git rebase --skip
Git will not create a copy of that commit and move on to the next one. If at any point in time we wish to stop the rebase all together, we run
git rebase --abort
This will tell Git to stop the rebase and revert everything back to before we even started.
If before starting a rebase we already know that we always want to keep the changes as they are in the feature branch, we can run the rebase with the -Xtheirs option.
git rebase -Xtheirs NEW_BASE_BRANCH
Likewise, if before starting the rebase we already know that we wish to keep the changes in the master branch, the we use the -Xours option.
git rebase -Xours NEW_BASE_BRANCH
This -X option is called the strategy option. You should keep in mind that when we supply a strategy to be used by default, said strategy is used in all commits that are being rebased.