If you’re a developer today, odds are you’ve learned Git, the version control system at the heart of modern software workflows. You know the basics — how repositories work, how to create branches and commit changes, and how to merge those changes and pull requests.
But now that you know the basics, it’s time to level up a little — to take advantage of some of the more powerful features of Git in your workflow. Here are five advanced Git features to make part of your current and future dev efforts.
Simplify commit histories with
When you have two branches in a project (e.g. a development branch and a master branch), both of which have changes that need to be combined, the
git merge command is the natural and straightforward way to unify them. A
merge adds the development history of one branch as a merge commit to the other. While this preserves both histories in complete detail, it can make the overall history of the project difficult to follow. In some cases, you might want a simpler and cleaner result.
git rebase command also merges two branches, but does it a little differently. A
git rebase rewrites the commit history of one branch so that the other branch is incorporated into it from the point where it was created. This makes for a less noisy, and more linear, commit history for that branch. But it also means that potentially useful details about the other branch and the merge process are removed.
To that end,
rebase is best used when you have multiple private branches that you want to consolidate into a single, clean commit history before merging it with a public branch. This way, you’re getting the full benefit of
rebase — making a commit history more linear and less noisy — without obscuring crucial details about the history of commits to your project.
Clean up merges with
git merge --squash
Another way to make merges, and subsequent commits, less noisy is by using the
--squash option in
--squash takes all the commits from an incoming branch and flattens them into a single, consolidated commit.
The beauty of a squashed merge is that you can choose how to apply the resulting staged files. You can just commit the whole set of changes as one, or you can commit a few files at a time where the changes are closely related. A squashed merge is also useful if the commit history of the incoming branch is useful only in the context of that branch, or if it’s from a private branch that is going to be discarded anyway.
As with a
rebase, this technique works best for committing internal branches to master, but it’s also suitable for pull requests if needed.
Speed up bug searches with
Subtle regressions in code are the hardest to tease out. Imagine you’ve just added a test to your codebase to chase down a bug, but you’re not sure when the bug first appeared … and you have hundreds or even thousands of commits in your repository. The
git bisect command lets you vastly reduce the amount of code you have to search to find the commit that created the bug.
When you enable
git bisect start) you specify two points in your codebase to bound your search: one where you know things are bad (
HEAD, typically), and one where you know things were still good.
bisect will check out a commit halfway between the bad commit and the good one, and let you run your tests. This binary subdivision process repeats until it turns up the commit that broke things.
git bisect is a godsend for large codebases with long, complex commit histories, sparing you the trouble of having to paw through every last commit in the hopes that you’ll find your bug sooner or later. At the very least, it cuts down by half the amount of searching and testing you need to do.
Reapply commits with
git commands are useful only in narrowly specific circumstances, and safely ignored even by moderately advanced users. But when you run smack into one of those specific circumstances, it pays to know them.
git cherry-pick. It lets you take a given commit — any commit, from any branch — and apply it to a different branch, without having to apply any other changes from the history of that commit. This is useful in a few key circumstances:
- You made a commit to the wrong branch, and you want to apply it quickly to the right one.
- You want to apply a fix from a branch to trunk before continuing with other work on trunk code.
Note that you have some options besides directly applying the commit when you
cherry-pick it. If you pass the
--no-commit option, for instance, the cherry-picked commit is placed in the staging area of the current branch.
Organize projects elegantly with Git submodules
Just as most programming languages provide a way to import packages or modules, Git offers a way to automatically include the contents of one repository inside another, a submodule. You can create a subdirectory inside a repo, and automatically populate it with the contents of another repo, typically by referring to a specific commit hash for the sake of consistency.
Note that Git submodules work best under the following conditions:
- The submodules in question don’t change often, or they are locked to a specific commit. Any work on a submodule, rather than with a submodule, should be managed separately.
- Everyone is using a version of Git that supports submodules and understands the steps needed to work with them. For instance, submodule directories aren’t always populated automatically with the contents of the submodule repository. You might need to use the
git submodule updatecommand on the repo to bring everything up to date.