Advanced Git Features


In our journey through Git so far, we've covered the fundamentals that form the backbone of any developer's daily workflow. We've explored how to initialize repositories, create and manage branches, collaborate with teammates through remote repositories, and implement effective merging strategies. Now it's time to elevate your Git expertise to new heights by diving into its advanced features.
This article will introduce you to powerful tools and techniques that extend beyond basic version control. These advanced capabilities can help you manage complex projects more efficiently, recover from sticky situations, and customize Git to fit your specific workflow needs. Let's unlock the full potential of Git's sophisticated toolset.
Stashing Changes: A Safe Place for Work in Progress
Have you ever been in the middle of implementing a feature when an urgent bug report comes in? You're not ready to commit your half-finished work, but you need a clean working directory to fix the bug. Git stash comes to the rescue.
Temporarily Storing Changes
The git stash
command takes your uncommitted changes (both staged and unstaged) and saves them on a stack of unfinished changes that you can reapply at any time.
# Stash your changes
git stash save "Work in progress for feature X"
After running this command, your working directory will be clean, matching the state of the HEAD commit. You're now free to switch branches, fix bugs, or perform other operations without losing your work.
Managing Multiple Stashes
Each time you run git stash
, Git creates a new stash entry and adds it to your stash stack. You can view all your stashes with:
git stash list
This will display something like:
stash@{0}: On feature-branch: Work in progress for feature X
stash@{1}: On main: Quick fix for login page
You can reference specific stashes using their index, for example, stash@{1}
.
Applying Stashed Changes
When you're ready to continue working on your stashed changes, you have two options:
# Apply the most recent stash but keep it in the stash list
git stash apply
# Apply a specific stash
git stash apply stash@{1}
# Apply the most recent stash and remove it from the stash list
git stash pop
If you no longer need a particular stash, you can remove it:
git stash drop stash@{1}
Or clear all stashes at once:
git stash clear
Stashing is an invaluable tool for context switching without committing incomplete work, giving you the flexibility to juggle multiple tasks efficiently.
Interactive Operations: Fine-Grained Control
Git offers interactive modes for several operations, giving you precise control over what gets included in your commits and how your history is shaped.
Interactive Staging
Rather than staging entire files with git add
, interactive staging allows you to select specific portions of files to include in your next commit:
git add -i
This opens an interactive menu where you can:
Stage specific files
Unstage files
View differences
Stage parts of files (patches)
The patch option is particularly powerful, allowing you to stage or unstage specific lines within a file. This promotes cleaner, more focused commits that represent logical units of work.
Interactive Rebase
Interactive rebase is one of Git's most powerful features, allowing you to modify your commit history:
git rebase -i HEAD~5
This command opens an editor showing the last five commits, where you can:
Reorder commits by changing their order in the list
Edit commit messages
Combine multiple commits into one (squash)
Split a commit into multiple commits
Delete commits entirely
Pause during rebasing to modify files
Interactive rebase is perfect for cleaning up your commit history before pushing to a shared repository, ensuring your changes tell a coherent story.
Cherry-Picking Commits
Cherry-picking allows you to apply the changes from a specific commit to your current branch:
git cherry-pick 7300a6f
This is useful when you want to:
Apply a bug fix from another branch without merging the entire branch
Select specific features from a development branch to release
Restore changes that were accidentally reverted
History Rewriting: Perfecting Your Timeline
Git gives you the power to rewrite history, which can be both a blessing and a curse. Used responsibly, these techniques help maintain a clean, readable project history.
Amending Commits
Forgot to include a file in your last commit? Made a typo in your commit message? The --amend
option lets you modify your most recent commit:
# Add forgotten files
git add forgotten-file.js
# Amend the previous commit
git commit --amend -m "Correct commit message"
This replaces the previous commit with a new one, combining the previously committed changes with your staged changes.
Squashing Commits
While interactive rebase offers a way to squash commits, you might also use the --squash
option when merging:
git merge --squash feature-branch
This takes all the changes from the feature branch and stages them, allowing you to create a single commit representing the entire feature.
Force Pushing and Its Dangers
After rewriting history, you may need to force push your changes:
git push --force
But beware: force pushing rewrites the remote history, which can cause problems for collaborators who have based their work on the old history. Instead, consider using:
git push --force-with-lease
This safer alternative checks if the remote branch has been updated since you last fetched, preventing you from overwriting others' work.
Git Hooks: Automating Your Workflow
Git hooks are scripts that Git executes before or after certain events, such as committing, pushing, or receiving changes.
Introduction to Git Hooks
Hooks reside in the .git/hooks
directory of your repository. Sample hooks are provided with the .sample
extension. To activate a hook, rename it to remove the extension and make it executable.
Pre-commit and Post-commit Hooks
A pre-commit hook runs before a commit is created, making it perfect for:
Running tests
Checking code style
Preventing commits with debugging code
Validating commit messages
Here's a simple pre-commit hook that checks for debugging statements:
#!/bin/sh
if git diff --cached | grep -E 'console\.log|debugger';
then
echo "Error: Debugging statements detected"
exit 1
fi
Post-commit hooks run after a commit is created, useful for:
Sending notifications
Triggering continuous integration
Updating documentation
Creating Custom Hooks
To create a custom hook:
Navigate to the
.git/hooks
directoryCreate a new file with the appropriate name (e.g.,
pre-commit
)Make it executable:
chmod +x pre-commit
Write your script in any language, just make sure the shebang line points to the correct interpreter
Submodules and Subtrees: Managing Project Dependencies
For projects that depend on external code, Git provides two approaches: submodules and subtrees.
Submodules
Submodules allow you to keep a Git repository as a subdirectory of another Git repository:
# Add a submodule
git submodule add https://github.com/example/library lib
# Initialize and update submodules (after cloning a repository with submodules)
git submodule init
git submodule update
Submodules store a reference to a specific commit in the external repository, ensuring your project uses a consistent version of dependencies.
Subtrees
Git subtrees merge the history of one repository into a subdirectory of another:
# Add a subtree
git subtree add --prefix=lib https://github.com/example/library main --squash
# Update a subtree
git subtree pull --prefix=lib https://github.com/example/library main --squash
Unlike submodules, subtrees include the entire code directly in your repository, making it easier for collaborators to work with the code without additional steps.
Choosing Between Submodules and Subtrees
Choose submodules when:
You need precise version control over dependencies
The dependency is large and you don't want to include its entire history
Multiple projects share the same dependency
Choose subtrees when:
You want to simplify the workflow for collaborators
You need to modify the dependent code frequently
You want to maintain a standalone repository
Advanced Logging and Diffing
Git's logging and diffing capabilities extend far beyond the basics, offering powerful ways to visualize and understand project history.
Custom Log Formats
Customize your log output to show exactly what you need:
git log --pretty=format:"%h - %an, %ar : %s" --graph
This command shows a graphical representation of your commit history with abbreviated hashes, author names, relative dates, and commit messages.
You can create aliases for your favorite log formats:
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
Now you can simply use git lg
for a visually appealing log.
Advanced Diff Operations
Compare branches, tags, or specific commits:
# Compare two branches
git diff main..feature-branch
# Show changes in a branch that will be applied when merging
git diff main...feature-branch
# Show changes across multiple commits
git diff HEAD~3..HEAD
# Show only the names of changed files
git diff --name-only main..feature-branch
Visualizing History
For complex projects with many branches and merges, visualizing the history can be enlightening:
git log --graph --oneline --all
This command shows a ASCII graph of the commit history across all branches. For a more sophisticated visualization, consider using a GUI tool like GitKraken, Sourcetree, or GitHub Desktop.
Conclusion: Mastering Git's Advanced Features
The advanced features we've explored in this article represent Git's true power. They allow you to:
Manage work in progress with stashing
Exercise fine-grained control over commits with interactive operations
Maintain a clean, coherent history with history rewriting
Automate workflows with hooks
Manage dependencies with submodules and subtrees
Gain deep insights with advanced logging and diffing
As with any powerful tool, these features should be used responsibly, especially when collaborating with others. History rewriting, in particular, can cause problems if misused in shared repositories.
In our next article, we'll explore Git best practices and workflows, showing you how to integrate these advanced features into efficient, team-friendly development processes that scale with your projects.
Subscribe to my newsletter
Read articles from Mikey Nichols directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Mikey Nichols
Mikey Nichols
I am an aspiring web developer on a mission to kick down the door into tech. Join me as I take the essential steps toward this goal and hopefully inspire others to do the same!