Gitting Your First Dev Job

What are the most essential Git commands to know in your first year as a developer?

A year and a half ago I decided I was ready for a career shift. I had heard great things about Turing School and decided to try out one of their Try Coding weekends. I attended classes all day Saturday and Sunday learning the basics of my shell, Ruby and JavaScript. I was hooked. I attended the Back End Engineering program from January - August 2018. We primarily focused on Ruby on Rails, with a sprinkling of JavaScript and other technologies.

I graduated from boot camp in August 2018, and began working at Viget the following month. I was nervous, expecting my Rails knowledge to be far inferior to other developers that have been working in the framework for years. Of course, there was still plenty of Rails to master, but I was surprised by all the other stuff there was to learn. In school, everything else felt like an overwhelming distraction from what I thought I was supposed to be focused on -- Rails.

I was so focused on learning the details of this magical framework, that I'd ignore tons of other things. I'd often need to run sudo in front of commands when others didn't need to. I'd get confused managing different versions of Ruby, and just change the .ruby-version and update everything to make my life easier. My philosophy was, why spend time resolving these types of issues when I could be doing the REAL work of writing code? TL;DR: This is not a great philosophy.

These types of tasks are a major part of a developer's job. I had told myself I'd learn these other details after I fully understood Rails, which feels comical in retrospect. You're never going to know everything there is to know about a particular framework. These "extra" tasks would have been extremely valuable to prioritize while at school.

Of the many coding-adjacent tasks to learn, my biggest regret is not spending more time learning Git before starting here. So without further ado, here are a few of my favorite Git commands.

Git

At school, I learned just enough to git by (sorry...). I could push and pull from a repo on GitHub, and check out a branch and merge that into master. Projects were typically small enough that you could coordinate with your partner on not touching the same parts of the codebase. Merge conflicts were rarely a big issue, with a few memorable exceptions.

Here's a few of the things I'd glazed over and have come to strongly value.

Spike branch

This seems minor in retrospect, but it was really valuable in speeding up my workflow initially. A spike branch is a branch you create that you're planning to experiment on and ultimately delete. When starting a potentially gnarly rebase on my-cool-feature branch or perhaps wanting to go back a few commits and try a different implementation, I could branch off of that branch to a duplicate lets-try-this spike branch. That way, if I were to mess anything up, I could go back to the original my-cool-feature branch. It let me take greater risks when learning, because I knew even if I royally botched the code or the Git history, I could delete the spike branch and start again. Now that I know the tools better (especially git reflog...see below), I use this less often, but it is still a great tool to have in the toolbox.

# from my-cool-feature-branch, let's make a spike branch
$ git checkout -b lets-try-this
# experiment with with some code and commit it
$ git add .
$ git commit -m "trying this out"
# whoops that really didn't work! revert!!
$ git checkout my-cool-feature-branch
# whew back to square one. let's delete that spike branch for the sake of git hygiene
$ git branch lets-try-this -D

Rebase

At Turing, we learned rebasing for one project, and it was a terrifying beast that I vowed to never touch again with a 10 foot pole. One reason some use rebase instead of merge commits is that it can make the Git history cleaner to read. That was not a very convincing argument when it was just me and a partner working together on a project that was completed in a week or less. Handling merge conflicts at every single commit seemed tedious (especially as this was before I knew to squash commits). Once I realized that rebasing was the Viget standard, I followed this tutorial to better wrap my head around it, but the majority of my learning just came through practice. It wasn't until I was working on projects of a larger scale that I saw the value in rebasing. Now having a team of several developers, who may have been maintaining a codebase for months or years, it became clear that an easy-to-read Git log was a necessity.

# from my-cool-feature-branch, let's rebase off of master to pull in the latest changes
$ git pull --rebase origin master
# You'll see something like this in your terminal
remote: Enumerating objects: 60, done.
remote: Counting objects: 100% (60/60), done.
remote: Compressing objects: 100% (21/21), done.
remote: Total 46 (delta 26), reused 36 (delta 19), pack-reused 0
Unpacking objects: 100% (46/46), done.
From github.com:vigetlabs/cool_project
 * branch            master     -> FETCH_HEAD
   27b325e..3814381  master     -> origin/master
First, rewinding head to replay your work on top of it...
Applying: Adding absinthe_ecto and absinthe_phoenix
Applying: Wires up the user model
Applying: Adding tests associated with users
# If there are any conflicts associated with a particular commit, it will alert you and you will fix directly in your editor, then add and continue
$ git add .
$ git rebase --continue
# No exciting message to let you know when the rebase is complete, it simply applies the last commit and calls it a day.
Applying: User can login

Squash

Commit early and commit often. But, you also don't want to be that person with messages like its working!, oh wait now it's REALLY working, fixing a typo, removing trailing whitespace. On that note too, checkout this great blog post on writing a great commit message. With squashing, you can rewrite your history! No one needs to know about your multitude of typos, or flip-flopping back and forth between two different implementations before deciding on one. For a quick fix (like the example of noticing a small typo just after you commit), there's git commit --amend, where you can add to your last commit either with a new commit message, or add the --no-edit flag to not change the commit message. If you have several commits that you want to squash down into one, you'll want to squash them using an interactive rebase.

# Say you want to squash those last four commits into one
$ git rebase -i HEAD~4
# This will prompt your text editor to open. Replace the words "pick" with "squash" next to the commits you want to 
# squash into the commit *before* it. Save and close you editor, and Git will squash your commits and ask you to 
# change your commit message.

Cherry pick

You're working on my-cool-feature and your coworker is working on this-other-awesome-feature when you realize you'll need to share a bit of code. In this scenario, one of you would make the required change, commit it, then the other can cherry pick that specific commit into their branch. This avoids either both people duplicating the work in separate commits (which will make history confusing later when both are merged into master), or avoids merging two otherwise unrelated branches too soon. They can just share the one commit.

# checkout the branch with the commit you want to pull in to find its SHA
$ git checkout this-other-awesome-feature
$ git log
a8892be Margaret Williford      Adding tests associated with users
fa805ff Margaret Williford      Wires up the user model
97fa01a Margaret Williford      Adding absinthe_ecto and absinthe_phoenix
...
# found the commit I want!
$ git checkout my-cool-feature
$ git cherry-pick fa805ff
# Note you may have to resolve merge conflicts here

Reflog

Git is your friend. It's here to manage versions, track history, and generally just not lose your sh!t. If you think you've lost a commit, you can usually find it in reflog and revert it. Understanding how to use git reflog was the single thing that greatly relieved my Git anxiety. git reflog shows a list of all commits Git has in storage, as well as details on every time you switched branches, merged, or rebased. If you git reset your HEAD back a few commits, and realize that you actually needed one of the later commits, git reflog will still have a record of the commit you left behind. Using the SHA associated with the commit you need to pull back in, you can cherry-pick that commit back into your history.

$ git reflog
a8892be (HEAD -> example) HEAD@{0}: rebase finished: returning to refs/heads/example
a8892be (HEAD -> example) HEAD@{1}: pull --rebase origin master: User can login
fa805ff HEAD@{2}: pull --rebase origin master: Adding tests associated with users
97fa01a HEAD@{3}: pull --rebase origin master: Adding absinthe_ecto and absinthe_phoenix
3814381 (origin/master, origin/HEAD) HEAD@{4}: pull --rebase origin master: checkout 381438199e473086f6ec42f17fc3daf1eb946dd6
45f2a31 (graphql) HEAD@{5}: checkout: moving from graphql to example
45f2a31 (graphql) HEAD@{6}: commit: here's an example commit
ef7e7bf HEAD@{7}: checkout: moving from example to graphql
ef7e7bf HEAD@{8}: checkout: moving from graphql to example
...

These are the Git tools that I reach for the most, but there's a lot of Git magic to be learned. While frameworks may come and go, Git seems to be here to stay for the foreseeable future. It's worth investing the time understanding it. It isn't something extra, it's a fundamental part of the work we do.