Although every team’s workflow is unique, some best practices stay constant. Let’s take a look at a few of my favorites:

1. Try Terminal.

Although plenty of GUI applications for Git exist, I strongly recommend using Git in Terminal. This might seem counterintuitive: Don’t GUIs make complex programs easier? That’s true for basic users, but experienced developers often prefer a text interface. It keeps them in the zone, fingers rarely leaving the keyboard, eyes rarely leaving the code.

Plus, for interacting with GitHub and other repository systems, GUI applications have two drawbacks: They hide the inner workings of Git, preventing developers from mastering it, and they sometimes introduce errors of their own.

The Git command line serves to inform the user and keep errors out. Accessing Git’s documentation for how commands are used is as easy as typing “man git” in Terminal. When commands fail, Git provides useful messages and points out solutions. For example, when the user tries unsuccessfully to push code to a remote repository, the failure message usually explains why Git refused. The main branch might, for instance, be missing commits that are on the remote branch.

These messages help you learn Git more deeply. I used to abort when a patch was rejected, but one day, I noticed the message Git provided: “Use ‘--skip’ to skip this patch.” After a quick Google search, I’d learned how to better manage merge conflicts during rebasing.

A pull-request workflow promotes great habits and provides protection. It consists of just two basic steps.

First, for every new feature, start a new branch named after that feature. That way, the master branch stays untouched as you continue development. Knowing there’s no way to introduce bugs into the master engenders confidence and encourages experimentation.

Then, once the branch is stable, create a pull request. If you’re working with others, this allows them to review your changes. If you’re on your own, it lets you review a diff of the code in the branch. Once all issues are settled, merge the code into the repository.

You’ll want to keep the commit history clean. With fewer commits in your history, it’s easier to reason your way through a network of branches and manage conflicts and merges. But pull requests can have multiple commits. So use the squash-and-merge function to turn multiple commits into one when the pull request is merged in.

4. Avoid merge commits.

Conversely, you should skip merge commits whenever possible. These are tempting one-line fixes to resolve differences between branches. But if you’re squashing pull requests and rebasing, there shouldn’t be irreconcilable code differences between branches. Merge conflicts should happen only in rare cases when two developers edit the same line of code.

5. Review others’ code independently.

It often seems easiest to ask colleagues about their code during reviews, but this only delays a merge for another round. Instead, unless it’s a matter of mere preference or opinion, try to answer questions about their code on your own. When possible, review an entire pull request at once. Piecemeal comments and questions make for grumpy co-workers.

Code refactors are common. As an application matures, you’ll find better locations or names for files. Fortunately, there’s a Git command for moving or renaming files: “Git mv.” Without this command, Git might record that you’ve deleted a file and added a new one (when you only renamed it), so using “Git mv” is critical. For this reason, some integrated development environments, such as PyCharm, invoke “Git mv” in the background if you use it to rename or move a file.

7. Rewrite; don’t rage.

Even if you do everything above, mistakes happen. You might accidentally delete the wrong branch or merge a whole section of code into the wrong branch. You may even force-push and lose hours of work.

When mistakes occur, avoid making them worse. Take a deep breath. If you get frantic, you’re more likely to compound mistakes. Remain calm, and proceed cautiously.

The glory of Git is its “Undo” feature. Never be afraid to make copies of branches. As a sort of insurance policy, I’ll often create a temporary version of a branch by appending a single letter.

For example, if I’m dealing with merging issues, I need to fix them without messing up my current branch. So if I’m on branch “make-filters-great,” I’ll use “Git checkout-b make-filters-great-m” (where “-m” indicates merge branch). But then — whoops! — I rebased from “dev” and got myself into a mess; “make-filters-great-m” is now a train wreck!

No worries: I can check out “make-filters-great,” delete “make-filters-great-m,” and create “make-filters-great-m-part-2.” No harm done.

This time, my rebase worked, so I’ll push “make-filters-great-m-part-2” to origin as “make-filters-great” using “Git push origin make-filters-great-m-part-2:make-filters-great.” If it fails for any reason on GitHub, I still have “make-filters-great” locally.

Remember, your computer is a repository, just like the one on GitHub. If you can straighten things out locally, you can then force-push the right version to GitHub. If it’s a shared branch, be sure to inform your co-workers, who may need to delete and re-pull that branch.

Ultimately, using Git is like riding a bicycle. It’s fairly easy to learn the basics and ride down the street. Too often, that’s where people stop learning.

To truly master it — to ride the Tour de France — you need to practice. You need to endure, try new commands, and work with more experienced developers. It’s not always easy, but trust me, it’s worth it.

Josh Guffey
is a Yeti Alum.
Previously, Josh co-founded Collective Ray, a design consulting company that created user interfaces for web and mobile applications. He’s also served as a partner at Sourcebits and as a developer for Method.
A dog owner, bicyclist, and yoga enthusiast, Josh graduated in 2009 from the Art Institute of Fort Lauderdale with a degree in web design and interactive media. His tools of choice are JavaScript, Git, Xcode, and Sketch.
Follow Josh on Twitter.