0Pricing

Mastering the Craft: Advanced Git & GitHub Techniques for Professional Developers

Dive into advanced Git commands and powerful GitHub features that elevate your development workflow, from interactive rebasing and Git hooks to GitHub Actions and Code Owners, enabling cleaner history, automation, and robust collaboration.

G
Git & GitHub Professional Workflow · 10 min read · 1,962 words

Welcome back to the CoddyKit blog! We're thrilled to have you with us for the fourth installment of our series on "Git & GitHub Professional Workflow." So far, we've covered the essentials, explored best practices, and learned how to sidestep common pitfalls. Now, it's time to level up. Today, we're venturing into the exciting world of advanced Git techniques and real-world GitHub features that transform a good developer into a true Git master.

As you progress in your software development journey, you'll encounter complex scenarios that demand more than just basic git add, commit, and push. This post is designed to equip you with the knowledge to handle these situations gracefully, automate tedious tasks, and collaborate more effectively within a professional team. Let's unlock the true power of Git and GitHub!

Advanced Git Techniques for a Polished History

1. Interactive Rebase: Sculpting Your Commit History (git rebase -i)

Imagine you've been working on a feature branch for a while. You've made several small commits, some fixing typos, others adding debug statements, and finally, the core functionality. Before merging this into your main branch, you want a clean, concise, and meaningful history. This is where git rebase -i (interactive rebase) becomes your best friend.

Interactive rebase allows you to rewrite a series of commits. You can:

  • Squash multiple small commits into a single, logical commit.
  • Reorder commits.
  • Edit commit messages or even the contents of a commit.
  • Drop commits entirely.

Real-World Use Case: Cleaning a Feature Branch

Let's say your feature branch my-new-feature branched off main, and you've made 5 commits. To clean up the last 5 commits before merging:

git checkout my-new-feature
git rebase -i HEAD~5

This command opens an editor showing your last five commits. You'll see something like this:

pick 0a1b2c3 Initial commit for feature X
pick 1d2e3f4 Fix typo in README
pick 2g3h4i5 Add basic functionality
pick 3j4k5l6 Debugging changes
pick 4m5n6o7 Implement final feature logic

# Rebase 5a6b7c8..4m5n6o7 onto 5a6b7c8 (5 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) for each commit
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.

You could change it to:

pick 0a1b2c3 Initial commit for feature X
s 1d2e3f4 Fix typo in README
s 2g3h4i5 Add basic functionality
s 3j4k5l6 Debugging changes
pick 4m5n6o7 Implement final feature logic

Then, save and exit. Git will then prompt you to combine the commit messages for the squashed commits, resulting in a much cleaner history.

2. Git Reflog: Your Safety Net for Lost Commits (git reflog)

Have you ever accidentally reset your branch, deleted a commit, or messed up a rebase? The feeling of panic is real. But fear not, git reflog is here to save the day!

git reflog (reference log) records every single time your HEAD pointer has changed. This includes commits, merges, rebases, resets, and more. It's essentially a local history of your repository's actions, even if those actions aren't part of the main commit graph anymore.

Real-World Use Case: Recovering from a Bad Reset

Let's say you accidentally ran git reset --hard HEAD~3, thinking you only wanted to go back one commit, and now you've lost three valuable commits. Don't commit another commit of despair!

git reflog

You'll see a list like this:

a1b2c3d HEAD@{0}: reset: moving to HEAD~3
e4f5g6h HEAD@{1}: commit: Add final feature logic
i7j8k9l HEAD@{2}: commit: Debugging changes
m0n1o2p HEAD@{3}: commit: Add basic functionality
q3r4s5t HEAD@{4}: commit (initial): Initial commit for feature X

You can see that HEAD@{1} was the state *before* your accidental reset. To restore your branch to that state:

git reset --hard HEAD@{1}

Voila! Your lost commits are back. git reflog is a powerful local safety net, but remember it's local to your machine.

3. Git Hooks: Automating Your Workflow

Git hooks are scripts that Git executes before or after events like committing, pushing, or receiving pushed commits. They are a powerful way to automate tasks and enforce policies.

There are two main types:

  • Client-side hooks: Reside in your local repository (.git/hooks/) and are triggered by operations like committing or merging.
  • Server-side hooks: Reside on the Git server and are triggered by network operations like receiving pushed commits.

Real-World Use Case: Enforcing Code Standards with a pre-commit Hook

A common client-side hook is pre-commit. This script runs before Git asks you for a commit message or creates a commit. If the script exits with a non-zero status, the commit is aborted.

You can use pre-commit to:

  • Run linters (ESLint, Black, Prettier).
  • Execute unit tests.
  • Check for specific patterns (e.g., preventing sensitive information from being committed).

Here's a simple example of a pre-commit hook that checks for a specific file:

#!/bin/sh

# Check if a specific file exists before committing

if [ ! -f "src/main.js" ]; then
  echo "Error: 'src/main.js' must exist before committing."
  exit 1
fi

# Run a linter (assuming 'eslint' is installed and configured)
# if ! npx eslint --fix src/; then
#   echo "ESLint issues found. Please fix them before committing."
#   exit 1
# fi

exit 0

Save this script as .git/hooks/pre-commit and make it executable (chmod +x .git/hooks/pre-commit). Now, every time you try to commit, this script will run.

4. Git Stash: Beyond Simple Saves (git stash)

While often introduced as a basic way to temporarily save changes, git stash has advanced uses:

  • git stash save "message": Add a descriptive message to your stash.
  • git stash list: View all your stashes.
  • git stash apply stash@{n}: Apply a specific stash from your list without dropping it.
  • git stash branch new-branch-name stash@{n}: Create a new branch from where the stash was created and apply the changes to it. This is incredibly useful if you realize your stashed changes belong on a new feature branch.
  • git stash --include-untracked or git stash -u: Stash untracked files as well.

Real-World GitHub Features for Collaborative Excellence

1. GitHub Actions: Your CI/CD Powerhouse

GitHub Actions is GitHub's integrated continuous integration and continuous delivery (CI/CD) platform. It allows you to automate virtually any software development workflow directly within your repository.

You can use Actions to:

  • Automatically build and test your code on every push or pull request.
  • Deploy your application to various environments.
  • Automate release processes.
  • Perform code quality checks, security scans, and more.

Real-World Use Case: Automated Testing on Pull Request

A typical GitHub Actions workflow might look like this (saved in .github/workflows/ci.yml):

name: CI Build and Test

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]

jobs:
  build-test:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
    - name: Set up Node.js
      uses: actions/setup-node@v4
      with:
        node-version: '20'
    - name: Install dependencies
      run: npm install
    - name: Run tests
      run: npm test

This simple YAML configuration tells GitHub to run a job named build-test on an Ubuntu runner whenever code is pushed to main or develop, or a pull request targets these branches. It checks out the code, sets up Node.js, installs dependencies, and runs tests. If any step fails, the workflow fails, and the pull request status will reflect this, preventing broken code from being merged.

2. Code Owners: Defining Responsibility and Streamlining Reviews

In larger projects, it can be challenging to know who should review specific parts of the codebase. GitHub's Code Owners feature addresses this by allowing you to define individuals or teams responsible for specific files or directories.

When a pull request modifies code owned by a defined code owner, they are automatically requested for review. This ensures that changes are reviewed by the most knowledgeable people and helps maintain code quality and consistency.

You define code owners in a file named CODEOWNERS in the root, .github/, or docs/ directory of your repository:

# This is a comment.
# Each line is a file pattern followed by one or more owners.

# All files in the src/ directory are owned by @team-frontend
/src/ @team-frontend

# The database schema files are owned by @backend-lead
/db/schema/*.sql @backend-lead

# The documentation folder is owned by @docs-team
/docs/* @docs-team

# Specific file owned by an individual
/config/settings.json @john-doe

3. Protected Branches: Guarding Your Mainlines

Protected branches are a critical GitHub feature for ensuring the stability and integrity of your main development lines (e.g., main or develop). You can configure rules that prevent direct pushes, require successful status checks (from GitHub Actions, for example), and mandate a minimum number of approving reviews before a pull request can be merged.

Key protections include:

  • Requiring pull request reviews before merging.
  • Requiring status checks to pass before merging.
  • Requiring branches to be up to date before merging.
  • Preventing force pushes.
  • Restricting who can push to matching branches.

4. Issue and Pull Request Templates: Standardizing Communication

Effective communication is paramount in team development. GitHub's issue and pull request templates provide predefined structures that guide contributors on what information to include when creating new issues or opening pull requests.

This helps to:

  • Ensure all necessary information is provided upfront.
  • Reduce back-and-forth questions.
  • Maintain consistency across all contributions.
  • Streamline the review process.

You place these Markdown files in the .github/ISSUE_TEMPLATE/ or .github/PULL_REQUEST_TEMPLATE/ directories.

Putting It All Together: A Professional Workflow Scenario

Let's visualize how these advanced techniques and features integrate into a seamless professional workflow:

  1. A developer checks out a new feature branch from develop.
  2. While working, they frequently use git stash to switch contexts or save work temporarily.
  3. Before pushing their feature branch for review, they use git rebase -i to squash their small, iterative commits into a few logical, well-described commits.
  4. A pre-commit Git hook automatically runs a linter and unit tests, ensuring code quality before the commit is even finalized.
  5. The developer pushes their cleaned-up branch and opens a Pull Request against develop.
  6. GitHub Actions automatically trigger, running a comprehensive test suite and building the application.
  7. Based on the CODEOWNERS file, relevant team members are automatically requested for review.
  8. The develop branch is protected, requiring at least two approving reviews and all GitHub Actions checks to pass before the PR can be merged.
  9. If, at any point, the developer makes a mistake (e.g., accidentally resets their branch), they confidently use git reflog to recover their work.

This integrated approach fosters a highly efficient, reliable, and collaborative development environment, minimizing errors and maximizing productivity.

Conclusion

By delving into advanced Git commands like interactive rebase and reflog, and leveraging powerful GitHub features such as Actions, Code Owners, and Protected Branches, you're not just using Git and GitHub; you're mastering them. These tools empower you to maintain a pristine project history, automate crucial development tasks, and collaborate with unparalleled efficiency and confidence.

Keep practicing these techniques, and you'll soon find yourself navigating complex repositories with ease and contributing to projects at a truly professional level. Stay tuned for our final post in this series, where we'll explore future trends and the broader Git and GitHub ecosystem!

ProgrammingTutorialCoddyKit

Enjoyed this article?

Explore more tutorials and insights to level up your coding skills.

Browse All Articles →