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.
# 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
# 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
# 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
# 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
# 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
# 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
# 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.
# Types of tags: # Lightweight: Just a pointer (like a branch that never moves) # Annotated: Full object with metadata (recommended)
Creating and Managing Tags
# 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)
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
#!/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
#!/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
# 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
# .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
# 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
# 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
# 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
# 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
# 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.
# 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
# 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.
# 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
#!/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
#!/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
#!/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
#!/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
#!/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
#!/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)
// 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
#!/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
#!/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
#!/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
#!/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
| Concept | Command | Use Case |
|---|---|---|
| Detached HEAD | git checkout v1.0.0 | Viewing old release |
| Save detached | git checkout -b new-branch | Keep changes from detached |
| Tag (light) | git tag v1.0.0 | Quick bookmark |
| Tag (annotated) | git tag -a v1.0.0 -m "msg" | Release versioning |
| Push tags | git push --tags | Share releases |
| Amend commit | git commit --amend | Fix last commit |
| Interactive rebase | git rebase -i HEAD~3 | Clean up history |
| Squash commits | rebase -i with squash | Combine related commits |
| Reflog | git reflog | Recover lost commits |
| Force push | git push --force-with-lease | Safe history rewrite |
| Pre-commit hook | .git/hooks/pre-commit | Quality checks before commit |
| Pre-push hook | .git/hooks/pre-push | Tests before pushing |
| Commit-msg hook | .git/hooks/commit-msg | Validate commit messages |
🚀 Practice Exercises
Exercise 1: Practice Detached HEAD Recovery
# 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
# 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
# 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
Post a Comment