Skip to main content

Advanced Git Concepts

Advanced Git Concepts: The DevOps Engineer's Complete Guide
Master Git beyond the basics and unlock powerful version control techniques used by professional development teams.

📅 Published: Feb 2026
⏱️ Estimated Reading Time: 20 minutes
🏷️ Tags: Git, Version Control, Advanced Git, DevOps, Source Control, Git Hooks


🎭 Detached HEAD: What It Is and Why It Happens

Understanding HEAD in Git

Think of HEAD as a sticky note that tells you "you are here" on a map. Normally, this sticky note is attached to a branch name (like main or develop). When you're on a branch, moving forward (committing) also moves the branch pointer.

Detached HEAD is when your sticky note is directly attached to a commit, not to a branch. You're no longer on a named branch—you're floating in commit space.

bash
# Normal state: HEAD points to a branch
git status
# On branch main
# Your branch is up to date with 'origin/main'

# Detached HEAD state:
git checkout v1.0.0
git status
# HEAD detached at v1.0.0
# nothing to commit, working tree clean

Why Detached HEAD Happens

bash
# 1. Checking out a tag (most common reason)
git checkout v2.1.0
# HEAD is now at abc1234... Release version 2.1.0

# 2. Checking out a specific commit hash
git checkout abc1234
# HEAD is now at abc1234... Fix critical bug #123

# 3. Checking out a remote branch without tracking
git checkout origin/feature-branch
# Note: switching to 'origin/feature-branch'. You are in 'detached HEAD' state.

# 4. Interactive rebase during the process
git rebase -i HEAD~3
# During rebase, you're in detached HEAD until it completes

# 5. Bisecting to find bugs
git bisect start
git bisect bad
git bisect good abc1234
# Bisecting: You're in detached HEAD while searching

Working in Detached HEAD

bash
# You can still make commits in detached HEAD
git checkout v1.0.0
echo "hotfix" > file.txt
git add file.txt
git commit -m "Emergency hotfix on v1.0.0"
# [detached HEAD def4567] Emergency hotfix on v1.0.0

# But these commits are vulnerable! They can be garbage collected
# because no branch points to them.

Saving Work from Detached HEAD

bash
# Method 1: Create a new branch (recommended)
git checkout -b hotfix/v1.0.1
# Now your commits are safe on a named branch
git push -u origin hotfix/v1.0.1

# Method 2: Create a tag (for releases)
git tag v1.0.0-hotfix1
git push origin v1.0.0-hotfix1

# Method 3: Cherry-pick to existing branch
git checkout main
git cherry-pick def4567  # The commit from detached HEAD

# Method 4: Stash and apply (if you haven't committed)
git stash
git checkout main
git stash pop

Real-World Scenarios

Scenario 1: Emergency Production Hotfix

bash
# Production is on v2.1.0, need immediate fix
git checkout v2.1.0
# HEAD detached at v2.1.0

# Make the fix
echo "security patch" > patch.txt
git add patch.txt
git commit -m "SEC-123: Apply critical security patch"

# Create hotfix branch
git checkout -b hotfix/v2.1.1

# Deploy and tag
git tag v2.1.1
git push origin hotfix/v2.1.1 v2.1.1

# Merge back to develop
git checkout develop
git merge --no-ff hotfix/v2.1.1

Scenario 2: Investigating Old Bug

bash
# Need to test behavior in older version
git checkout v1.5.0
# Detached HEAD state

# Run tests against old version
./run-tests.sh

# Found where bug was introduced
git bisect start
git bisect bad  # Current version is bad
git bisect good v1.4.0  # This version was good

# Git will automatically checkout commits to test
# Each checkout is in detached HEAD

# After finding the bad commit
git bisect reset  # Returns to original branch

Detached HEAD Best Practices

bash
# 1. NEVER make commits in detached HEAD without creating a branch
# Bad:
git checkout v1.0.0
git commit -m "Lost commit"  # This commit will be orphaned!

# Good:
git checkout -b hotfix v1.0.0
git commit -m "Safe commit"

# 2. Always know where you are
git branch --show-current  # Shows current branch or (HEAD detached)
git rev-parse --abbrev-ref HEAD  # Same as above

# 3. Create recovery alias
git config --global alias.save-detached '!f() { \
    branch="detached-$(date +%Y%m%d-%H%M%S)"; \
    git checkout -b $branch; \
    echo "Saved detached HEAD to branch: $branch"; \
}; f'

# Usage:
git checkout abc1234
# make changes
git commit -am "changes"
git save-detached

🏷️ Tags & Releases: Versioning Your Success

What Are Git Tags?

Tags are permanent bookmarks in your repository's history. Unlike branches which move, tags stay fixed to a specific commit forever. Think of tags as archival labels for important milestones.

bash
# Types of tags:
# Lightweight: Just a pointer (like a branch that never moves)
# Annotated: Full object with metadata (recommended)

Creating and Managing Tags

bash
# Lightweight tag (just a name)
git tag v1.0.0

# Annotated tag (with metadata) - ALWAYS USE THIS
git tag -a v1.0.0 -m "Production release 1.0.0"

# Tag with GPG signature (cryptographically verified)
git tag -s v1.0.0 -m "Signed release 1.0.0"

# Tag a specific commit (not just HEAD)
git tag -a v1.0.0 abc1234 -m "Release 1.0.0"

# List tags
git tag
git tag -l "v1.*"  # Pattern matching
git tag --sort=-v:refname  # Sort by version

# Show tag details
git show v1.0.0

# Delete local tag
git tag -d v1.0.0

# Delete remote tag
git push --delete origin v1.0.0

# Push tags to remote
git push origin v1.0.0
git push --tags  # Push all tags

Semantic Versioning (SemVer)

text
MAJOR.MINOR.PATCH
   ↑     ↑      ↑
Breaking  New    Bug
changes feature fixes

Examples:
v1.0.0     - Initial release
v1.1.0     - Added new feature (backward compatible)
v1.1.1     - Bug fixes
v2.0.0     - Breaking API changes
v1.2.0-rc1 - Release candidate
v1.2.0-beta - Beta release

Release Management Workflow

bash
#!/bin/bash
# release-workflow.sh
# Professional release process

RELEASE_VERSION=$1
RELEASE_BRANCH="release/$RELEASE_VERSION"

echo "=== Starting Release $RELEASE_VERSION ==="

# 1. Create release branch from develop
git checkout -b $RELEASE_BRANCH develop

# 2. Update version numbers
echo "Updating version files..."
sed -i "s/version=.*/version=$RELEASE_VERSION/" VERSION
sed -i "s/\"version\": .*/\"version\": \"$RELEASE_VERSION\",/" package.json

# 3. Commit version bump
git add VERSION package.json
git commit -m "chore(release): bump version to $RELEASE_VERSION"

# 4. Final testing
echo "Running final tests..."
./run-tests.sh
if [ $? -ne 0 ]; then
    echo "Tests failed! Fix issues and rerun."
    exit 1
fi

# 5. Merge to main (production branch)
git checkout main
git merge --no-ff $RELEASE_BRANCH -m "release: merge $RELEASE_VERSION"

# 6. Create annotated tag
git tag -a "v$RELEASE_VERSION" -m "Release version $RELEASE_VERSION"

# 7. Merge back to develop
git checkout develop
git merge --no-ff $RELEASE_BRANCH -m "chore: merge release $RELEASE_VERSION back to develop"

# 8. Push everything
git push origin main develop
git push origin "v$RELEASE_VERSION"

# 9. Clean up
git branch -d $RELEASE_BRANCH
git push origin --delete $RELEASE_BRANCH

echo "=== Release $RELEASE_VERSION Complete ==="

Release Artifacts and Changelogs

bash
#!/bin/bash
# generate-changelog.sh

# Generate changelog from git history
REPO_URL="https://github.com/company/project"

echo "# Changelog" > CHANGELOG.md
echo "" >> CHANGELOG.md

# Get all tags sorted by version
TAGS=$(git tag -l --sort=-v:refname)

# For each tag, get commits since previous tag
PREV_TAG=""
for TAG in $TAGS; do
    echo "## $TAG" >> CHANGELOG.md
    echo "" >> CHANGELOG.md
    
    if [ -z "$PREV_TAG" ]; then
        # First tag - get all commits
        RANGE="$TAG"
    else
        RANGE="$PREV_TAG..$TAG"
    fi
    
    # Group commits by type
    echo "### Features" >> CHANGELOG.md
    git log $RANGE --grep="^feat" --pretty=format:"- %s (%h)" >> CHANGELOG.md
    echo "" >> CHANGELOG.md
    echo "" >> CHANGELOG.md
    
    echo "### Bug Fixes" >> CHANGELOG.md
    git log $RANGE --grep="^fix" --pretty=format:"- %s (%h)" >> CHANGELOG.md
    echo "" >> CHANGELOG.md
    echo "" >> CHANGELOG.md
    
    PREV_TAG=$TAG
done

Signed Tags and Release Verification

bash
# 1. Configure GPG key for signing
gpg --full-generate-key
gpg --list-secret-keys --keyid-format LONG

# 2. Configure Git to use your key
git config --global user.signingkey KEY_ID
git config --global commit.gpgsign true  # Sign all commits
git config --global tag.gpgsign true     # Sign all tags

# 3. Create signed tag
git tag -s v1.0.0 -m "Signed release 1.0.0"

# 4. Verify signed tag
git tag -v v1.0.0
# Output: gpg: Signature made ...
#         gpg: Good signature from "Developer Name <email@example.com>"

# 5. Export public key for verification
gpg --armor --export KEY_ID > public-key.asc

# 6. Import public key on other machines
gpg --import public-key.asc
git verify-tag v1.0.0

Tag-Based Deployment

yaml
# .gitlab-ci.yml - Deploy based on tags
stages:
  - test
  - build
  - deploy

variables:
  CI_REGISTRY_IMAGE: registry.gitlab.com/mygroup/myapp

test:
  stage: test
  script:
    - npm install
    - npm test

build:
  stage: build
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
  only:
    - tags

deploy:
  stage: deploy
  script:
    - kubectl set image deployment/myapp myapp=$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
    - kubectl rollout status deployment/myapp
  only:
    - tags

📜 Rewriting History: Power with Responsibility

The Philosophy of History Rewriting

Never rewrite public/shared history. Only rewrite commits that haven't been pushed or are in feature branches.

Think of Git history like a published book:

  • Published (pushed) = Readers have copies, can't change

  • Draft (local) = You can edit freely before publishing

git commit --amend: Fixing the Last Commit

bash
# 1. Change commit message
git commit --amend -m "New and improved message"

# 2. Add forgotten files
git add forgotten-file.txt
git commit --amend --no-edit  # Keep same message

# 3. Change author
git commit --amend --author="New Author <email@example.com>"

# 4. Amending after push (DANGER - force push needed)
git push --force-with-lease origin feature-branch

git rebase: Reorganizing Commits

bash
# Basic rebase - replay commits on top of another branch
git checkout feature
git rebase main
# feature commits now appear AFTER latest main commit

# Interactive rebase - power user's tool
git rebase -i HEAD~3
# Editor opens with:
# pick abc1234 Commit message 1
# pick def5678 Commit message 2
# pick ghi9012 Commit message 3

# Commands available:
# p, pick = use commit
# r, reword = use commit but edit message
# e, edit = use commit but stop for amending
# s, squash = merge into previous commit
# f, fixup = like squash but discard message
# d, drop = remove commit

Squashing Commits: Clean History

bash
# Before squash:
git log --oneline
# def5678 Add documentation
# abc1234 Fix typo in documentation
# 123abc4 Fix formatting
# 456def4 Initial commit

# Squash last 3 commits into one
git rebase -i HEAD~3
# Change to:
# pick 456def4 Initial commit
# squash abc1234 Fix formatting
# squash def5678 Fix typo
# squash ghi9012 Add documentation

# After squash:
git log --oneline
# xyz7890 Add documentation and fixes
# 456def4 Initial commit

Real-World History Rewriting

Scenario 1: Cleaning Up Feature Branch Before PR

bash
# Messy feature branch
git checkout feature/payment-integration
git log --oneline
# a1b2c3d Fix test
# b2c3d4e Add more logging
# c3d4e5f Actually fix the bug
# d4e5f6g Oops, fix typo
# e5f6g7h Add payment API integration
# f6g7h8i Initial commit

# Clean it up
git rebase -i HEAD~6

# Reorder and squash:
# pick f6g7h8i Initial commit
# squash e5f6g7h Add payment API integration
# fixup d4e5f6g Oops, fix typo
# fixup c3d4e5f Actually fix the bug
# squash b2c3d4e Add more logging
# squash a1b2c3d Fix test

# Result:
git log --oneline
# x1y2z3a Add payment API integration with tests
# f6g7h8i Initial commit

Scenario 2: Splitting a Commit

bash
# You have one big commit that should be two
git log -1
# commit abc1234
# Add user authentication and profile page

# Split it
git rebase -i HEAD~1
# Change 'pick' to 'edit'
# Stopped at abc1234

# Reset to previous commit, keeping changes
git reset HEAD^

# Now changes are unstaged
git add auth/
git commit -m "feat: add user authentication"

git add profile/
git commit -m "feat: add user profile page"

git rebase --continue

git reflog: Your Safety Net

The reflog is Git's undo history. It records every movement of HEAD.

bash
# View reflog
git reflog
# abc1234 HEAD@{0}: commit: Fix critical bug
# def5678 HEAD@{1}: rebase -i (finish): returning to refs/heads/feature
# ghi9012 HEAD@{2}: commit: Add tests
# jkl1234 HEAD@{3}: commit: WIP

# Oops, I accidentally deleted a branch
git reflog | grep "feature"
# abc1234 HEAD@{5}: checkout: moving from feature to main

# Recover the branch
git checkout -b feature-recovered abc1234

# Undo a bad rebase
git reset --hard HEAD@{2}  # Go back before rebase

# Restore lost commit
git cherry-pick abc1234

History Rewriting Best Practices

bash
# 1. NEVER rewrite public history
# BAD:
git commit --amend
git push --force

# GOOD:
git commit --amend
git push --force-with-lease  # Safer, checks if remote changed

# 2. Use branches as safety nets
git checkout -b backup-branch
git checkout original-branch
git rebase -i HEAD~5  # If something goes wrong, backup-branch is safe

# 3. Document forced pushes
git push --force-with-lease origin feature-branch
echo "Force pushed feature-branch after rebasing on main - $(date)" >> FORCE_PUSH_LOG.md

# 4. Team agreement on history policy
# Example: 
# - Feature branches: clean history before merge
# - Main/develop: linear history (no merge commits)
# - Release tags: never change

🪝 Git Hooks: Automating Your Workflow

What Are Git Hooks?

Git hooks are custom scripts that Git executes before or after specific events. Think of them as automated quality checkpoints in your development workflow.

bash
# Hook locations
.git/hooks/
├── applypatch-msg.sample
├── commit-msg.sample
├── fsmonitor-watchman.sample
├── post-update.sample
├── pre-applypatch.sample
├── pre-commit.sample      # Before commit creation
├── pre-merge-commit.sample
├── pre-push.sample        # Before pushing
├── pre-rebase.sample
├── pre-receive.sample
├── prepare-commit-msg.sample
├── push-to-checkout.sample
└── update.sample

pre-commit: Quality Gate

bash
#!/bin/bash
# .git/hooks/pre-commit
# This runs BEFORE each commit

echo "=== Running pre-commit checks ==="

# 1. Check for merge conflicts
if grep -r "^<<<<<<< HEAD" . || grep -r "^>>>>>>>" .; then
    echo "Error: Merge conflict markers found"
    exit 1
fi

# 2. Check for debugging code
if grep -r "console.log" --include="*.js" .; then
    echo "Warning: console.log found in JavaScript files"
    # Exit with 0 to allow commit with warning
fi

# 3. Check file size
LARGE_FILES=$(find . -type f -size +5M ! -path "*.git*" ! -path "*/node_modules/*")
if [ -n "$LARGE_FILES" ]; then
    echo "Error: Files larger than 5MB detected:"
    echo "$LARGE_FILES"
    exit 1
fi

# 4. Run linters
if [ -f "package.json" ]; then
    npm run lint || exit 1
fi

if [ -f "*.py" ]; then
    flake8 . || exit 1
fi

# 5. Check for secrets/credentials
SECRET_PATTERNS="password|secret|key|token|credential|PRIVATE"
if grep -ri --exclude-dir={.git,node_modules} "$SECRET_PATTERNS" .; then
    echo "Error: Possible secrets detected in code"
    exit 1
fi

echo "✓ pre-commit checks passed"

Advanced pre-commit: Formatting and Validation

bash
#!/bin/bash
# .git/hooks/pre-commit
set -e  # Exit on any error

echo "=== Code Quality Checks ==="

# Get staged files
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)

# JavaScript/TypeScript files
JS_FILES=$(echo "$STAGED_FILES" | grep -E '\.(js|jsx|ts|tsx)$' || true)
if [ -n "$JS_FILES" ]; then
    echo "Running Prettier..."
    echo "$JS_FILES" | xargs npx prettier --write
    
    echo "Running ESLint..."
    echo "$JS_FILES" | xargs npx eslint --fix
    
    git add $JS_FILES
fi

# Python files
PY_FILES=$(echo "$STAGED_FILES" | grep -E '\.py$' || true)
if [ -n "$PY_FILES" ]; then
    echo "Running Black..."
    echo "$PY_FILES" | xargs black
    
    echo "Running isort..."
    echo "$PY_FILES" | xargs isort
    
    echo "Running flake8..."
    echo "$PY_FILES" | xargs flake8
    
    git add $PY_FILES
fi

# Terraform files
TF_FILES=$(echo "$STAGED_FILES" | grep -E '\.tf$' || true)
if [ -n "$TF_FILES" ]; then
    echo "Running terraform fmt..."
    echo "$TF_FILES" | xargs terraform fmt
    git add $TF_FILES
fi

# Check for TODO/FIXME tags
echo "Checking for TODO/FIXME tags..."
if git diff --cached | grep -i "\+.*todo\|+.*fixme"; then
    echo "⚠️  Warning: Commit contains TODO/FIXME markers"
    # Don't exit with error, just warn
fi

echo "✓ All formatting complete"

pre-commit: Security Scanning

bash
#!/bin/bash
# .git/hooks/pre-commit

echo "=== Security Scan ==="

# 1. AWS keys pattern
AWS_KEY_PATTERN="AKIA[0-9A-Z]{16}"
if git diff --cached | grep -E "$AWS_KEY_PATTERN"; then
    echo "❌ AWS Access Key ID detected in commit"
    exit 1
fi

# 2. Private keys
if git diff --cached | grep -E "\-+BEGIN .* PRIVATE KEY\-+"; then
    echo "❌ Private key detected in commit"
    exit 1
fi

# 3. Database connection strings
DB_PATTERN="(mysql|postgresql|mongodb)://[^:]+:[^@]+@"
if git diff --cached | grep -E "$DB_PATTERN"; then
    echo "❌ Database connection string with credentials detected"
    exit 1
fi

# 4. API keys
API_PATTERN="api[_-]key[=:]\s*['\"]?[a-zA-Z0-9]{20,}"
if git diff --cached | grep -E "$API_PATTERN"; then
    echo "❌ API key detected in commit"
    exit 1
fi

# 5. IP addresses (private)
PRIVATE_IP_PATTERN="(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)"
if git diff --cached | grep -E "$PRIVATE_IP_PATTERN"; then
    echo "⚠️  Warning: Private IP address detected"
    # Warn but allow commit
fi

echo "✓ Security scan passed"

commit-msg: Enforcing Commit Convention

bash
#!/bin/bash
# .git/hooks/commit-msg
# Validates commit message format

COMMIT_MSG_FILE=$1
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")

echo "=== Validating Commit Message ==="

# Conventional Commits pattern
# Format: <type>(<scope>): <description>
# Types: feat, fix, docs, style, refactor, test, chore, perf

PATTERN="^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\([a-z0-9-]+\))?: .{1,50}$"

if ! echo "$COMMIT_MSG" | grep -qE "$PATTERN"; then
    echo "❌ Invalid commit message format"
    echo ""
    echo "Expected: <type>(<scope>): <description>"
    echo ""
    echo "Valid types:"
    echo "  feat:     New feature"
    echo "  fix:      Bug fix"
    echo "  docs:     Documentation only"
    echo "  style:    Code style (formatting, missing semicolons)"
    echo "  refactor: Code change that neither fixes bug nor adds feature"
    echo "  test:     Adding missing tests"
    echo "  chore:    Maintenance tasks"
    echo "  perf:     Performance improvement"
    echo "  ci:       CI configuration changes"
    echo ""
    echo "Example: feat(auth): add login rate limiting"
    echo ""
    exit 1
fi

# Check line length
if [ ${#COMMIT_MSG} -gt 72 ]; then
    echo "⚠️  Warning: Commit message exceeds 72 characters"
fi

# Check for WIP commits
if echo "$COMMIT_MSG" | grep -qi "^wip"; then
    echo "⚠️  Warning: This is a Work In Progress commit"
fi

echo "✓ Commit message format valid"

pre-push: Deployment Gatekeeper

bash
#!/bin/bash
# .git/hooks/pre-push
# Runs BEFORE pushing to remote

REMOTE=$1
URL=$2

echo "=== Running pre-push checks ==="
echo "Remote: $REMOTE"
echo "URL: $URL"

# 1. Prevent pushing to main/master directly
CURRENT_BRANCH=$(git branch --show-current)
if [ "$CURRENT_BRANCH" = "main" ] || [ "$CURRENT_BRANCH" = "master" ]; then
    read -p "⚠️  Pushing directly to $CURRENT_BRANCH? (y/n) " -n 1 -r
    echo
    if [[ ! $REPLY =~ ^[Yy]$ ]]; then
        echo "❌ Push aborted"
        exit 1
    fi
fi

# 2. Run tests
echo "Running test suite..."
if [ -f "package.json" ]; then
    npm test || exit 1
elif [ -f "pom.xml" ]; then
    mvn test || exit 1
elif [ -f "Makefile" ]; then
    make test || exit 1
fi

# 3. Check if commit messages contain required references
git log origin/$CURRENT_BRANCH..$CURRENT_BRANCH --format=%s | while read line; do
    if ! echo "$line" | grep -qE "\[JIRA-[0-9]+\]"; then
        echo "⚠️  Commit missing JIRA reference: $line"
    fi
done

# 4. Check for large files
LOCAL_COMMITS=$(git rev-list origin/$CURRENT_BRANCH..$CURRENT_BRANCH)
for COMMIT in $LOCAL_COMMITS; do
    LARGE_FILES=$(git ls-tree --name-only -r $COMMIT | xargs git ls-files --error-unmatch 2>/dev/null | \
                   xargs git cat-file --batch-check='%(objectsize) %(objectname)' 2>/dev/null | \
                   awk '$1 > 5000000 {print $2}')
    if [ -n "$LARGE_FILES" ]; then
        echo "❌ Commit $COMMIT contains files larger than 5MB"
        echo "$LARGE_FILES"
        exit 1
    fi
done

# 5. Check for security vulnerabilities (Node.js)
if [ -f "package-lock.json" ]; then
    npm audit --audit-level=high || exit 1
fi

echo "✓ All pre-push checks passed"

Installing Hooks Across Teams

bash
#!/bin/bash
# setup-hooks.sh
# Install standardized hooks across the team

echo "=== Setting up Git Hooks ==="

HOOKS_DIR=".git/hooks"

# Backup existing hooks
if [ -d "$HOOKS_DIR" ]; then
    TIMESTAMP=$(date +%Y%m%d-%H%M%S)
    mkdir -p ".git/hooks-backup-$TIMESTAMP"
    cp .git/hooks/* ".git/hooks-backup-$TIMESTAMP/" 2>/dev/null
    echo "✅ Backed up existing hooks to .git/hooks-backup-$TIMESTAMP"
fi

# Download hooks from repository
HOOKS_REPO="https://raw.githubusercontent.com/company/git-hooks/main"

for HOOK in pre-commit pre-push commit-msg; do
    curl -sSL "$HOOKS_REPO/$HOOK" -o ".git/hooks/$HOOK"
    chmod +x ".git/hooks/$HOOK"
    echo "✅ Installed $HOOK hook"
done

# Share hooks configuration
cat > .git-hooks-config.yml << 'EOF'
# Git Hooks Configuration
pre-commit:
  lint: true
  format: true
  secrets: true
  large-files: true
  
pre-push:
  run-tests: true
  security-scan: true
  jira-required: true
EOF

echo "✅ Installed hook configuration"
echo "=== Git Hooks Setup Complete ==="

Shared Hooks with Husky (Node.js)

json
// package.json
{
  "name": "my-project",
  "scripts": {
    "lint": "eslint .",
    "test": "jest",
    "format": "prettier --write ."
  },
  "husky": {
    "hooks": {
      "pre-commit": "npm run lint && npm run format",
      "pre-push": "npm test",
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  },
  "devDependencies": {
    "husky": "^8.0.0",
    "lint-staged": "^13.0.0",
    "commitlint": "^17.0.0"
  },
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.{json,md,yml}": [
      "prettier --write"
    ]
  }
}

Custom Hook Examples

bash
#!/bin/bash
# .git/hooks/prepare-commit-msg
# Automatically add branch name to commit message

COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
SHA1=$3

# Only for regular commits (not merges, rebases, etc.)
if [ -z "$COMMIT_SOURCE" ]; then
    BRANCH_NAME=$(git branch --show-current)
    
    # Extract ticket number (assuming JIRA format: feature/JIRA-123-description)
    if [[ $BRANCH_NAME =~ ([A-Z]+-[0-9]+) ]]; then
        TICKET=${BASH_REMATCH[1]}
        echo "[$TICKET] $(cat $COMMIT_MSG_FILE)" > $COMMIT_MSG_FILE
    fi
fi
bash
#!/bin/bash
# .git/hooks/post-commit
# Notify team after commit

COMMIT_HASH=$(git rev-parse HEAD)
COMMIT_MSG=$(git log -1 --pretty=%B)
BRANCH=$(git branch --show-current)

# Send notification to Slack
SLACK_WEBHOOK="https://hooks.slack.com/services/xxx/yyy/zzz"

curl -X POST -H 'Content-type: application/json' \
    --data "{
        \"text\": \"💻 New commit on \`$BRANCH\`\",
        \"attachments\": [
            {
                \"color\": \"good\",
                \"text\": \"$COMMIT_MSG\n<https://github.com/company/repo/commit/$COMMIT_HASH|View commit>\"
            }
        ]
    }" \
    $SLACK_WEBHOOK 2>/dev/null || true

🎯 Real-World Scenarios

Scenario 1: Hotfix Release Process

bash
#!/bin/bash
# hotfix-release.sh

HOTFIX_VERSION=$1
BASE_TAG=$2

echo "=== Starting Hotfix $HOTFIX_VERSION ==="

# 1. Create hotfix branch from production tag
git checkout -b hotfix/$HOTFIX_VERSION $BASE_TAG

# 2. Apply fix
git cherry-pick abc1234  # Bug fix commit from develop

# 3. Run tests
npm test
if [ $? -ne 0 ]; then
    echo "❌ Tests failed!"
    exit 1
fi

# 4. Create release commit
git commit -m "chore(release): hotfix $HOTFIX_VERSION"

# 5. Tag release
git tag -a "v$HOTFIX_VERSION" -m "Hotfix version $HOTFIX_VERSION"

# 6. Deploy to production
git push origin "v$HOTFIX_VERSION"
git push origin hotfix/$HOTFIX_VERSION

# 7. Merge back to develop
git checkout develop
git merge --no-ff hotfix/$HOTFIX_VERSION -m "chore: merge hotfix $HOTFIX_VERSION"
git push origin develop

# 8. Clean up
git branch -d hotfix/$HOTFIX_VERSION
git push origin --delete hotfix/$HOTFIX_VERSION

echo "=== Hotfix $HOTFIX_VERSION Complete ==="

Scenario 2: Automated Release Notes

bash
#!/bin/bash
# release-notes.sh

RELEASE_TAG=$1
PREVIOUS_TAG=$(git describe --abbrev=0 --tags $RELEASE_TAG^ 2>/dev/null || echo "")

if [ -z "$PREVIOUS_TAG" ]; then
    RANGE="$RELEASE_TAG"
else
    RANGE="$PREVIOUS_TAG..$RELEASE_TAG"
fi

cat << EOF
# Release $RELEASE_TAG

## 📅 Release Date
$(date +%Y-%m-%d)

## 🚀 Features
$(git log $RANGE --grep="^feat" --pretty=format:"- %s (%h)" | sed 's/^feat: //')

## 🐛 Bug Fixes
$(git log $RANGE --grep="^fix" --pretty=format:"- %s (%h)" | sed 's/^fix: //')

## 📚 Documentation
$(git log $RANGE --grep="^docs" --pretty=format:"- %s (%h)" | sed 's/^docs: //')

## 🧪 Tests
$(git log $RANGE --grep="^test" --pretty=format:"- %s (%h)" | sed 's/^test: //')

## 🔧 Maintenance
$(git log $RANGE --grep="^chore" --pretty=format:"- %s (%h)" | sed 's/^chore: //')

## 👥 Contributors
$(git log $RANGE --pretty=format:"- %an" | sort -u)

## 📦 Downloads
- [Source code]($REPO_URL/archive/refs/tags/$RELEASE_TAG.tar.gz)
- [Release artifacts]($REPO_URL/releases/tag/$RELEASE_TAG)

EOF

📋 Quick Reference Cheat Sheet

ConceptCommandUse Case
Detached HEADgit checkout v1.0.0Viewing old release
Save detachedgit checkout -b new-branchKeep changes from detached
Tag (light)git tag v1.0.0Quick bookmark
Tag (annotated)git tag -a v1.0.0 -m "msg"Release versioning
Push tagsgit push --tagsShare releases
Amend commitgit commit --amendFix last commit
Interactive rebasegit rebase -i HEAD~3Clean up history
Squash commitsrebase -i with squashCombine related commits
Refloggit reflogRecover lost commits
Force pushgit push --force-with-leaseSafe history rewrite
Pre-commit hook.git/hooks/pre-commitQuality checks before commit
Pre-push hook.git/hooks/pre-pushTests before pushing
Commit-msg hook.git/hooks/commit-msgValidate commit messages

🚀 Practice Exercises

Exercise 1: Practice Detached HEAD Recovery

bash
# 1. Create a tag
git tag -a practice-v1 -m "Practice tag"

# 2. Enter detached HEAD
git checkout practice-v1

# 3. Make a change
echo "detached test" > test.txt
git add test.txt
git commit -m "detached commit"

# 4. Recover the commit
git checkout -b recovered-branch

# 5. Verify
git log --oneline -1

Exercise 2: Clean Up History

bash
# 1. Create messy history
touch file1.txt && git add . && git commit -m "wip"
touch file2.txt && git add . && git commit -m "fix typo"
touch file3.txt && git add . && git commit -m "actually fix"
touch file4.txt && git add . && git commit -m "add feature"

# 2. Squash commits
git rebase -i HEAD~4
# Change pick to squash for commits 2-4

# 3. Verify clean history
git log --oneline

Exercise 3: Create and Test Hooks

bash
# 1. Create pre-commit hook
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
echo "Testing pre-commit hook"
if [ -f "secret.txt" ]; then
    echo "Error: secret.txt should not be committed"
    exit 1
fi
EOF
chmod +x .git/hooks/pre-commit

# 2. Test it
touch secret.txt
git add secret.txt
git commit -m "test"  # Should fail

# 3. Remove and try again
rm secret.txt
git commit -m "test"  # Should succeed

🔗 Master Advanced Git with Hands-on Labs

Advanced Git skills separate junior from senior developers. The ability to recover lost commits, create clean history, and automate workflows with hooks is invaluable.

👉 Practice advanced Git techniques with real scenarios at:
https://devops.trainwithsky.com/

Our platform provides:

  • Interactive Git history challenges

  • Detached HEAD recovery scenarios

  • Hook creation workshops

  • Release management simulations

  • Team workflow exercises


Frequently Asked Questions

Q: When should I use detached HEAD intentionally?
A: When testing older versions, during bisect, or creating quick hotfixes from tags.

Q: How do I recover if I lost commits?
A: Check git reflog first—it's your best friend. Never panic, Git rarely loses data permanently.

Q: Should I force push?
A: Only on private branches with --force-with-lease. Never on shared branches.

Q: What's the difference between squash and fixup?
A: Squash combines commits and asks for message. Fixup combines commits and discards message.

Q: Can hooks be shared with the team?
A: Yes! Either version them in a scripts folder and symlink, or use tools like Husky for Node.js.

Q: How do I skip hooks temporarily?
A: git commit --no-verify or git push --no-verify

Q: What's the best Git workflow?
A: Whatever your team agrees on and consistently follows. GitFlow, GitHub Flow, or trunk-based development all work.

Have questions about advanced Git? Share your tricky Git scenario in the comments below! 💬

Comments

Popular posts from this blog

Introduction to Terraform – The Future of Infrastructure as Code

  Introduction to Terraform – The Future of Infrastructure as Code In today’s fast-paced DevOps world, managing infrastructure manually is outdated . This is where Terraform comes in—a powerful Infrastructure as Code (IaC) tool that allows you to define, provision, and manage cloud infrastructure efficiently . Whether you're working with AWS, Azure, Google Cloud, or on-premises servers , Terraform provides a declarative, automation-first approach to infrastructure deployment. Shape Your Future with AI & Infinite Knowledge...!! Read In-Depth Tech & Self-Improvement Blogs http://www.skyinfinitetech.com Watch Life-Changing Videos on YouTube https://www.youtube.com/@SkyInfinite-Learning Transform Your Skills, Business & Productivity – Join Us Today! In today’s digital-first world, agility and automation are no longer optional—they’re essential. Companies across the globe are rapidly shifting their operations to the cloud to keep up with the pace of innovatio...

📊 Monitoring & Logging in Kubernetes – Tools like Prometheus, Grafana, and Fluentd

  Monitoring & Logging in Kubernetes – Tools like Prometheus, Grafana, and Fluentd Monitoring and logging are essential for maintaining a healthy and well-performing Kubernetes cluster. In this guide, we’ll cover why monitoring is important, key monitoring tools like Prometheus and Grafana, and logging tools like Fluentd to help you gain visibility into your cluster’s performance and logs. Shape Your Future with AI & Infinite Knowledge...!! Want to Generate Text-to-Voice, Images & Videos? http://www.ai.skyinfinitetech.com Read In-Depth Tech & Self-Improvement Blogs http://www.skyinfinitetech.com Watch Life-Changing Videos on YouTube https://www.youtube.com/@SkyInfinite-Learning Transform Your Skills, Business & Productivity – Join Us Today! 🚀 Introduction In today’s fast-paced cloud-native environment, Kubernetes has emerged as the de-facto container orchestration platform. But deploying and managing applications in Kubernetes is just half the ba...

🔒 Kubernetes Security – RBAC, Network Policies, and Secrets Management

  Kubernetes Security – RBAC, Network Policies, and Secrets Management Security is a critical aspect of managing Kubernetes clusters. In this guide, we'll cover essential security mechanisms like Role-Based Access Control (RBAC) , Network Policies , and Secrets Management to help you secure your Kubernetes environment effectively. Shape Your Future with AI & Infinite Knowledge...!! Want to Generate Text-to-Voice, Images & Videos? http://www.ai.skyinfinitetech.com Read In-Depth Tech & Self-Improvement Blogs http://www.skyinfinitetech.com Watch Life-Changing Videos on YouTube https://www.youtube.com/@SkyInfinite-Learning Transform Your Skills, Business & Productivity – Join Us Today! 🚀 Introduction: Why Kubernetes Security Is Non-Negotiable As Kubernetes becomes the backbone of modern cloud-native infrastructure, security is no longer optional—it’s mission-critical . With multiple moving parts like containers, pods, services, nodes, and more, Kuberne...