The previous module on rebasing taught us how to move commits around and perform some basic edits while doing so, but now we’re going to really get our hands dirty. We’ll learn how to split up commits, revive lost snapshots, and completely rewrite a repository’s history to our exact specifications.
Hopefully, this module will get you much more comfortable with the core Git components, as we’ll be inspecting and editing the internal makeup of our project.
DOWNLOAD THE REPOSITORY FOR THIS MODULE
If you’ve been following along from the previous module, you already have everything you need. Otherwise, download the zipped Git repository from the above link, uncompress it, and you’re good to go.
Create the Red Page
First, let’s create a new branch and add a few more HTML pages.
git checkout -b new-pages git branch
Notice that we created a new branch and checked it out in a single step by passing the -b flag to the git checkout command.
Next, create the file red.html
and add the following content:
We’ll hold off on committing this page for the moment.
Create a file called yellow.html
, which should look like the following.
Link and Commit the New Pages
Next, we’ll link both new pages to the home page. Add the following items to the “Navigation” section in index.html
:
git add red.html yellow.html index.html git status git commit -m "Add new HTML pages"
This is an example of a bad commit. It performed multiple, unrelated tasks, and it has a relatively generic commit message. Thus far, we haven’t really specified when it’s appropriate to commit changes, but the general rules are essentially the same as for branch creation:
This will ensure that your project has a meaningful commit history, which gives you the ability to see exactly when and where a feature was added or a piece of functionality was broken. However, in practice, you’ll often wind up committing several changes in a single snapshot, since you won’t always know what constitutes a “well-defined” addition as you’re developing a project. Fortunately, Git lets us go back and fix up these problem commits after the fact.
Let’s create one more page before splitting that “bad” commit: Add the following HTML to a file called green.html.
Add a link to index.html
in the “Navigation” section:
git add green.html index.html git status git commit -m "Add green page"
The commits introduced in our new-pages branch are:
4c3027c Add green page db96c72 Add new HTML pages
But, we want these commits to look more like:
4c3027c Add green page 9b1a64f Add yellow page 77a1cf1 Add red page
To achieve this, we can use the same interactive rebasing method covered in the previous module, only this time we’ll actually create commits in the middle of the rebasing procedure.
git rebase -i master
Change the rebase listing to the following, then save the file and exit the editor to begin the rebase.
edit db96c72 Add new HTML pages pick 4c3027c Add green page
Related Page:: Learn About Git Rebase
First, let’s take a look at where we are with git log –oneline:
db96c72 Add new HTML pages 7070b0e Add link to about section in home page ...
When Git encountered the edit command in the rebase configuration, it stopped to let us edit the commit. As a result, the green page commit doesn’t appear in our history yet. This should be familiar from the previous module. But instead of amending the current commit, we’re going to completely remove it:
git reset --mixed HEAD~1 git log --oneline git status
The git reset command moves the checked out snapshot to a new commit, and the HEAD~1 parameter tells it to reset to the commit that occurs immediately before the current HEAD (likewise, HEAD~2 would refer to second commit before HEAD). In this particular case, HEAD~1 happens to coincide with master. The effect on our repository can be visualized as:
You may recall from UNDOING CHANGES that we used git reset –hard to undo uncommitted changes to our project. The –hard flag told Git to make the working directory look exactly like the most recent commit, giving us the intended effect of removing uncommitted changes.
But, this time we used the –mixed flag to preserve the working directory, which contains the changes we want to separate. That is to say, the HEAD moved, but the working directory remained unchanged. Of course, this results in a repository with uncommitted modifications. We now have the opportunity to add the red.html
and yellow.html
files to distinct commits.
Let’s start with the red page. Since we only want to commit content that involves the red page, we’ll have to manually go in and remove the yellow page’s link from the “Navigation” section. In index.html, change this section to match the following:
Now, we can group the red page’s updates into an independent commit.
git add red.html index.html git status git commit -m "Add red page"
Next up is the yellow page. Go ahead and add it back to the “Navigation” section in index.html:
And again, stage and commit the snapshot.
git add yellow.html index.html git status git commit -m "Add yellow page"
We’ve successfully split up the contents of a single commit into two new snapshots, as shown below.
But, don’t forget that the rebase still needs to transfer the green page:
git rebase --continue
To summarize, we removed the “bad” commit from the current branch with git reset, keeping the contained HTML files intact with the ??mixed flag. Then, we committed them in separate snapshots with the usual git add and git commit commands. The point to remember is that during a rebase you can add, delete, and edit commits to your heart’s content, and the entire result will be moved to the new base.
Next, we’re going to “accidentally” remove the green page commit so we can learn how to retrieve it via Git’s internal repository data.
git reset --hard HEAD~1 git status git log --oneline
This moves the checked-out commit backward by one snapshot, along with the new-pages pointer. Note that git status tells us that we have nothing to commit, since the –hard flag obliterated any changes in the working directory. And of course, the git log output shows that the new-pages branch no longer contains the green commit.
Frequently asked Git Interview Questions
This behavior is slightly different from the reset we used in the interactive rebase: this time the branch moved with the new HEAD. Since we were on (no branch) during the rebase, there was no branch tip to move. However, in general, git reset is used to move branch tips around and optionally alter the working directory via one of its many flags (e.g., –mixed or –hard).
The commit that we removed from the branch is now a dangling commit. Dangling commits are those that cannot be reached from any branch and are thus in danger of being lost forever.
Git uses something called the reflog to record every change you make to your repository. Let’s take a look at what it contains:
git reflog
The resulting output should look something like the following. Depending on your version of Git, the messages might be slightly different. You can press Space to scroll through the content or q to exit.
9b1a64f HEAD@{0}: reset: moving to HEAD~1 002185c HEAD@{1}:
rebase -i (finish): returning to refs/heads/new-pages 002185c HEAD@{2}:
rebase -i (pick): Add green page 9b1a64f HEAD@{3}: commit:
Add yellow page 77a1cf1 HEAD@{4}: commit: Add red page 7070b0e HEAD@{5}:
reset: moving to HEAD~1 ...
The above listing reflects our last few actions. For example, the current HEAD, denoted by HEAD@{0}, resulted from reseting HEAD to HEAD~1. Four actions ago, the yellow page was applied during our rebase, as shown in HEAD@{3}.
The reflog is a chronological listing of our history, without regard for the repository’s branch structure. This lets us find dangling commits that would otherwise be lost from the project history.
At the beginning of each reflog entry, you’ll find a commit ID representing the HEAD after that action. Check out the commit at HEAD@{2}, which should be where the rebase added the green page (change the ID below to the ID from your reflog).
git checkout 002185c
This puts us in a detached HEAD state, which means our HEAD is no longer on the tip of a branch. We’re actually in the opposite situation as we were in Undoing Changes when we checked out a commit before the branch tip. Now, we’re looking at a commit after the tip of the branch, but we still have a detached HEAD:
To turn our dangling commit into a full-fledged branch, all we have to do is create one:
git checkout -b green-page
We now have a branch that can be merged back into the project:
The above diagram makes it easy to see that the green-page branch is an extension of new-pages, but how would we figure this out if we weren’t drawing out the state of our repository every step of the way?
To view the differences between branches, we can use Git’s log-filtering syntax.
git log new-pages..green-page
This will display all commits contained in green-page that aren’t in the new-pages branch. The above command tells us that green-page contains one more snapshot than new-pages: our dangling commit (although, it’s not really dangling anymore since we created a branch for it).
You can also use this syntax to limit the output of git log. For example, to display the last 4 commits on the current branch, you could use:
git log HEAD~4..HEAD
However, this is a bit verbose for such a common task, so Git developers added the -n flag as an easier way to limit output.
git log -n 4
The -n 4 parameter tells Git to show only the last four commits from the current HEAD, making it the equivalent of the HEAD~4..HEAD syntax shown above. Similarly, -n 3, -n 2, and -n 1 would display three, two, and one commit, respectively. This feature becomes very useful once a repository grows beyond one screenful of history.
We’ve revived our lost commit, and now we’re ready to merge everything back into the master branch. Before we do the merge, let’s see exactly what we’re merging:
git checkout master git log HEAD..green-page --stat
The git log HEAD..green-page command shows us only those commits in green-page that aren’t in master (since master is the current branch, we can refer to it as HEAD). The new –stat flag includes information about which files have been changed in each commit. For example, the most recent commit tells us that 14 lines were added to the green.html file and 3 lines were added to index.html:
commit 002185c71e6674915eb75be2afb4ca52c2c7fd1b Author: Ryan Date:
Wed Jan 11 06:49:50 2012 -0600
Add green page green.html | 14 ++++++++++++++ index.html |
3 +++ 2 files changed, 17 insertions(+), 0 deletions(-)
If we didn’t already know what was in this new commit, the log output would tell us which files we needed to look at. But, we authored all of these changes, so we can skip right to the merge.
git merge green-page
The following diagram shows the state of our repository after the merge.
Note that the green-page branch already contains all the history of new-pages, which is why we merged the former instead of the latter. If this wasn’t the case, Git would complain when we try to run the following command.
git branch -d new-pages
We can go ahead and delete the green page branch, too.
git branch -d green-page
Our work-support plans provide precise options as per your project tasks. Whether you are a newbie or an experienced professional seeking assistance in completing project tasks, we are here with the following plans to meet your custom needs:
Name | Dates | |
---|---|---|
Git Training | Nov 19 to Dec 04 | View Details |
Git Training | Nov 23 to Dec 08 | View Details |
Git Training | Nov 26 to Dec 11 | View Details |
Git Training | Nov 30 to Dec 15 | View Details |
Priyanka Vatsa is a Senior Content writer with more than five years’ worth of experience in writing for Mindmajix on various IT platforms such as Palo Alto Networks, Microsoft Dynamics 365, Siebel, CCNA, Git, and Nodejs. She was involved in projects on these technologies in the past, and now, she regularly produces content on them. Reach out to her via LinkedIn and Twitter.