YouTube Summaries | Advanced Git Tutorial

January 25th, 2024

Introduction:

This summary will serve to cement the learnings that I took from the video above, which discusses advanced practices in git, including cherry-picking, submodules and reflogging. I hope you find it useful too!

Interactive Rebase

Interactive rebase is a powerful tool for modifying commit history. It allows the ability to reorder, edit, and combine commits, offering flexibility in crafting a clean and meaningful commit history.

# Start interactive rebase from the commit before the one you want to modify
git rebase -i <commit>

# In the interactive rebase editor, re-order, edit, or squash commits as needed
pick <commit1>
edit <commit2>
pick <commit3>

# Amend the selected commit
git commit --amend

# Continue with the rebase
git rebase --continue

# Finish the rebase
git rebase --finish

Squashing using Interactive Rebase

To squash two commits in Git using interactive rebase, follow the following steps. Let’s assume you want to squash the last two commits:

  1. Open a terminal and navigate to your Git repository.

  2. Start an interactive rebase for the last three commits (replace HEAD~3 with the appropriate commit range):

    git rebase -i HEAD~3
    
  3. An editor will open with a list of commits. It might look like this:

    pick abc123 Last commit
    pick def456 Second-to-last commit
    pick 789ghi Third-to-last commit
    
  4. Change the word “pick” to “squash” (or just “s”) for the commits you want to squash. In this case, you want to squash the last two commits:

    pick abc123 Last commit
    squash def456 Second-to-last commit
    squash 789ghi Third-to-last commit
    
  5. Save and close the editor.

  6. Another editor will open for you to edit the commit message. You can modify the commit message or keep it as-is.

    # This is a combination of 2 commits.
    # This is the 1st commit message:
    
    Last commit
    
    # This is the commit message #2:
    
    Second-to-last commit
    
  7. Save and close the editor.

  8. The rebase is complete, and the last two commits are now squashed into a single commit.

  9. If you’ve already pushed the commits to a remote branch, you’ll need to force-push the changes:

    git push origin branch-name --force
    

Remember that interactive rebase rewrites commit history, so use it with caution, especially if the branch is shared with others. Force-pushing can overwrite remote history, and collaborators may need to sync their local branches accordingly.

Cherry-Picking

Cherry-picking is a technique for selectively applying specific commits to another branch. It is a technique that proves valuable for incorporating specific changes without merging entire branches. Below is an example of a cherry-picking process when done in the terminal.

# Switch to the branch where you want to apply the commit
git checkout <target_branch>

# Cherry-pick the commit from the source branch
git cherry-pick <commit_hash>

# Resolve any merge conflicts if needed
git add <conflicted_files>
git cherry-pick --continue

# Finish the cherry-pick process
git cherry-pick --finish

Reflog

The reflog (reference logs) in Git is a powerful tool for tracking and recovering changes in the repository. It acts as a safety net, allowing developers to navigate through the history, even after complex operations or accidental changes.

The reflog maintains a chronological list of operations that affected the references in the repository, such as branch creations, commits, merges, and more. Each entry in the reflog contains a commit hash, a reference (e.g., branch or HEAD), an action performed, and a timestamp.

Code Example:

# View the reflog
git reflog

Recovering Lost Commits

If you accidentally delete a branch or reset to an unintended state, the reflog helps recover lost commits. The reflog entries act as a safety buffer before references are updated permanently.

Code Example:

# Find the commit hash in the reflog
git reflog <branch-name>

# Reset a branch to a specific commit using the commit hash
git reset --hard <commit-hash>

Undoing Git Operations

The reflog simplifies undoing operations. For instance, if a commit is mistakenly amended or removed, the reflog entries provide an easy way to revert to the previous state.

Code Example:

# Restore to the previous state before an operation
git reset --hard HEAD@{1}

Time Travel with Reflog

Developers can navigate through the reflog’s timeline, exploring different states of the repository. This is particularly useful for understanding changes, debugging, or identifying when specific actions occurred.

Code Example:

# Move to a previous state using reflog
git reset --hard HEAD@{2}

Cautionary Note

While reflog is a valuable tool, users should exercise caution, especially when force-pushing or amending commits. The reflog helps recover lost changes, but it’s not a substitute for proper collaboration practices.

In summary, the Git reflog is an essential feature for managing and recovering from unintended changes, offering a safety net for developers navigating the complex landscape of Git history.

Code Example:

# Clear the reflog (use with caution)
git reflog expire --expire=now --all
git gc --prune=now

Submodules

Git submodules are a feature that enables the inclusion of external Git repositories within a parent Git repository. This allows for the integration of third-party code or libraries while keeping them separate from the main project files. Let’s delve deeper into how to work with Git submodules:

  1. Adding a Submodule:

    git submodule add <repository-url> <path>
    

    Use this command to add a submodule. Replace <repository-url> with the URL of the Git repository you want to include and <path> with the local path where the submodule should be stored.

  2. Understanding Submodule Structure:

    • The submodule itself is a fully functional Git repository.
    • The parent repository stores submodule information in .gitmodules and .git/config.
    • The submodule’s working files reside within the parent project but aren’t part of the version control context.
  3. Committing Submodules:

    git add <path-to-submodule>
    git commit -m "Add submodule"
    

    After adding a submodule, you need to commit the changes to the parent repository. Include the submodule path in the git add command.

  4. Cloning a Project with Submodules:

    git clone --recursive <repository-url>
    

    When cloning a project with submodules, use the --recursive option to automatically initialize and update submodules.

  5. Manually Initializing Submodules:

    git submodule update --init --recursive
    

    If not using the --recursive option during cloning, manually initialize and update submodules using this command.

  6. Viewing Submodule Information:

    git submodule status
    

    Check the status of submodules to see the commit they are checked out on.

  7. Working with Submodule Revisions:

    • Submodules are always checked out on a specific commit, not a branch.
    • This ensures a consistent version of the submodule code, regardless of updates in the submodule repository.
  8. Managing Submodules Safely:

    • Avoid manual manipulation of submodule configuration files.
    • Use proper Git commands or GUI tools for submodule management to prevent issues.

Submodule Workflow

Submodules are fully functional Git repositories nested within the main repository. Some of the main commands when working with submodules were touched on previously, several more are shown below that deal with updating and modifying submodules that exist within a parent git repository.

# Update submodules in an existing clone
git submodule update --init --recursive

# Modify files within the submodule
cd lib
# Make changes...
git add .
git commit -m "Modify submodule"

# Push changes to submodule repository
git push origin main

# Update the main repository with the new submodule commit
cd ..
git add lib
git commit -m "Update submodule to latest commit"

Search & Find

Searching and Finding in Git

Searching and finding specific information within a Git repository is crucial for navigating commit history effectively. Git provides various tools for filtering commits based on different criteria.

  1. Filtering by Date:

    • Use the git log command with the --after and --before flags to find commits within a specific date range.
    git log --after="2022-07-01" --before="2022-07-05"
    
  2. Filtering by Commit Message:

    • Employ the --grep flag with git log to search for commits containing a specific keyword or phrase in their commit messages.
    git log --grep="refactored"
    
    • Utilize regular expressions with grep for more advanced search patterns.
  3. Filtering by Author:

    • Use the --author flag with git log to find commits by a specific author.
    git log --author="Heinemeier Colleague"
    
  4. Filtering by File:

    • Identify the evolution of a specific file over time by filtering commits that modified that file.
    git log -- <file-path>
    
    • The double dashes ensure Git recognizes the file name and not misinterpret it as a branch name.
  5. Comparing Branches:

    • Compare commits between branches to identify the differences.
    git log feature-branch..main
    
    • This provides insights into changes made in the main branch after branching off from the feature branch.
  6. Combining Criteria:

    • Combine multiple criteria to narrow down search results. For instance, find commits by a specific author before a certain date.
    git log --author="Heinemeier Colleague" --before="2022-07-01"
    
    • Experiment with different combinations to refine search queries.