Advanced Git Features
Now that you've mastered the basics of Git, it's time to explore some of its more powerful features. These advanced techniques will give you greater control over your repository's history and workflow. Don't worry if these concepts seem complex at first – we'll break them down step by step.
Rebasing
Rebasing is a way to integrate changes from one branch into another by moving or combining a sequence of commits to a new base commit. In simpler terms, it's like "replaying" your changes on top of other changes.
To understand rebasing, let's first recall how merging works:
When you merge one branch into another, Git creates a new "merge commit" that combines the changes from both branches. This preserves the entire history of your project, including all the branches and merges.
Rebasing, on the other hand, moves your branch's commits to the tip of the other branch, creating a linear history. It's like saying, "Apply my changes as if I had started working from this newer point."
Why Use Rebasing?
Cleaner Project History: Rebasing can create a cleaner, more linear project history. Instead of lots of merge commits, you get a straight line of commits.
Easier to Understand: A linear history can be easier to read and understand, especially for newcomers to your project.
Easier Conflict Resolution: By rebasing frequently, you deal with conflicts in smaller chunks, which can be easier to manage.
Cleaner Feature Branches: Before merging a feature branch into main, you can use rebasing to clean up your commits, making the eventual merge cleaner.
Basic Rebasing Example
Let's say you have a feature branch that's based off the main branch:
Here, commits A, B, and C are unique to your feature branch, while F and G are new commits on the main branch that happened after you created your feature branch.
To rebase the feature branch onto main:
Checkout the feature branch:
git checkout feature
Rebase onto main:
git rebase main
After rebasing, your history will look like this:
Notice that the feature branch commits (A, B, C) are now at the main branch's tip and have new commit hashes (A', B', C'). Rebasing creates new commits with the same changes but different parent commits.
Interactive Rebasing
Interactive rebasing is a more powerful form of rebasing that allows you to modify commits as they're being replayed. This is useful for cleaning up your commit history before merging a feature branch.
It's not something I think you'll use often, but I wanted to explore it in case you ever encounter a scenario in which you need it to rectify a complex commit history.
To start an interactive rebase you select how many commits you want to work from (in the following, we will choose three):
git rebase -i HEAD~3
This opens an editor where you can choose actions for each of the last three commits:
pick
: Use the commit as isreword
: Use the commit, but edit the commit messageedit
: Use the commit, but stop for amendingsquash
: Use the commit, but meld it into the previous commitfixup
: Like squash, but discard the commit's log messagedrop
: Remove the commit
This allows you to do things like:
- Combine multiple small commits into a larger one
- Split a large commit into smaller ones
- Remove or edit commits
- Reorder commits
When to Use Rebase
- To integrate the latest changes from the main branch into your feature branch
- To clean up your commit history before merging a feature branch
- To maintain a linear project history
- Some companies and Open Source projects use rebasing as the default way to manage their code for this linear project history.
Caution with Rebase
Never rebase commits pushed to a public repository and on which others may have based work. Rebasing rewrites history, which can cause problems for other contributors if they've already based work on the original commits.
Cherry-picking
Cherry-picking allows you to apply the changes introduced by some existing commits. It's useful when you want to pick specific commits from one branch and use them to another.
To cherry-pick a commit:
git cherry-pick <commit-hash>
This creates a new commit on your current branch with the changes from the specified commit.
When to Use Cherry-pick
- To apply a bug fix to multiple branches
- To recover specific changes from a branch that's being discarded
- To apply only specific features from one branch to another
Stashing Changes
Git stash is a feature that allows you to temporarily store modified, tracked files to clean your working directory. It's like putting your changes in a drawer for safekeeping, enabling you to switch contexts quickly without committing incomplete work.
Why Use Git Stash?
Imagine you're working on a feature, but it's not quite ready to commit. Suddenly, switching to a different branch to fix an urgent bug would be best. You don't want to commit your half-done work, but you can't bring those changes to the other branch either. This is where git stash
comes to the rescue.
Basic Stashing
To stash your changes:
git stash
This command will take all your modified tracked files and save them on a stack of unfinished changes you can reapply at any time.
After stashing, your working directory will be clean, matching the state of your last commit.
To bring your stashed changes back, you can apply the most recent stash and remove it from the stash list:
git stash pop
If you want to apply the stash but keep it in the stash list (in case you want to apply it to multiple branches), use:
git stash apply
Named Stashes
By default, stashes are identified only by a number and the branch and commit from which they were created. For better organization, especially if you plan to stash multiple times, you can create named stashes:
git stash save "WIP: refactoring authentication"
This allows you to identify what each stash contains quickly.
Viewing Stashes
To see all your stashes:
git stash list
This will show you something like:
stash@{0}: WIP on feature-branch: 1234abc Your last commit message stash@{1}: On main: WIP: refactoring authentication
Applying Specific Stashes
If you have multiple stashes, you can choose which one to apply:
git stash apply stash@{2}
Creating a Branch from a Stash
If you've stashed some changes and want to work on them in a new branch:
git stash branch new-branch-name stash@{1}
This creates a new branch based on the commit you were on when you stashed your changes, applies the stashed changes, and then drops the stash if it applied successfully.
Cleaning Up Stashes
To remove a single stash:
git stash drop stash@{1}
To remove all stashes:
git stash clear
When to Use Stash
Switching contexts: When you need to switch branches but aren't ready to commit your current work.
Pulling changes: To quickly hide local changes when pulling from a remote repository. You can stash, pull, and then reapply your changes.
Experimenting: To save temporary changes you're unsure you want to commit. If the experiment doesn't work out, you can simply discard the stash.
Best Practices for Stashing
Use descriptive stash messages: This helps you remember what each stash contains.
Don't keep stashes for too long: Stashes are meant to be temporary. Try to apply or drop them as soon as possible.
Be careful when applying stashes: If your working directory has changed significantly since you created the stash, you might encounter conflicts.
While stashing is a powerful tool, it's not a substitute for proper branching and committing. Use it temporarily to store changes, not as a permanent solution for managing different sets of changes.
Other Advanced Features
Let's briefly look at some more advanced features that might come in useful. This will introduce some of these concepts so you can research in more depth if you think it's a tool you'll need.
If you are already feeling overwhelmed, ignore the rest of this section and revisit it when you've digested everything else.
Reflog
The reflog is a log of all reference updates in your repository. It helps recover lost commits:
git reflog
Submodules
Submodules allow you to keep a Git repository as a subdirectory of another Git repository:
git submodule add <repository-url> <path>
Git Hooks
Hooks are scripts that Git executes before or after events such as: commit, push, and receive. They're useful for automating or customizing your Git workflow.
Git Bisect
Bisect helps you find the commit that introduced a bug by using a binary search algorithm:
git bisect start git bisect bad # Current version is bad git bisect good <commit-hash> # Last known good version
While all these features require careful use, learning them (or bookmarking them for reference) will significantly give you some confidence in handling complex version control scenarios. In the
The following section will explore using Git in various development environments and with different tools.