- 1. Getting geeky with Git #1. Remotes and upstream branches
- 2. Getting geeky with Git #2. Building blocks of a commit
- 3. Getting geeky with Git #3. The branch is a reference
- 4. Getting geeky with Git #4. Fast-forward merge and merge strategies
- 5. Getting geeky with Git #5. Improving merge workflow with rebase
- 6. Getting geeky with Git #6. Interactive Rebase
- 7. Getting geeky with Git #7. Cherry Pick with Reflog
- 8. Getting geeky with Git #8. Improving our debugging flow with Bisect and Worktree
- 9. Getting geeky with Git #9. Understanding the revert feature
- 10. Getting geeky with Git #10. The overview of Git hooks with Husky
- 11. Getting geeky with Git #11. Keeping our Git history clean with fixup commits
So far, we’ve covered a few different tools useful when rewriting the Git history, such as merging and rebasing. In this article, we go more in-depth and try cherry-picking. While doing so, we also learn how the reflog functionality works.
Git Cherry Pick
When we rebase or merge, we usually get all of the content of a branch. This might not always be the desired outcome. For example, there might be a case when we need to patch the production with some content of another branch. Although we would prefer to merge the work more gracefully, some situations might require us to apply some hotfixes. It is always a good thing to have the above possibility.
With git cherry-pick, we can choose a commit from one branch and apply it to another one. To do so, we need to provide Git with the hash of a commit.
If you want to know how Git creates hashes from commits, check out Getting geeky with Git #2. Building blocks of a commit
To get the most recent commit, let’s use git log -n 1.
1 2 |
git checkout feature-one git log -n 1 |
commit 9aa3824e9ea1079ab11c1403e1e3bce54bc20010 (HEAD -> feature-one)
Author: marcin <wanago.marcin@gmail.com>
Date: Sun Sep 13 18:09:36 2020 +0200Feature one
We don’t need to provide a full hash, since it is a lot of characters. Usually, seven or eight characters would be enough to identify a commit.
1 2 3 |
git checkout master git cherry-pick 9aa3824 git log -n 1 |
commit a20e15ed74ae902fa2ff9219b34a8deb7a849408 (HEAD -> master)
Author: marcin <wanago.marcin@gmail.com>
Date: Sun Sep 13 18:09:36 2020 +0200Feature one
The drawbacks of using cherry-pick
Doing the above moved the changes associated with the chosen commit into the master branch. We can notice a significant thing above. When we cherry-picked the above commit, Git created an entirely new object with a new SHA identifier. Let’s look even closer into the above commits.
1 |
git cat-file -p 9aa3824 |
tree 8b928b7dd43ac177f8afcac35bdf73be5d229db6
parent e8c12bc611b2ec3da76c9486513aade6bfec2855
author marcin <wanago.marcin@gmail.com> 1600013376 +0200
committer marcin <wanago.marcin@gmail.com> 1600013376 +0200Feature one
1 |
git cat-file -p a20e15e |
tree 8b928b7dd43ac177f8afcac35bdf73be5d229db6
parent e8c12bc611b2ec3da76c9486513aade6bfec2855
author marcin <wanago.marcin@gmail.com> 1600013376 +0200
committer marcin <wanago.marcin@gmail.com> 1600013957 +0200Feature one
Even though the above commits have the same contents when it comes to the changes, the commit object differs a bit.
When we look into the committer part of the above commits, we can spot a small difference. At the end of the line, there is a Unix timestamp with a timezone. Since we’ve performed the cherry-pick after the actual commit, it differs.
Since Git takes the timezone into account when generating the SHA hash, the newly generated hash is different than the original. All of the above might lead to having duplicates of commits. This does not help in keeping our history straightforward.
Making our cherry-picks more apparent
To deal with the above issue, we can use the -x argument. Doing so appends an additional line at the end of our commit message.
1 2 |
git cherry-pick -x 9aa3824 git log -n 1 |
commit 57d66c36d8788dcee44a2addfb734fe5790afa92 (HEAD -> master)
Author: marcin <wanago.marcin@gmail.com>
Date: Sun Sep 13 18:09:36 2020 +0200Feature one
(cherry picked from commit 9aa3824e9ea1079ab11c1403e1e3bce54bc20010)
Another solution would be to use the --no-commit option.
1 2 |
git cherry-pick --no-commit 9aa3824 git status |
On branch master
Changes to be committed:
(use “git restore –staged <file>…” to unstage)
modified: index.js
Doing that prevents Git from automatically committing the changes. Now, we can create a commit ourselves with a commit message of our choosing. It might prove to be especially useful if we want to cherry-pick multiple commits at once.
Git Reflog
In this series, we’ve often used git log. It shows us the current HEAD and its ancestors.
In the third part of this series we’ve learned that HEAD is a pointer to a commit that our repository is checked out on
With git reflog, we don’t traverse through the HEAD ancestors. It is a list that we can view to see the commits that the HEAD pointed to. Git stores it locally, so it is not included when pushing and pulling from a remote repository.
The expiry time for the reflog entries can be specified through the reflogExpire option in the configuration. It defaults to 90 days. After this period, git removes entries that are not reachable otherwise
The most basic usage is through running git reflog:
e8c12bc (HEAD -> master) HEAD@{0}: reset: moving to HEAD~1
57d66c3 HEAD@{1}: cherry-pick: Feature one
e8c12bc (HEAD -> master) HEAD@{2}: reset: moving to HEAD
e8c12bc (HEAD -> master) HEAD@{3}: reset: moving to HEAD~1
a20e15e HEAD@{4}: cherry-pick: Feature one
e8c12bc (HEAD -> master) HEAD@{5}: checkout: moving from feature-one to master
9aa3824 (feature-one) HEAD@{6}: commit: Feature one
e8c12bc (HEAD -> master) HEAD@{7}: checkout: moving from master to feature-one
e8c12bc (HEAD -> master) HEAD@{8}: commit (initial): Initial commit
The above is a shortcut for git reflog show HEAD. We could also try running git reflog show feature-one, for example.
We can take advantage of that and see commits that we would not be able to otherwise. Imagine that we would have accidentally removed the feature-one branch.
1 |
git branch -D feature-one |
Even though that happened, we can still get the hash of the commit that we might want to cherry-pick.
1 2 |
git reflog git cherry-pick 9aa3824 |
The above might help us in some nasty situations. Git even keeps a record of the work that we stash. We can access it through git reflog stash.
Specifying the requested logs
When looking through the reference logs, we can be more specific. For example, we can run git reflog show HEAD@{6} to see where the HEAD pointed six moves ago.
9aa3824 HEAD@{6}: commit: Feature one
e8c12bc (HEAD -> master) HEAD@{7}: checkout: moving from master to feature-one
e8c12bc (HEAD -> master) HEAD@{8}: commit (initial): Initial commit
All entries in the reflog have timestamps attached. Thanks to that, we can filter them by time.
1 |
git reflog show HEAD@{120.minutes.ago} |
a20e15e HEAD@{Sun Sep 13 18:19:17 2020 +0200}: cherry-pick: Feature one
e8c12bc (HEAD -> master) HEAD@{Sun Sep 13 18:18:50 2020 +0200}: checkout: moving from feature-one to master
9aa3824 HEAD@{Sun Sep 13 18:09:36 2020 +0200}: commit: Feature one
e8c12bc (HEAD -> master) HEAD@{Sun Sep 13 18:08:49 2020 +0200}: checkout: moving from master to feature-one
e8c12bc (HEAD -> master) HEAD@{Sun Sep 13 18:07:25 2020 +0200}: commit (initial): Initial commit
There are more ways to specify the time when using the reflog. For a full list, check out the documentation.
Summary
Today we’ve learned a few new tools that might come in handy in various situations. Although we need to remember that using cherry-pick might have some negative effect on the readability of our history. When used wisely, it can become a useful command – especially when combined with the use of the reflog. Surely, it is good to be aware of both when the right moment comes.