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.
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.
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.
To better understand how HEAD
works, let's use a simple example. Imagine you're documenting the development of the telephone:
- Commit 1: The first designs of the telephone are drafted.
- Commit 2: A prototype is built.
- Commit 3: The first test of the prototype takes place.
- 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.
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:
-
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. -
Use the following command to check out Commit 2:
Bash1git 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.
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.
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:Bash1git checkout main
This will reattach
HEAD
tomain
, 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:
Bash1git checkout -
This command acts like a “back” button, taking
HEAD
to the commit or branch you had checked out before entering the detachedHEAD
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:
Bash1git checkout -b new-branch-name
This will attach your current work to
new-branch-name
, preserving any changes.
Imagine you checked out Commit 2 (Prototype built
). If you’re done reviewing, simply reattach HEAD
to main
:
Bash1git checkout main
Or, if you want to save any changes made to the prototype, create a new branch to preserve your work:
Bash1git checkout -b prototype-review
Now HEAD
is attached to prototype-review
, and your changes are saved.
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 currentHEAD
.HEAD~2
goes back two commits from the currentHEAD
.
Using the ~
symbol simplifies transitioning between recent commits, which can be useful when reviewing recent changes:
Bash1# 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.
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.