One of Git’s core features is the ability to make multiple versions of your project. Often, these are used for short-term forks called “feature branches,” which get merged into master. However, sometimes it is necessary to have truly separate branches, which makes it harder to keep them in sync.
Why Maintain Different Branches?
Usually, branches are short lived, and intended to be merged back into the main release branch. In some cases though, it’s necessary to maintain fully separate branches. For example, you may have branches targetting different platforms or dependencies, with different features, or just separate release branches that you keep alive for a while.
This is the same workflow with most forks, and depending on the amount and severity of changes, it may be hard to keep in sync with upstream. If your two branches are largely in sync minus a few commits, you’ll have a much easier time handling conflicts and keeping things up to date.
In most cases though, you should look for alternatives to having to separate branches, because it can get quite tedious, especially with merge conflicts and all of the extra testing. For example, if you have two builds targetting different platforms, many languages have build configuration and preprocessors that can handle that. C# has #if NETVERSION, which allows changes to code based on which platform it’s compiling for.
Whatever your reason though, this is a valid Git workflow that many people do use.
Keeping Branches in Sync With Rebasing
There are basically two options for how to go about this. The first and most common method is rebasing, which is a lot like merging, but allows the branches to be completely independent.
You can think of Git commits like a chain of changes going back in time, each one pointing to the previous commit. When you create a new branch, it breaks off from the main master branch at a specific point, the base of the branch.
Rebasing is basically lifting up the entire feature branch, and moving it to a new point in time, where the end of it points to a different chain of commits. This is most useful if the forked branch only contains a few commits, because then the merge conflicts will be easier to sort out. In this example, rebasing this branch incorporates the B and C commits, but not D and E, as it doesn’t have to rebase onto the HEAD of the branch.
Rebasing is commonly used only for local branches though, and presents a few problems when used with shared branches. The commits don’t actually move; they’re copied over, resulting in new commit IDs, with the HEAD of the branch being moved to the new location.
This means that the old commits are left stranded. Because Git is a decentralized version control system, your coworkers could have un-pushed commits that reference those deleted commits. Rebasing will have to be something you coordinate with any collaborators, and if anyone has any conflicts, they will need to fix it locally by copying those changes to the correct location.
Rebasing a branch is pretty easy. You’ll need to checkout the feature branch, pull all the changes from your remote, and then run rebase
This will likely result in merge conflicts, which you will have to resolve yourself and then commit the changes.
While most day-to-day Git tasks can be done pretty easily from the command line, rebasing is by nature a pretty visual operation, so we do recommend using a GUI Git client like Fork or GitKraken if you can. This will show you the branch lines and help you plan the rebase more effectively, and can even perform interactive rebases.
Since rebasing basically applies each commit from the feature branch onto a new location, you don’t have to include all of them, and interactive rebasing can drop commits you don’t need. It’s possible to do from the command line, but makes more sense from a GUI.
Fixing Unwanted Commits
What happens if you don’t want to include some commits when you rebase? If you have commits on your feature branch that you’d like to exclude when rebasing onto master, then you can do an interactive rebase. This will drop the unwanted commits when rebasing, essentially removing them from history.
But, it’s much more likely that there are commits on the master branch that you’d rather not have on your feature branch. Because rebasing sets the base of the new branch to master, there’s no way to not include those commits.
If there’s just one or two commits that you’d like to not have, you can probably rebase anyway, and either sort it out in the merge conflict, fix it manually, or just revert the commit. Git has a “revert” command that will apply the opposite changes, essentially reversing a commit and making it like it never happened. To use it, run git log to find the commit you want to revert:
Then, copy the SHA1 hash and revert the commit:
This will create a “revert commit” on the feature branch.
Cherry-Picking Commits Onto Another Branch
The last scenario you may run into is if there are only a few commits from master that you’d like to have on feature. This is common if you’re maintaining branches for different versions, because there are often hotfixes and patches that need to be applied to older versions of software.
This is the most annoying method of syncing things though, because if you’re not keeping your branches at least somewhat in sync, there’s a high chance that the commit you want to include might be incompatible with the older branch.
But, Git does have tools to essentially copy and paste commits onto a different branch. This action is called cherry-picking, because you’re grabbing a single commit out of the history and plucking it out. Similarly to rebasing, cherry-picking creates new copied commits, but Git is usually smart enough to sort it out, even if you were to merge the branches together later on.
To cherry pick, you’ll need to grab the commit ID. If your branch history is complicated, you can run git log with the –graph option to get a visual representation of your history, though a GUI client is especially useful for tasks like this.
Then, make sure you’re on the feature branch, and run cherry-pick with the commit ID to copy it over.
This may result in conflicts, which you will have to resolve.