DEV Community

Samuel Rouse
Samuel Rouse

Posted on

Git Bits: Symbols

Git is...complicated. This series focuses on breaking git down into commit-sized pieces.

Symbols

Some of the confusion around git comes from the different special symbols and references used to navigate through repository history, compare changes between commits, and manipulate branches. These may look cryptic at first, but learning what and how they work can change the way you think about git.

HEAD

Not quite a symbol, but an important foundation. This is the "You are here" sign on the map that is your git graph. HEAD is a reference to the current commit you're working on.

While each branch has a "head" (lowercase) that refers to the latest commit on that branch, the HEAD (uppercase) refers to where you are currently working. While it is most often pointed at a branch, it can point to a tag or a commit as well.

  • HEAD: The current commit
  • HEAD^: The parent of the current commit
  • HEAD@{1}: The previous value of HEAD (where HEAD was before the last command that moved it)

Ancestry References

Needing to traverse the graph happens. Perhaps you need to quickly see if one of your recent changes caused a defect, or you realize you meant to split off a related feature branch three commits ago. These operators allow you to walk the graph.

The Tilde (~) Operator

The tilde operator allows you to reference previous commits – or ancestors – relative to a starting point:

  • HEAD~1: Refers to the commit one step before HEAD
  • main~3: References the commit three steps before the tip of the main branch
  • HEAD~: Shorthand for HEAD~1
# Combine the last 3 commits into one
git reset --soft HEAD~3
git commit -m "Combined commit message"
Enter fullscreen mode Exit fullscreen mode

Branch Prediction

When using the tilde operator, you may run into merge commits. Git follows the first parent in these cases. This is particularly useful when you want to go back in history in a linear fashion.

# Show what was committed one commit ago
git show HEAD~1

# Check out the version from three commits before
git checkout HEAD~3
Enter fullscreen mode Exit fullscreen mode

To help understand, the first parent was the checked out or current branch when the merge happened. Looking back at a merge, you might think of the current branch as the merge destination or receiving branch. When resolving merge conflicts, you may see the terminology "ours" vs. "theirs".

# Checkout "our" branch
git checkout main

# Merge "their" branch into "our" branch
git merge develop
Enter fullscreen mode Exit fullscreen mode

"Our" branch is the first parent in a merge; the checked out branch. "Theirs" is the branch being added.

The Caret (^) Operator

The caret operator also references ancestors but is focused on navigating merge commits:

  • HEAD^: Refers to the first parent of HEAD (same as HEAD~)
  • HEAD^2: Refers to the second parent of HEAD (only meaningful for merge commits)
  • main^^: References the grandparent of the main branch if the last two commits were merges (same effect as main~2)
# Show the first parent of a merge commit
git show HEAD^1

# Show the second parent of a merge commit (the branch that was merged in)
git show HEAD^2
Enter fullscreen mode Exit fullscreen mode

If you are troubleshooting a defect and want to see the state of a feature branch before it was merged into main, you might use ^2 to navigate onto "their" branch without needing to know the branch name or details.

Stacking Symbols

These symbols describe a path through the graph to a particular point. What's more interesting: you aren't limited to one. You can combine a series of traversal operators to get to a specific commit.

# 1. Go back one commit: ~1
# 2. Go back on a merge to the incoming or "their" branch: ^2
# 3. Go back three more commits on that branch: ~3
git checkout HEAD~1^2~3
Enter fullscreen mode Exit fullscreen mode

You won't use this style very often, but I think knowing about it helps understand what's happening. These symbols aren't references to specific things; they are instructions to navigate the history graph. That's why they are called operators: unlike commits which are a state, these represent an action.

If you look at the history like a map, you might read HEAD~1^2~3 like this:

  1. Starting at your current location (HEAD).
  2. Go back one intersection (commit).
  3. Take the other road that merged into this one.
  4. Go straight three more times.

Mermaid git graph with arrows describing the multiple traversal steps

Commit-ish

Not only can you perform these traversal actions from special references like HEAD and branches, you can perform them from any commit or commit-ish reference like a tag.

# Two steps back from a specific commit
git checkout 0269f84~2

# Get "their" branch from a merge commit tagged as v2.1.0
git checkout v2.1.0^2
Enter fullscreen mode Exit fullscreen mode

Range References

Double Dot Notation (..)

The double dot notation helps you see differences between two references:

  • main..develop: Shows commits that are in develop but not in main
  • HEAD..origin/main: Shows what commits will be pulled from origin/main

This is one of the most commonly used notations for comparing branches:

# List commits in develop that aren't yet in main
git log main..develop

# See what changes will be pushed to origin
git diff HEAD..origin/main
Enter fullscreen mode Exit fullscreen mode

You can use this style to output a patch file for sharing with others:

git diff HEAD..origin/main > activeWork.patch
Enter fullscreen mode Exit fullscreen mode

Triple Dot Notation (...)

The triple dot notation shows commits that are in either reference but not in both:

  • main...develop: Shows commits unique to both main and develop
  • HEAD...FETCH_HEAD: Shows changes between local branch and what was just fetched

This is useful for seeing the complete picture of how two branches diverged:

# See all commits that are in main or develop, but not in both
git log main...develop

# Visualize the divergence between branches
git log --graph --oneline --all main...develop
Enter fullscreen mode Exit fullscreen mode

The @ Syntax

The @ syntax provides a way to reference points in the reflog. Unlike the regular git log, git reflog keeps track of activity on your particular clone of the repo, including branch switches. Navigating through this will be pretty rare, but can be useful for seeing what you've been up to.

  • main@{yesterday}: Where the tip of main was yesterday
  • HEAD@{2}: Where HEAD was two moves ago
  • HEAD@{upstream} or @{u}: The upstream branch of the current branch
# See what the branch looked like yesterday
git show main@{yesterday}

# Compare with what you had yesterday
git diff main@{yesterday}

# Find commit where you accidentally removed code (from reflog)
git log -p HEAD@{2}
Enter fullscreen mode Exit fullscreen mode

Commit Ranges for Operations

These symbols aren't just for viewing history; they're also used for operations:

  • git rebase -i HEAD~3: Interactively rebase from three commits back
  • git cherry-pick main..feature: Apply all commits from main to feature onto the current branch
  • git revert HEAD~5..HEAD~2: Revert a range of commits

When performing operations with ranges, you can use git log --oneline with your range to verify which commits will be affected.

Conclusion

Git's special symbols and revision references let you control how you navigate your repository's history. The syntax might seem arcane at first, but the operators let you navigate, compare, and manipulate your history with precision.

Whether you're analyzing branches, cherry-picking changes, or fixing mistakes in your commits, knowing the symbols puts you in charge of your version control tools.


Author's note: The first draft of this article was produced with the assistance of generative AI.

git commit -am 'Symbols'
git push
Enter fullscreen mode Exit fullscreen mode

Top comments (0)