Git Good: Improve Your Version Control Skills

Git is a fantastic tool, used by millions of people around the world. While Git GUIs can be useful tools, using git on the command line has a higher ceiling for productivity, especially when one can use aliases or refer to their own command history.

Today, I’m going to go over a few Git features that I use every day. Many of these will build on top of basic Git knowledge (add, reset, checkout, commit, merge, pull, push), so we should be familiar with these before proceeding.

Hooks

Git hooks are scripts that are run before or after an action occurs. By adding files (with names matching the hooks in the githooks man pages) to a repositories .git/git-hooks/ directory, scripts can be triggered to run on certain actions. The possibilities here are endless – linting before pushing, catching errors on commits, or automatically deploying websites, just to name a few. In turn, this helps the developer maintain a healthy codebase.

Reflog

Git reflog (pronounced ref-log, not re-flog) is a very handy tool in the case of an accidental change. It records when the tip of the local branch was updated, such as on a commit, reset, merge, pull, checkout, and more.

Output of running git reflog
Output of running git reflog.

While it’s fairly easy to reset or checkout an earlier revision if something like a merge goes wrong, it’s much more difficult to fix a deleted branch or a hard reset. This is where reflog comes in. Running git reflog will show the local history of the repo, and allows the user to quickly revert back to another point in time. Pretty neat!

Patch/Interactive

Many Git commands have a -p / --patch available to use, which allows for interactive usage of the command. It’s great to selectively add, stash, or commit file changes!

“Interactive usage” shouldn’t be confused with git add --interactive though, where the user can quickly stage, unstage, and revert file-changes. This also allows the user to open the patch menu for files.

Results of git add --interactive
Results of git add –interactive

The patch menu has a few option – which can seem confusing at first, but are quite natural after using it a few times:


y - stage this hunk
n - do not stage this hunk
q - quit; do not stage this hunk or any of the remaining ones
a - stage this hunk and all later hunks in the file
d - do not stage this hunk or any of the later hunks in the file
g - select a hunk to go to
/ - search for a hunk matching the given regex
j - leave this hunk undecided, see next undecided hunk
J - leave this hunk undecided, see next hunk
k - leave this hunk undecided, see previous undecided hunk
K - leave this hunk undecided, see previous hunk
s - split the current hunk into smaller hunks
e - manually edit the current hunk
? - print help

Aliases

Aliases are a huge time saver, especially for frequently used commands or for forgettable ones. There are two ways popular ways to do this.

Git Config

Every .config file can store these aliases, which can be convenient for using unique aliases in different projects. Naturally, global aliases can be stored in ~/.gitconfig. To add an alias for status (or whatever you choose), run the command git config alias.st "status". Adding the --global flag will store it in the ~/.gitconfig rather than the local config.

Additionally, the aliases could be manually added to a config file underneath the [alias] heading.

To see all aliases created this way, run git alias.

To run an aliased command, run git [aliasName].

Shell builtin aliases

These method, while not entirely due to Git, is a convenient way to store Git aliases. Simply run alias gst="git status"

Regardless of the method chosen, I recommend sticking to a single method for storing Git aliases. It’s much more convenient to run alias | grep git or git alias than to track down a command that could be in either.

Shell aliases are my personal preference, as I prefer typing something like gst rather than git st. It also allows for easy chaining of other shell commands (e.g. sed, cut, xargs, sort, uniq, grep, and more). However, I encourage you do go with the method that seems more natural to you.

I started out with these aliases, and have added others as time has gone on.

Recap / Quick Tips

  • Add hooks to .git/hooks/ to maintain code health
  • Use git reflog as a safety net when things go bad
  • git add --interactive for a nice command-line staging interface
  • Run add, stash, commit, and others with -p or –patch to interactively choose hunks
  • Use aliases (either git or shell) to boost productivity
  • git stash save -- "your message here" to label a stash
  • Both git log --patch and git log --stat will show more detailed log information
  • git cherry-pick to merge individual commits from one branch to another
  • git bisect to use binary search to find a specific commit
  • Use a dash (-) to reference a previous ref.git merge - or git checkout - will reference the last branch or ref.
  • When merging, use git checkout--ours FILENAME or git checkout--theirs FILENAME to keep changes from the local or incoming branch
  • Remove a file from git, but not locally by running git rm --cached FILENAME. This is useful when adding something to the .gitignore
  • HEAD references the tip of the branch. HEAD^ references the parent. HEAD~2 shows the parent of the parent. HEAD…HEAD~2 shows everything between HEAD and HEAD~2. In these examples, HEAD can be replaces with commit hashes.