Lesson 1
Navigating Commits and Detached Heads
Introduction

Welcome to the very first lesson of the Undoing Changes and Time Traveling course. In this course, you will explore Git's powerful "time travel" capabilities, which allow you to navigate through your project’s history and make corrections when necessary. This lesson focuses on efficiently managing changes and collaborating on coding projects by exploring ways to check out old commits, manage the detached HEAD state, and reference commits relative to HEAD. These skills are essential for those moments when you need to explore the past and make informed decisions about changes.

Revisiting Commits and the Role of HEAD

As we learned earlier, a commit in Git is a snapshot of your project at a specific point in time, capturing the current state of your files. These commits form a timeline that tracks the evolution of your project.

Let’s now revisit the role of HEAD. HEAD in Git points to your current branch (for example, main or master), and that branch points to the latest commit on that branch. This structure allows Git to keep track of "where you are" in your project.

When you create a new commit, the branch pointer moves to the new commit, and since HEAD is pointing to the branch, it always reflects the latest commit in your branch. In this way, HEAD indirectly tells Git what commit is currently checked out.

The ".git/HEAD" File

Inside your project's .git directory, there's a file called HEAD. This file stores a reference to the branch that HEAD is currently pointing to. For example, it might contain refs/heads/main, indicating that HEAD is pointing to the main branch.

Whenever you switch branches, Git updates the HEAD file to point to the new branch. Similarly, when you make new commits, the branch pointer moves, and since HEAD is tied to the branch, it follows along with the branch’s latest commit.

Example: Tracking the Development of the Telephone

To better understand how HEAD works, let's use a simple example. Imagine you're documenting the development of the telephone:

  1. Commit 1: The first designs of the telephone are drafted.
  2. Commit 2: A prototype is built.
  3. Commit 3: The first test of the prototype takes place.
  4. Commit 4: The final model is demonstrated to the public.

In this scenario, you are working on the main branch, and HEAD is pointing to the main branch, which in turn points to Commit 4, where the telephone was successfully demonstrated. This setup shows that your current position in the project timeline is at Commit 4, the latest point in the branch.

Now, let’s visualize this with the diagram below. The diagram illustrates how HEAD points to the main branch, and how the main branch points to the latest commit in the timeline, which in this case is Commit 4.

Checking Out Old Commits

Now that we know HEAD points to the latest commit on a branch, let’s explore what happens when we want to look at a previous commit. This process is known as checking out an old commit.

Checking out an older commit allows you to view the project exactly as it was at a specific point in time. This can be useful for reviewing past work, debugging, or testing specific versions of your project.

To check out an older commit, you’ll need its unique commit hash. You can find this by using git log --oneline, which shows each commit’s hash and message in a concise format.

Let’s continue with our telephone example:

  1. Run git log --oneline to view your commit history. The output might look like this:

    14f5e6a7 (HEAD -> main) Final model of the telephone demonstrated 22c3d4e5 Prototype tested for the first time 31a2b3c4 Prototype built 40a1b2c3 First designs of the telephone

    Suppose you want to review the project as it was when the prototype was built (Commit 2), which has the hash 1a2b3c4.

    Note: Commit hashes are often long, but Git allows you to use just the first 7 characters (like 1a2b3c4) to uniquely identify the commit.

  2. Use the following command to check out Commit 2:

    Bash
    1git checkout 1a2b3c4

After running this command, HEAD will move to point directly to Commit 2 instead of the latest one on the branch. Now, you’re viewing a “snapshot” of the project exactly as it was when the prototype was built, letting you examine that specific stage in detail.

Visualizing the Checkout Process

To illustrate this process, take a look at the diagram below. Initially, HEAD points to the latest commit on the main branch, which is Commit 4. But when we run git checkout 1a2b3c4, HEAD moves directly to Commit 2 (Prototype built), disconnecting from the branch. Now, HEAD is no longer following the branch—it’s pointing directly to that earlier commit! This situation is known as a "detached HEAD" state.

Uh-oh! Poor Cosmo looks a bit panicked! This might sound tricky, but don’t worry—in the next section, we’ll dive into what this means and show exactly how to handle it, so Cosmo (and you!) can feel confident again.

Reattaching to Detached HEAD

When you check out an older commit, HEAD enters a detached state, pointing directly to that specific commit rather than following a branch. While this lets you view past versions, any new commits made in this state won’t be attached to a branch. To get back on track, you have a few options:

  • Return to your latest branch: To go back to your previous branch (like main), run:

    Bash
    1git checkout main

    This will reattach HEAD to main, putting you back at the latest commit on that branch.

  • Return to the exact commit you were on before the checkout: If you want to go directly to the last commit you were working on, you can use:

    Bash
    1git checkout -

    This command acts like a “back” button, taking HEAD to the commit or branch you had checked out before entering the detached HEAD state.

  • Create a new branch to save any changes in the detached state: If you’ve made changes in the detached state and want to keep them, create a new branch to save your work:

    Bash
    1git checkout -b new-branch-name

    This will attach your current work to new-branch-name, preserving any changes.

Example: Reattaching in the Telephone Project

Imagine you checked out Commit 2 (Prototype built). If you’re done reviewing, simply reattach HEAD to main:

Bash
1git checkout main

Or, if you want to save any changes made to the prototype, create a new branch to preserve your work:

Bash
1git checkout -b prototype-review

Now HEAD is attached to prototype-review, and your changes are saved.

Referencing Commits Relative to HEAD

Git offers a convenient way to reference commits relative to the HEAD using the ~ symbol. This allows for quick navigation through commits without needing specific hashes.

For example:

  • HEAD~1 refers to the commit right before the current HEAD.
  • HEAD~2 goes back two commits from the current HEAD.

Using the ~ symbol simplifies transitioning between recent commits, which can be useful when reviewing recent changes:

Bash
1# Check out the previous commit 2git checkout HEAD~1

These references are particularly handy for quickly identifying where recent changes or errors might have been introduced.

Summary

I hope that both Cosmo and you are now feeling more confident as we explored the concept of time traveling in Git by checking out old commits, dealing with the detached HEAD state, and referencing commits relative to HEAD. These skills enhance your ability to navigate the history of your project and make informed coding decisions. As you move forward, you're encouraged to explore these concepts hands-on in the upcoming practice exercises, solidifying your understanding of Git's powerful capabilities.

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.