Advanced GitHub Interview Questions and Answers

๐Ÿ“‹ Table of Contents โ–พ
  1. Questions & Answers
  2. 📝 Knowledge Check

🐱‍⬛ Advanced GitHub Interview Questions

This lesson targets mid-to-senior engineers and DevOps roles. Topics include interactive rebase, git bisect, Git hooks, the object model, reflog, worktrees, submodules, advanced GitHub Actions, branch protection, Dependabot, GitHub Environments, and partial clone. These questions separate Git users from Git engineers.

Questions & Answers

01 What is git rebase –interactive and how do you use it?

Rebase Interactive rebase (git rebase -i) lets you rewrite, reorder, squash, and edit commits in your local history before sharing them. The most powerful tool for cleaning up a messy commit history.

# Rebase the last 4 commits interactively
git rebase -i HEAD~4
# Opens editor with:
# pick abc1234 feat: add login form
# pick def5678 fix typo
# pick ghi9012 wip
# pick jkl3456 fix: validate email properly

# Commands (change 'pick' to):
# p pick   -- keep as-is
# r reword -- keep commit, edit the message
# e edit   -- pause to amend the commit (add/remove files)
# s squash -- meld into previous commit (keep both messages)
# f fixup  -- meld into previous commit (discard THIS message)
# d drop   -- remove the commit entirely
# x exec   -- run a shell command after this commit

# Common pattern: squash WIP commits before a PR
# pick abc1234 feat: add login form
# f   def5678 fix typo            <-- fold into abc1234
# f   ghi9012 wip                 <-- fold into abc1234
# r   jkl3456 fix: validate email -- keep but edit message

# Result: 2 clean commits instead of 4 messy ones

# Reorder commits (just move lines in the editor)
# Add a fixup to a specific earlier commit
git rebase -i HEAD~5
# Move the fixup line directly under the commit it fixes

# Abort if something goes wrong
git rebase --abort

# Continue after resolving a conflict
git add .
git rebase --continue

# NOTE: only do this on local/private commits
# Force push required after rewriting shared branch history (careful!)

02 What is git bisect and how does it help you find bugs?

Debugging git bisect uses binary search to find the exact commit that introduced a bug. It cuts the search space in half at each step โ€” finding the culprit in O(log n) steps.

# Start bisecting
git bisect start

# Mark current state as bad (the bug exists here)
git bisect bad

# Mark a known good commit (before the bug existed)
git bisect good v1.2.0    # or a commit SHA

# Git checks out the midpoint commit automatically
# Test the code manually (or run automated test)

# Tell Git the result
git bisect good           # no bug here -- search in the later half
git bisect bad            # bug exists -- search in the earlier half

# Git keeps halving until it finds the first bad commit
# First bad commit is: abc1234 feat: update user model

# End the session (returns to original HEAD)
git bisect reset

# AUTOMATE with a test script
git bisect start
git bisect bad HEAD
git bisect good v1.2.0
git bisect run npm test -- --testPathPattern="auth"
# Git automatically runs the script and marks good/bad based on exit code
# Exit 0 = good, non-zero = bad

# Show what was tested during session
git bisect log

# Replay a bisect session
git bisect log > bisect.log
git bisect replay bisect.log

# Skip a commit you can't test (e.g., broken build)
git bisect skip

03 What are Git hooks and how do you use them?

Hooks Git hooks are shell scripts that run automatically at specific points in the Git workflow โ€” before or after commits, pushes, merges, and more. They live in .git/hooks/.

# Common hooks (stored in .git/hooks/):
# pre-commit     -- runs before commit is created (lint, format, tests)
# commit-msg     -- validates the commit message format
# pre-push       -- runs before push (run tests, block secrets)
# post-commit    -- after commit (notification, logging)
# post-merge     -- after merge (run npm install if package.json changed)
# pre-rebase     -- before rebase (safety checks)

# Example: pre-commit hook (make executable: chmod +x .git/hooks/pre-commit)
#!/bin/sh
# Run linter before every commit
npm run lint
if [ $? -ne 0 ]; then
  echo "Linting failed. Commit aborted."
  exit 1
fi

# Example: commit-msg hook (enforce conventional commits)
#!/bin/sh
COMMIT_MSG=$(cat "$1")
PATTERN="^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?(!)?: .{1,100}$"
if ! echo "$COMMIT_MSG" | grep -qE "$PATTERN"; then
  echo "Invalid commit message. Use: type(scope): description"
  echo "E.g.: feat(auth): add login endpoint"
  exit 1
fi

# Share hooks with the team (hooks in .git/ are NOT committed)
# Solution 1: husky (Node.js projects)
# npm install --save-dev husky
# npx husky init
# echo "npm run lint" > .husky/pre-commit

# Solution 2: store hooks in .githooks/ and configure core.hooksPath
git config core.hooksPath .githooks
# Now .githooks/ is committed and shared with the team

04 What is the Git object model? How does Git store data?

Internals Git is a content-addressable filesystem. Everything stored in Git is identified by a SHA-1 (or SHA-256 in new repos) hash of its content. Four object types make up the entire data model.

# Four object types:
# blob    -- stores file content (no filename, no metadata)
# tree    -- stores a directory listing (filenames + pointers to blobs/trees)
# commit  -- stores metadata (author, message, parent commits, pointer to root tree)
# tag     -- annotated tag (pointer to a commit with tagger info)

# Inspect objects
git cat-file -t abc1234    # show type: commit, tree, blob, or tag
git cat-file -p abc1234    # pretty-print the object

# A commit object looks like:
# tree 7a8f9b3...          (root tree of this snapshot)
# parent d4e5f6a...        (previous commit)
# author Alice <a@b.com> 1713888000 +0000
# committer Alice ...
#
# feat: add user login

# A tree object looks like:
# 100644 blob 3a4b5c... README.md
# 040000 tree 6d7e8f... src
# 100755 blob 9a1b2c... start.sh

# Objects are stored in .git/objects/ (first 2 chars = directory)
ls .git/objects/ab/
# cdef1234...  (the rest of the SHA is the filename)

# The DAG (Directed Acyclic Graph):
# Commits form a DAG -- each commit points to its parent(s)
# No commit can point to a future commit (acyclic)
# Branches and tags are just text files containing a SHA
cat .git/refs/heads/main
# abc1234def...  (the SHA of the latest commit on main)

# This is why Git is fast: identifying any state is just comparing SHAs

05 What is git reflog and how do you use it to recover lost commits?

Recovery The reflog is a local-only log of every position HEAD has been at. It records all checkout, commit, merge, rebase, and reset operations โ€” even those that “lost” commits.

# View the reflog
git reflog
# abc1234 HEAD@{0}: reset: moving to HEAD~3
# def5678 HEAD@{1}: commit: feat: add payment
# ghi9012 HEAD@{2}: commit: feat: add cart
# jkl3456 HEAD@{3}: commit: feat: add login

# Scenario: accidentally reset --hard and lost commits
git reset --hard HEAD~3   # oh no -- lost 3 commits!

# Recovery: find the SHA from reflog
git reflog                # find HEAD@{1} = def5678 (the lost commits)
git reset --hard def5678  # restore to that state (commits are back!)

# Or: create a branch at the lost commit
git switch -c recovery/lost-work def5678

# Reflog for a specific branch
git reflog show feature/auth
git reflog show origin/main

# Expire reflog entries (normally auto-managed)
# Unreachable objects survive in reflog for 30 days (90 days for reachable)
git reflog expire --expire=now --all  # force expire (careful!)
git gc                                # garbage collect expired objects

# Key scenarios where reflog saves you:
# - Accidental git reset --hard
# - Dropped commits after rebase
# - Accidentally deleted a branch
# - Rebased and lost commits

# Note: reflog is LOCAL only -- does NOT sync to GitHub
# Can't recover commits from another machine's lost work via reflog

06 What are Git worktrees?

Advanced Git worktrees allow you to check out multiple branches simultaneously into different directories โ€” sharing the same .git database. No need to stash or commit WIP to switch context.

# Create a worktree for a different branch
git worktree add ../project-hotfix hotfix/security-patch
# Creates a new directory ../project-hotfix checked out to hotfix/security-patch

# Create a worktree for a new branch
git worktree add -b feature/new-dashboard ../project-dashboard

# List all worktrees
git worktree list
# /home/alice/project          abc1234 [main]
# /home/alice/project-hotfix   def5678 [hotfix/security-patch]
# /home/alice/project-dashboard ghi9012 [feature/new-dashboard]

# Each worktree is a fully functional working directory
cd ../project-hotfix
# Edit, commit, push -- completely independent from main worktree

# Remove a worktree
git worktree remove ../project-hotfix       # remove if clean
git worktree remove --force ../project-hotfix  # force remove with changes

# Prune stale worktree entries
git worktree prune

# Worktrees vs Cloning:
# Clone: two full repos, independent .git databases, no shared objects
# Worktree: one .git database shared, branches linked -- push/fetch affects both
# Worktree is: lighter, no double-download, changes visible across worktrees

# Practical use case:
# - Working on feature, urgent bug reported
# - Without worktree: git stash, switch branch, fix, switch back, unstash
# - With worktree: just cd into the worktree, fix, cd back -- no interruption

07 What is the difference between git submodule and git subtree?

Advanced

  • git submodule โ€” embeds one Git repository inside another as a reference (pointer to a specific commit). The sub-repo stays separate; contributors must run git submodule update. Complex workflow but full separation.
  • git subtree โ€” merges another repository’s history directly into a subdirectory of the parent repo. No separate .git; works transparently. Contributors don’t need to know about it.
# --- git submodule ---
# Add a submodule
git submodule add https://github.com/org/library.git libs/library
git commit -m "chore: add library submodule"

# Clone a repo WITH submodules
git clone --recurse-submodules https://github.com/org/project.git
# or after plain clone:
git submodule init
git submodule update

# Update all submodules to latest
git submodule update --remote --merge

# .gitmodules file (auto-created)
# [submodule "libs/library"]
#     path = libs/library
#     url  = https://github.com/org/library.git

# --- git subtree ---
# Add a subtree
git subtree add --prefix=libs/library \
    https://github.com/org/library.git main --squash

# Pull updates
git subtree pull --prefix=libs/library \
    https://github.com/org/library.git main --squash

# Push changes back to the library repo
git subtree push --prefix=libs/library \
    https://github.com/org/library.git main

# Summary:
# Submodule: separate repos, complex workflow, team must know, updates are explicit
# Subtree:   merged history, transparent, simpler for consumers, harder to push back

08 What is advanced GitHub Actions syntax? Explain jobs, matrix, and reusable workflows.

GitHub Actions

# Matrix strategy -- run same job across multiple versions/OSes
jobs:
  test:
    strategy:
      matrix:
        os:           [ubuntu-latest, windows-latest, macos-latest]
        node-version: [18, 20, 22]
        exclude:
          - os: windows-latest
            node-version: 18    # skip this combination
      fail-fast: false          # run all matrix entries even if one fails
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci && npm test

# Job dependencies -- run jobs in sequence
jobs:
  lint:
    runs-on: ubuntu-latest
    steps: [...]
  test:
    needs: lint         # test only runs if lint passes
    runs-on: ubuntu-latest
    steps: [...]
  deploy:
    needs: [lint, test] # deploy waits for BOTH
    if: github.ref == 'refs/heads/main'
    steps: [...]

# Reusable workflow -- call from another workflow
# .github/workflows/deploy.yml (reusable)
on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
    secrets:
      DEPLOY_KEY:
        required: true

# Caller workflow
jobs:
  deploy-staging:
    uses: ./.github/workflows/deploy.yml
    with:
      environment: staging
    secrets:
      DEPLOY_KEY: ${{ secrets.STAGING_DEPLOY_KEY }}

09 What is branch protection in GitHub and how do you configure it?

GitHub Branch protection rules prevent accidental or unauthorised changes to important branches like main.

# GitHub Settings โ†’ Branches โ†’ Add branch protection rule
# Branch name pattern: main  (or main*, release/*, v*)

# Key protection options:
# โœ… Require a pull request before merging
#    - Required number of approvals: 1 or 2
#    - Dismiss stale reviews when new commits are pushed
#    - Require review from code owners (CODEOWNERS file)

# โœ… Require status checks to pass
#    - Choose specific CI checks (test, lint, security-scan)
#    - Require branches to be up to date before merging

# โœ… Require conversation resolution before merging
# โœ… Require signed commits (GPG/SSH signing)
# โœ… Include administrators (no bypass even for admins)
# โœ… Require linear history (no merge commits -- only squash or rebase merge)
# โœ… Restrict who can push to matching branches

# Rulesets (GitHub's newer system, replaces older branch protection)
# More flexible: can target tags too, apply to forks, stack multiple rulesets

# .github/CODEOWNERS
# Defines who must review changes to specific paths
# * @org/core-team                    # everyone needs core team review
# /src/billing/ @alice @bob           # billing needs Alice or Bob
# /docs/ @org/docs-team
# *.md @org/docs-team

# Force-push protection
# - Prevents git push --force on protected branches
# - Use 'Allow force pushes' only for specific users if needed

10 What is GitHub Dependabot and how does it work?

GitHub Security Dependabot automatically monitors your dependencies for vulnerabilities and outdated versions, opening pull requests to update them.

# .github/dependabot.yml -- configure Dependabot
version: 2
updates:
  # NPM dependencies
  - package-ecosystem: "npm"
    directory: "/"            # root package.json
    schedule:
      interval: "weekly"
      day: "monday"
      time: "09:00"
      timezone: "Europe/London"
    open-pull-requests-limit: 5
    labels: ["dependencies", "automated"]
    reviewers: ["alice", "bob"]
    assignees: ["carol"]
    commit-message:
      prefix: "chore(deps)"
      include: "scope"
    groups:
      dev-dependencies:
        patterns: ["@types/*", "eslint*", "prettier"]
        update-types: ["minor", "patch"]

  # Docker images
  - package-ecosystem: "docker"
    directory: "/"
    schedule:
      interval: "monthly"

  # GitHub Actions
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"

  # Python pip
  - package-ecosystem: "pip"
    directory: "/backend"
    schedule:
      interval: "weekly"

# Dependabot security alerts:
# Settings โ†’ Security โ†’ Enable Dependabot alerts
# Alerts appear in Security tab when a dependency has a known CVE

# Auto-merge safe updates
# Use a workflow: on pull_request from dependabot, auto-merge if tests pass
on:
  pull_request:
jobs:
  auto-merge:
    if: github.actor == 'dependabot[bot]'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm test
      - uses: github/auto-merge-action@v2

11 What are GitHub Environments and deployment protection rules?

GitHub Actions Environments in GitHub Actions represent deployment targets (staging, production) with their own secrets, variables, and protection rules like required reviewers and wait timers.

# Define environment in a job
jobs:
  deploy-production:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://myapp.com    # shown in deployment log

    steps:
      - uses: actions/checkout@v4
      - name: Deploy
        env:
          DEPLOY_KEY: ${{ secrets.PROD_DEPLOY_KEY }}  # environment secret
          API_URL:    ${{ vars.PROD_API_URL }}          # environment variable
        run: ./deploy.sh

# GitHub Settings โ†’ Environments โ†’ production:
# โœ… Required reviewers: @alice (must approve before job runs)
# โœ… Wait timer: 10 minutes (delay before deployment)
# โœ… Deployment branches: main only (prevent prod deploys from other branches)
# โœ… Allow administrators to bypass: false

# Multi-environment pipeline
jobs:
  deploy-staging:
    environment: staging
    steps: [...]

  deploy-production:
    needs: deploy-staging
    environment: production   # pauses for approval
    steps: [...]

# Environment vs repository secrets:
# Environment secret: only accessible to jobs targeting that environment
# Repository secret: accessible to ALL workflows in the repo
# Organisation secret: accessible to all repos in the org (with permission)

# View deployment history
# GitHub โ†’ Repository โ†’ Deployments (shows all environment deployments)

12 What is Semantic Versioning (SemVer) and how do you automate releases with Git?

Versioning

# SemVer format: MAJOR.MINOR.PATCH (e.g., 2.3.1)
# MAJOR -- breaking changes (incompatible API changes)
# MINOR -- new features, backwards compatible
# PATCH -- bug fixes, backwards compatible
# Pre-release: 1.0.0-beta.1, 1.0.0-rc.2
# Build metadata: 1.0.0+build.20260422

# With conventional commits, version bumps are deterministic:
# feat:  โ†’ MINOR bump (0.1.0 โ†’ 0.2.0)
# fix:   โ†’ PATCH bump (0.2.0 โ†’ 0.2.1)
# BREAKING CHANGE: or feat/fix! โ†’ MAJOR bump (0.2.1 โ†’ 1.0.0)

# semantic-release -- full automation
# .releaserc.json
{
  "branches": ["main"],
  "plugins": [
    "@semantic-release/commit-analyzer",     // determine version bump
    "@semantic-release/release-notes-generator", // generate CHANGELOG
    "@semantic-release/npm",                 // update package.json version
    "@semantic-release/github"               // create GitHub Release
  ]
}

# GitHub Actions workflow
- name: Release
  uses: cycjimmy/semantic-release-action@v4
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

# git-cliff -- generate CHANGELOG from conventional commits
git cliff --tag v1.2.0 -o CHANGELOG.md

# Manual release checklist
git tag -a v2.0.0 -m "Release 2.0.0: new auth system"
git push origin v2.0.0
# Then create GitHub Release from the tag

13 What are GitHub composite actions and how do you build one?

GitHub Actions A composite action is a reusable action that groups multiple steps into a single named unit, shareable across workflows and repositories.

# .github/actions/setup-and-test/action.yml
name: "Setup and Test"
description: "Install dependencies, lint, and run tests"
inputs:
  node-version:
    description: "Node.js version"
    required: false
    default: "22"
  working-directory:
    description: "Directory to run commands in"
    required: false
    default: "."
outputs:
  coverage:
    description: "Coverage percentage"
    value: ${{ steps.test.outputs.coverage }}

runs:
  using: "composite"
  steps:
    - uses: actions/setup-node@v4
      with:
        node-version: ${{ inputs.node-version }}
        cache: npm

    - name: Install
      shell: bash
      working-directory: ${{ inputs.working-directory }}
      run: npm ci

    - name: Lint
      shell: bash
      run: npm run lint

    - name: Test
      id: test
      shell: bash
      run: |
        npm test -- --coverage --coverageReporters=json-summary
        COV=$(cat coverage/coverage-summary.json | jq '.total.lines.pct')
        echo "coverage=$COV" >> $GITHUB_OUTPUT

# Use the composite action in a workflow
jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/setup-and-test  # local composite action
        with:
          node-version: "22"

# Publish to GitHub Marketplace
# Create a public repo with action.yml at the root
# Add branding.icon and branding.color to action.yml

14 What is git sparse-checkout and when do you use it?

Advanced Sparse checkout lets you check out only a subset of files from a large repository โ€” useful for monorepos where you only need to work on one service.

# Sparse checkout a single directory from a large monorepo
git clone --filter=blob:none --sparse https://github.com/org/monorepo.git
cd monorepo

# Add the paths you want
git sparse-checkout init --cone         # cone mode: full directories only
git sparse-checkout set services/auth   # only checkout services/auth/

# Or no-cone mode for fine-grained patterns
git sparse-checkout init --no-cone
git sparse-checkout set "services/auth" "libs/shared" "*.json"

# Add more paths
git sparse-checkout add services/billing

# List what's currently checked out
git sparse-checkout list

# Disable sparse checkout (full checkout)
git sparse-checkout disable

# Combine with partial clone for minimal download
git clone \
  --filter=blob:none \    # don't download blobs until accessed
  --sparse \               # sparse checkout
  https://github.com/org/monorepo.git

# Partial clone filters:
# --filter=blob:none          # clone without file contents (download on access)
# --filter=tree:0             # clone without trees (for archive/log use cases)
# --filter=combine:blob:none+tree:0  # minimal clone

# Use cases:
# - Monorepo: check out only your team's service (auth, billing, etc.)
# - CI: only fetch what the build needs
# - Large repos (linux kernel, chromium): don't need everything locally

15 What is git filter-repo and when do you use it?

Advanced git filter-repo (the modern replacement for git filter-branch) rewrites repository history โ€” used to remove secrets, large files, or sensitive data from all commits.

# Install: pip install git-filter-repo

# --- Remove a secret that was accidentally committed ---
# This removes the file from ALL history
git filter-repo --path secrets.env --invert-paths

# Remove a specific string from all files in history
git filter-repo --replace-text <(echo "ACTUAL_SECRET===[REMOVED]")

# --- Split a large repo into smaller ones ---
# Extract only the 'services/auth' directory as a new repo
git filter-repo --path services/auth/ --path-rename services/auth/:

# --- Remove large binary files ---
git filter-repo --strip-blobs-bigger-than 10M

# --- Rename an author in history ---
git filter-repo --email-callback '
    return email.replace(b"old@email.com", b"new@email.com")
'

# After filter-repo:
# 1. The repo history is rewritten -- all SHAs change
# 2. Force-push to remote (COORDINATE WITH TEAM)
git push --force-with-lease origin --all
git push --force-with-lease origin --tags

# 3. ALL contributors must re-clone or hard-reset
git fetch; git reset --hard origin/main

# IMPORTANT: If you committed secrets:
# 1. Immediately revoke/rotate the secret (assume it's compromised)
# 2. Then remove from history
# 3. NEVER rely on "I'll remove it from history" -- attackers may have it already

# GitHub also provides: BFG Repo Cleaner (faster for blob removal)
# bfg --delete-files secrets.env
# bfg --replace-text passwords.txt

16 What is GitHub Codespaces?

GitHub GitHub Codespaces is a cloud-hosted development environment (VS Code or JetBrains in a browser/desktop, backed by a cloud VM) configured via a devcontainer.json file in the repository. Zero-setup onboarding for any contributor.

# .devcontainer/devcontainer.json
{
  "name": "My Project",
  "image": "mcr.microsoft.com/devcontainers/node:22",

  "features": {
    "ghcr.io/devcontainers/features/docker-in-docker:2": {},
    "ghcr.io/devcontainers/features/github-cli:1": {}
  },

  "forwardPorts": [3000, 5432, 6379],

  "portsAttributes": {
    "3000": { "label": "App", "onAutoForward": "openBrowser" }
  },

  "postCreateCommand": "npm ci && npx prisma db push",

  "customizations": {
    "vscode": {
      "extensions": [
        "dbaeumer.vscode-eslint",
        "esbenp.prettier-vscode",
        "bradlc.vscode-tailwindcss"
      ],
      "settings": {
        "editor.formatOnSave": true,
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      }
    }
  },

  "remoteEnv": {
    "DATABASE_URL": "${localEnv:DATABASE_URL}"
  }
}

# Benefits:
# - Instant dev environment for new contributors (no "works on my machine")
# - Consistent environment for all team members
# - Access from any device (iPad, Chromebook)
# - Prebuild support: pre-create the environment to cut startup time from 5min to 30s

# Cost: billed by compute hours; free tier for personal accounts

17 What are GitHub Packages?

GitHub GitHub Packages is a package registry integrated with GitHub. Supports npm, Maven, Gradle, NuGet, RubyGems, Docker, and generic container images. Packages are linked to repositories and secured with GitHub authentication.

# Publish an npm package to GitHub Packages
# package.json
{
  "name": "@myorg/my-library",
  "publishConfig": {
    "registry": "https://npm.pkg.github.com"
  }
}

# Authenticate (one-time setup)
npm config set //npm.pkg.github.com/:_authToken $GITHUB_TOKEN

# Publish in GitHub Actions
- name: Publish to GitHub Packages
  run: npm publish
  env:
    NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}  # automatic token

# .npmrc for consumers
@myorg:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}

# Publish a Docker image to GitHub Container Registry (ghcr.io)
- name: Log in to GHCR
  uses: docker/login-action@v3
  with:
    registry: ghcr.io
    username: ${{ github.actor }}
    password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push
  uses: docker/build-push-action@v5
  with:
    push: true
    tags: ghcr.io/${{ github.repository }}:${{ github.sha }}

# Visibility:
# - Public repo โ†’ package is public
# - Private repo โ†’ package is private (requires auth to pull)
# - Can also set package visibility independently

18 What is git gc (garbage collection) and why does it matter?

Internals git gc cleans up the repository by packing loose objects into packfiles, removing dangling objects, and compressing data. Git runs it automatically but you can trigger it manually.

# Run garbage collection
git gc                        # auto: pack objects, prune old loose objects
git gc --aggressive           # more thorough, much slower (do rarely)
git gc --prune=now            # remove all unreachable objects now

# What git gc does:
# 1. Packs loose objects into .git/objects/pack/*.pack files
#    - Each pack contains many objects with delta compression
#    - Dramatically reduces disk space (e.g., 100MB โ†’ 15MB)
# 2. Removes unreachable objects older than 2 weeks (default)
#    - Objects not pointed to by any branch, tag, or reflog
# 3. Repacks existing packfiles if beneficial
# 4. Generates pack index for fast object lookup

# Check repo size before/after
git count-objects -v          # count loose objects + size
git count-objects -vH         # human-readable sizes

# Common output:
# count:       0        (loose objects)
# size:        0.00 KiB (loose object size)
# in-pack:     28432    (objects in packfiles)
# packs:       3        (number of packfiles)
# size-pack:   42.00 MiB

# Automatic gc triggers
git config gc.auto 6700        # run auto-gc after 6700 loose objects
git config gc.autoPackLimit 50 # run auto-gc after 50 pack files

# Check if gc would help
git fsck                       # verify integrity and find dangling objects
git fsck --unreachable         # show unreachable objects (GC candidates)

19 What is CODEOWNERS in GitHub?

GitHub The CODEOWNERS file defines which individuals or teams own specific parts of the repository. When a PR changes a file owned by someone, they are automatically added as a required reviewer.

# .github/CODEOWNERS (or CODEOWNERS at root, or docs/CODEOWNERS)

# Default owner for everything
*                           @org/core-team

# Frontend files
/src/components/            @alice @bob
/src/styles/                @carol
*.css                       @carol

# Backend
/src/api/                   @org/backend-team
/src/database/              @dave @org/dba-team

# Documentation
*.md                        @org/docs-team
/docs/                      @org/docs-team

# Infrastructure
/terraform/                 @org/platform-team
/.github/                   @org/devops-team
Dockerfile                  @org/devops-team

# Security-sensitive files
/src/auth/                  @security-lead @org/security-team
/src/crypto/                @security-lead

# Specific file
package-lock.json           @org/core-team

# Rules:
# - Last matching rule wins
# - @org/team-slug for GitHub teams (must have read access to the repo)
# - @username for individual users
# - Requires branch protection: "Require review from Code Owners"

# Check who owns a file
# GitHub: browse to a file โ†’ click the CODEOWNERS link in the blame view

20 What is git archive and how do you use it?

Commands git archive exports a snapshot of the repository content (without the .git/ directory) as a ZIP or TAR archive. Useful for creating source distribution packages.

# Export the current HEAD as a zip file
git archive HEAD --format=zip -o release.zip

# Export a specific tag
git archive v2.1.0 --format=zip -o my-project-v2.1.0.zip

# Export with a prefix (all files inside a folder in the zip)
git archive v2.1.0 --prefix=my-project-2.1.0/ --format=tar.gz \
    -o my-project-2.1.0.tar.gz

# Export only a specific subdirectory
git archive HEAD:src --format=zip -o src-only.zip

# Export a specific branch
git archive origin/main --format=zip -o main-snapshot.zip

# Stream directly to remote server
git archive HEAD | ssh user@server "cat > /deploy/release.tar"

# .gitattributes: exclude files from git archive
# .gitattributes
tests/           export-ignore    # exclude tests from release zip
.github/         export-ignore    # exclude GitHub-specific files
.husky/          export-ignore
*.test.js        export-ignore
Makefile         export-ignore

# Create a GitHub Release with a source archive
# GitHub automatically creates Source code .zip and .tar.gz on every tag
# These are git archive --prefix= equivalent

# Difference from zip/tar:
# git archive: only committed files, no .git, respects .gitattributes export-ignore
# zip: includes all files including untracked and .git/

21 What is git partial clone and how does it improve performance for large repos?

Performance Partial clone defers downloading some objects from the server until they are actually needed โ€” dramatically reducing initial clone time for large repositories.

# Blob filter -- clone without file contents (only trees and commits)
# Files are downloaded on demand when accessed
git clone --filter=blob:none https://github.com/org/large-repo.git

# Tree filter -- extremely minimal (only commits, no trees or blobs)
# Use for: querying history without needing any file contents
git clone --filter=tree:0 https://github.com/org/large-repo.git

# Combine with sparse checkout for large monorepos
git clone \
    --filter=blob:none \
    --sparse \
    https://github.com/org/monorepo.git
cd monorepo
git sparse-checkout set services/auth libs/shared

# Shallow clone -- only last N commits (no full history)
git clone --depth=1 https://github.com/org/repo.git   # most common for CI
git clone --depth=50 https://github.com/org/repo.git  # last 50 commits

# Deepen a shallow clone later
git fetch --unshallow                 # get full history
git fetch --deepen=10                 # get 10 more commits

# Blobless vs shallow:
# Shallow (--depth=1): fast clone, limited git operations (bisect, blame across history)
# Blobless (--filter=blob:none): full history available, no initial blob download
# For CI builds: --depth=1 is usually sufficient and fastest
# For development: --filter=blob:none better (can bisect, blame)

# GitHub CI optimisation
- uses: actions/checkout@v4
  with:
    fetch-depth: 0    # full history (needed for semantic-release, git log)
    # OR
    fetch-depth: 1    # fastest (only last commit, good for most CI jobs)

📝 Knowledge Check

🧠 Quiz Question 1 of 5

What does git rebase –interactive allow you to do that a regular rebase does not?





🧠 Quiz Question 2 of 5

What is the git reflog and why is it useful?





🧠 Quiz Question 3 of 5

What is the key difference between git submodule and git subtree?





🧠 Quiz Question 4 of 5

Why should you immediately rotate a secret after removing it from Git history with git filter-repo?





🧠 Quiz Question 5 of 5

What does –filter=blob:none in git clone do and when is it useful?