Git is a SHA Management Tool

02-03-20 Melissa Thompson

Viewing Git as a SHA manager instead of as a source control file manager clears up a lot of confusion. Melissa shares some fundamental Git commands that help her manage SHAs in an enterprise-level project.

I use Git every day in my work on an enterprise-level ecommerce codebase. Some people view Git as a source control file manager, but Git doesn’t care about your files. It does everything based on SHAs (unique 40 character identifiers). Branch names are really just aliases for SHAs. Files, branches, and commits are all SHAs.

When I realized that Git just works on SHAs, my workflow changed. It cleared up a lot of confusion I had about where things were, how they were interacting, and whether I needed to panic when I ran a git reset --hard when I shouldn’t have. Here are a few basic Git commands for managing SHAs that I use to make my life a little easier.

Git Log

In our codebase, history is always changing as we add new commits and update our master branch. git log helps me keep up with a changing commit history and is one of my most frequently used Git commands. It shows a log of all the commits in my current branch:

commit 7a2733be4e9cfd057cc34c55673a578a9a021863
Author: Melissa Thompson <melissa@example.com>
Date:   Thu Dec 12 11:44:35 2019 -0500

    feat: add photo carousel


commit 672121a43d9e8dd298c5437a5a52bb5b4b9c318c
Author: Nate Jacobs <nate@example.com>
Date:   Thu Dec 12 09:51:31 2019 -0500

    fix: add image load handling

I usually only use this command for a couple of tasks:

  1. Looking at previous commit messages
  2. Getting commit SHAs for various purposes

The basic git log command includes a lot of details that I’m not always interested in. As you can see from the example and the list of things I use the command for, I usually don’t need to know who authored the commit or the date of the commit. To condense the information I get from the log, I add the --oneline flag. This flag is actually a combination of two others.

The first is --pretty=oneline, which condenses the commit logs into a single line and removes the author and date details:

7a2733be4e9cfd057cc34c55673a578a9a021863 feat: add photo carousel

The second is --abbrev-commit, which shows a shorthand version of the commit SHA:

commit 7a2733b
Author: Melissa Thompson <melissa@example.com>
Date:   Thu Dec 12 11:44:35 2019 -0500

    feat: add photo carousel

Using git log --oneline combines these flags for a super-condensed, need-to-know version of the commit log:

7a2733b feat: add photo carousel

Git Reset

The git reset command is another one that’s useful in many scenarios.

Imagine you’re reviewing a pull request for someone, and they’ll need to make some changes. If part of their branch is broken, help them out by suggesting a fix instead of just pointing out the issue. To do this, I’ll sometimes pull down the branch and make changes to figure out what a good suggestion would be. After that’s done, to keep my local environment clean, I’ll use git reset origin/branch --hard to reset my local branch to the SHA that is on the server. The --hard flag tells it to reset the branch no matter what, so it’s one that I use sparingly. You can learn more about the --hard and --soft flags in Catherine’s article, I Screwed Up Git; How Do I Fix It?

Another way I use git reset is to unstage a file. For example, sometimes changing my code causes an existing unit test to fail. When that happens, I need to know what’s happening in that unit to be able to fix the test. I make comments as I go through it so I can piece together how it works, and then I have the knowledge I need to fix the test file. Then I want to stage the test file but not the unit file. I could just say git add test-file.js, but if instead I run git add ., I’ll have added the test file and the unit file:

Changes to be committed:

    modified:   test-file.js
    modified:   unit-file.js

At this point, before I commit, I run git reset HEAD unit-file.js to unstage the unit file:

Changes to be committed:

    modified:   test-file.js

Changes not staged for commit:

    modified:   unit-file.js

You can also reset to a specific SHA by running git reset <SHA>

Git Reflog

While git log shows commit logs, git reflog (short for reference logs) shows the history of all Git actions I’ve taken. It shows when branches and other references were updated.

4419c39 (HEAD -> master, origin/master, origin/hero-cube-position, origin/HEAD, hero-cube-position) HEAD@{0}: merge hero-cube-position: Fast-forward
c25a76a (origin/new-hero-text-style, new-hero-text-style) HEAD@{1}: checkout: moving from hero-cube-position to master
4419c39 (HEAD -> master, origin/master, origin/hero-cube-position, origin/HEAD, hero-cube-position) HEAD@{2}: checkout: moving from master to hero-cube-position
c25a76a (origin/new-hero-text-style, new-hero-text-style) HEAD@{3}: rebase finished: returning to refs/heads/new-hero-text-style
c25a76a (origin/new-hero-text-style, new-hero-text-style) HEAD@{4}: pull --rebase: checkout c25a76a7ea9e1b99dc7a4e5e4acec6840513eae1
9f84e75 (origin/melissa-remove-auth, melissa-remove-auth) HEAD@{5}: commit: remove auth for prod
41f47d1 HEAD@{6}: checkout: moving from master to melissa-remove-auth
41f47d1 HEAD@{7}: reset: moving to HEAD
41f47d1 HEAD@{8}: pull --rebase: Fast-forward

Remember that --hard flag you can attach to a git reset? The one that discards all changes to files you’re tracking? This tool is really handy if you accidentally run that and lose your work. You can use git reflog to get the SHA (that seven-digit code at the beginning of each line) from before you ran the reset and go back to that place in your history.

Git Cherry-pick

Sparkboxers love a clean commit history, which means we avoid those messy merge commits that muddy it up. In his article How to Not Dread Rebases When Managing Long-Lived Feature Branches, Adam talks about how and why we avoid merge commits and what we do instead. One of the commands he calls out is git cherry-pick. It’s a great way to copy and move around commit SHAs.

For example, maybe I’ve accidentally made some commits to master locally. This is inadvisable because our master branch should always be in a state where it could be pushed to production. So I need to move those commits into a branch. I can make a new branch git branch fix--IE-flexbox-bug. I still need to know what to put in that branch, though. At this point I would use git log --oneline to grab those commit SHAs:

9965ebd (HEAD -> master) fix: update tests
3a980fc fix: flexbox bug in IE

Now I can checkout my new branch git checkout fix--IE-flexbox-bug and run a cherry-pick git cherry-pick 3a980fc 9965ebd. This will copy and move those commits into my new branch. I can check that I’ve cherry-picked in the correct order by running another git log --oneline to see that my commit messages are in the order I want:

341252c (HEAD -> test) fix: update tests
895b713 fix: flexbox bug in IE

The commit messages are in the order I made the changes, which means I should have a clean cherry-pick with no conflicts. Notice that once I’ve cherry-picked those SHAs onto a new branch, the SHAs change. That’s because the history and timestamp of the commit is different, so Git adjusts the SHA to accommodate that.

SHA Management

For all the commands I’ve listed here, there’s a pattern. Whether I’m running git log, git reset, git reflog, or git cherry-pick, I’m always interacting with SHAs. The primary way I use Git is as a SHA management tool. Since my goal in using Git is to manage SHAs, I can use the commands above to navigate the codebase with ease, always finding what I need and relatively easily remedying any mistakes I’ve made.