When working with large projects, it can be difficult to manage various branches and changes/commits you make to your repository. For purposes such as transferring bug fixes from one branch to another or creating new branches from commits, Git provides a variety of features. This includes the ability to selectively choose and apply specific changes. You will explore two important commands for achieving this and how to use them effectively.
Cherry-picking
Cherry-picking allows developers to apply a specific commit from one branch to another. It allows you to select individual changes and apply them to a different branch without merging the entire branch history.
To cherry-pick a commit, you need to identify the commit hash or reference and use the command. Here, you do not need to write the full commit hash, the first 5-7 characters are sufficient:
git cherry-pick <commit-hash>
You can also cherry-pick a range of commits by specifying the start and end commit hashes, like
git cherry-pick <start-commit-hash> ... <end-commit-hash>
Git will apply the changes made in the selected commit(s) to the current branch, creating new commits if necessary. Note that cherry-picking only copies the changes introduced by the selected commit(s) and does not transfer the commit history or any related metadata.
Let's use cherry-pick to apply commit r from the hotfix branch to the feature branch. First, switch to the feature branch.
git switch featureThen use the following command:
git cherry-pick rGit will commit r to the feature branch.
Uses of cherry-picking
Cherry-picking is particularly useful for the following cases:
Bug fixes: Cherry-picking allows you to apply specific bug fixes from one branch to another without merging the entire branch. This is particularly useful when you want to quickly address critical issues in a stable branch without introducing unrelated changes.
Feature integration: Suppose you are working on a feature branch and have made multiple commits. You can cherry-pick only the relevant commits into the main branch, keeping the feature branch isolated. This selective integration helps maintain a clean commit history and avoids merging unnecessary changes.
Backporting: Cherry-picking enables you to backport changes from a newer branch to an older branch. For example, if you have a bug fix in a newer version of your software, you can cherry-pick the commit onto an older version's branch to ensure the fix is applied.
Experimentation: Cherry-picking allows you to test and apply experimental changes to different branches selectively. You can cherry-pick commits to branches dedicated to testing, allowing you to assess the impact of specific changes before fully integrating them into your main codebase.
However, there is one drawback of cherry-picking. It can cause duplicate commits. Because of this, cherry-picking might not be the best tool for every scenario and sometimes it is beneficial to use traditional merges.
Checkout
Checkout is a versatile Git command that allows you to perform various operations related to branches, commits, and files. It serves multiple purposes, including switching to a different branch, creating a new branch, and discarding changes made to files. While cherry-picking deals with specific commits, checkout focuses on the entire state of a branch or file at a given point in time. The checkout command modifies the files in your working directory to match the specified branch or commit.
The checkout option is useful for the following cases:
Branch switching: The
git checkoutcommand is primarily used to switch between branches in Git. It updates your working directory to reflect the latest commit on the target branch, allowing you to continue working on that branch.Inspecting specific commits: With
git checkout, you can explore the state of your repository at a particular commit. This is useful for reviewing the changes made in the past, comparing different versions, or identifying the source of a bug or regression.File-level recovery: If you accidentally modify or delete a file, you can use
git checkoutto retrieve an earlier version of that file from a specific commit. This feature acts as a safety net, preventing permanent loss of important code.Creating new branches: The
-boption ingit checkoutallows you to create a new branch from an existing commit. This is beneficial when you want to branch off from a specific point in history, such as for creating a hotfix branch or experimenting with a new feature.
On top of all these, you can also use this command to discard local changes and restore branches to the previous version, check out remote branches and tags, and check out branches using their abbreviated names. But for now, let's look at only a few uses in detail.
Switching branches
Branch switching is a common use case of git checkout. To navigate between branches within a repository, the git checkout command updates the working directory and the HEAD pointer to reflect the state of the target branch. The HEAD is a pointer in Git that keeps track of the last commit in the active branch.
To switch from one branch to another, use the git checkout command followed by the branch name:
git checkout <branch-name>Git will simply move the HEAD pointer to the latest commit on that branch. Let's suppose you are currently on the main branch and want to switch to the hotfix branch to quickly fix a critical issue. You can use git checkout hotfix to switch branches.
Git also provides shorthand features to simplify branch navigation These are:
git checkout -and;git checkout @{-N}.
The git checkout - command allows you to quickly switch to the last branch you were on. This allows you to easily toggle between the current branch and the previous one. For example, if you switch from main to feature-branch, running git checkout - will take you back to main, and running it again will return you to feature-branch.
On the other hand, git checkout @{-N} provides more flexibility by letting you navigate deeper in your branch checkout history. The notation @{-N} refers to the Nth branch you were on. For instance, the @{-1} branch refers to the previous branch (equivalent to git checkout -), @{-2} refers to the branch before that, and so on. The history of branches is tracked in the reflog, which you can view with git reflog to determine the order of previously checked-out branches.
Creating branches
The git checkout command, combined with the -b flag simplifies the process of creating a new branch. This also allows you to branch off from an existing commit or branch to work on new features, bug fixes, or experiments. You can create new branches using:
git checkout -b <new-branch-name> <starting-point>For example, to create a new branch named feature-branch from an existing branch main, execute the following command:
git checkout -b feature-branch mainThis command creates a new branch called feature-branch starting from the main branch. Git will automatically switch to the newly created branch.
When collaborating on a project, branches could be hosted on platforms like GitHub. To work with one, start by fetching its contents using git fetch --all. This command updates your local references for all remote branches. In modern Git versions, you can directly check out a remote branch with git checkout <remotebranch>. For older Git versions, you need to create a local branch that tracks the remote branch using git checkout -b <remotebranch> origin/<remotebranch>.
Alternatively, you can create a local branch and synchronize it with the remote branch's latest commit using git reset --hard origin/<branchname>. This is helpful when you want a fresh start with the remote branch's state.
Checking out files
You can use git checkout to retrieve previous versions of files from specific commits. This can be useful when you want to view or restore a previous version of a particular file without affecting the entire repository. The syntax for checkout files is:
git checkout <commit-hash> -- <file-path>Here, commit-hash helps determine which commit the file should be taken from.
Suppose you want to check out script.js from commit ca82a6dff817ec66f44342007202690a93763949. You can use the following command:
git checkout ca82a6dff817ec66f44342007202690a93763949 -- src/buttons/script.jsRemember to include the path to the specific file. Running the command will replace the current version of script.js in your working directory with the version from the specified commit.
Detached HEAD state
When you check out a specific commit by its hash, tag, or use --detach explicitly, Git detaches HEAD because you are no longer on a branch. This is known as a detached HEAD state because HEAD points directly to a specific commit instead of a branch. Normally, HEAD is attached to a branch, so when you make new commits, they are added to that branch. In the detached HEAD state, you can view or modify files, but new commits in this state will not belong to any branch unless you explicitly create one.
You can check if you are in a detached HEAD state by running the git status command. If you're in a detached HEAD state, Git will explicitly tell you:
HEAD detached at refs/heads/mainThe detached HEAD state is very useful for:
Exploring the state of the repository at a specific commit or tag without affecting any branches;
Test changes or building the project at a specific commit without worrying about making changes to a branch;
To exit this state, simply checkout a branch.
Conclusion
Cherry-picking and checkout options are two useful Git features that can help developers manage their source code effectively. By utilizing cherry-picking and checkout options effectively, developers can have fine-grained control over integrating changes, managing branches, and exploring the history of their Git repositories. Let's review some important commands:
git cherry-pick commit-hashallows you to apply changes from the specified commit to the current branch;git checkout branch-namelets you switch between branches;git checkout commit-hash -- file-pathcan help you retrieve previous versions of files from the specified commits;git checkout -andgit checkout @{-N}makes it easier to navigate through your branch history;-bflag allows you to create a new branch based on an existing one.
These features contribute to maintaining a well-structured and organized version control workflow. By using these features effectively, developers can work more efficiently and make the most of Git's capabilities.