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?

  1. 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.

  2. Easier to Understand: A linear history can be easier to read and understand, especially for newcomers to your project.

  3. Easier Conflict Resolution: By rebasing frequently, you deal with conflicts in smaller chunks, which can be easier to manage.

  4. 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:

Visualizing the commit tree with A, B, and C as unique to your feature branch, while F and G are new commits on the main branch that happened after you created your feature 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:

  1. Checkout the feature branch:

    git checkout feature
    
  2. Rebase onto main:

    git rebase main
    

After rebasing, your history will look like this:

In this image showing the branch, you can see 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.

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 is
  • reword: Use the commit, but edit the commit message
  • edit: Use the commit, but stop for amending
  • squash: Use the commit, but meld it into the previous commit
  • fixup: Like squash, but discard the commit's log message
  • drop: 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.

GitBeginner
Avatar for Niall Maher

Written by Niall Maher

Founder of Codú - The web developer community! I've worked in nearly every corner of technology businesses: Lead Developer, Software Architect, Product Manager, CTO, and now happily a Founder.

Loading

Fetching comments

Hey! 👋

Got something to say?

or to leave a comment.