Git & GitHub in Depth: Theory, Practice, and the Commands Power Users Reach For
Git is a distributed version-control system: every clone is a full history, and branches are cheap pointers you move as you work. GitHub is a collaboration platform on top—pull requests, reviews, automation, and governance. This post is a course-style reference: how Git stores data under the hood, the workflows teams actually use, and the lesser-known commands that save you when history gets messy.
In short
Commits form a DAG of snapshots; branches and tags are names pointing at commits; remotes sync copies between machines. Master everyday add/commit/branch/merge/push, then learn recovery (reflog, reset, revert), inspection (log, show, bisect), and GitHub’s PR + Actions model for team delivery.
Git is not GitHub (but they ship together)
Git runs on your laptop, in CI, and on servers. It does not require GitHub, GitLab, or Bitbucket. GitHub hosts Git repositories and adds social and operational layers: issues, pull requests, branch protection, Actions, security scanning, and org-wide policies.
In platform work you will touch both: Git for what changed and why, GitHub (or similar) for who may merge it and how it reaches production. GitOps later treats Git as the control plane for infrastructure—this course is the foundation that makes that model legible.
Theory: what Git actually stores
Git’s database is a content-addressed store keyed by SHA-1 hashes (Git is migrating toward SHA-256 on newer versions; the mental model is unchanged). Four object types matter:
| Object | Contains | Analogy |
|---|---|---|
| Blob | File contents (no filename) | One version of a file’s bytes |
| Tree | Directory listing: names, modes, pointers to blobs/trees | A folder snapshot |
| Commit | Tree + parent commit(s) + author/committer + message | A labeled snapshot with lineage |
| Tag | Pointer to a commit (annotated tags add message/signature) | Release marker |
History is a directed acyclic graph (DAG): commits point to parent(s); merges have two parents. Branches are movable refs like refs/heads/main; HEAD usually points at a branch, which points at a commit. Detached HEAD means HEAD points directly at a commit—fine for inspection, risky for new work unless you create a branch.
Peek inside with plumbing commands (hidden gems #1):
git cat-file -t HEAD # commit
git rev-parse HEAD # full hash
git ls-tree -r HEAD --name-only | head
git show --stat HEAD
The three trees: working directory, index, repository
Git stages changes in layers:
- Working tree — files on disk you edit.
- Index (staging area) — what the next commit will record.
- Repository (HEAD) — committed snapshots.
# Edit files → stage → commit
git status
git add src/app.py docs/readme.md
git commit -m "feat: add health check endpoint"
# Modern restore (replaces much of checkout for paths)
git restore --staged src/app.py # unstage
git restore src/app.py # discard working-tree changes
.gitignore excludes untracked noise; .gitattributes controls line endings, diff drivers, and LFS filters. For secrets, ignore is not enough—use pre-commit hooks, git-secrets, or platform scanning; never rewrite public history to “remove” a leaked key without rotating the credential.
First repository: practical bootstrap
git init my-app
cd my-app
git branch -M main
echo "# My App" > README.md
git add README.md
git commit -m "chore: initial commit"
# Or clone existing work
git clone https://github.com/org/my-app.git
cd my-app
git remote -v
Clone copies the entire object database and checks out a branch. Fork (on GitHub) is a server-side clone under your account; you add upstream to track the original:
git remote add upstream https://github.com/org/my-app.git
git fetch upstream
git switch -c feature/sync main
git merge upstream/main
Branching and integration strategies
Branches are cheap. Teams pick a integration pattern:
| Model | Flow | Best when |
|---|---|---|
| Trunk-based | Short-lived branches; frequent merges to main | Strong CI, feature flags, platform teams |
| GitHub Flow | Branch → PR → merge to main → deploy | Web apps, continuous delivery |
| Git Flow | develop, release, hotfix branches | Scheduled releases, multiple versions in field |
git switch -c feature/rate-limit
# ... commits ...
git switch main
git pull --ff-only origin main
git merge --no-ff feature/rate-limit -m "Merge feature/rate-limit"
git push origin main
Merge records a merge commit (or fast-forward if possible). Rebase replays your commits on top of another tip—linear history, but rewrites SHAs; never rebase commits already pushed to a shared branch unless the team agrees.
git switch feature/rate-limit
git fetch origin
git rebase origin/main
# resolve conflicts → git add ... → git rebase --continue
git push --force-with-lease origin feature/rate-limit
--force-with-lease is the safe force-push: it fails if someone else updated the remote branch since your last fetch.
Remotes, fetch, pull, push
git fetch origin # download objects; don't merge
git log --oneline main..origin/main # what's on remote
git pull origin main # fetch + merge (or rebase with config)
git push -u origin feature/x # publish branch, set upstream
# Push a tag (releases)
git tag -a v1.2.0 -m "Release 1.2.0"
git push origin v1.2.0
Set defaults once:
git config --global init.defaultBranch main
git config --global pull.rebase false # or true for rebase pulls
git config --global push.autoSetupRemote true
Hidden and power-user Git commands
These are the commands tutorials skip until something breaks. Grouped by job.
Inspection and archaeology
git log --oneline --graph --decorate --all -20
git log -p -S "deleteUser" -- src/api/ # pickaxe: when string appeared/vanished
git log --follow -- path/old-name.py # history across renames
git blame -L 40,60 src/app.py
git show HEAD~3:src/app.py # file at ancestor commit
git shortlog -sn --since="3 months ago"
Stash, worktrees, sparse checkout
git stash push -u -m "wip before hotfix"
git stash list
git stash pop
git worktree add ../my-app-hotfix hotfix/1.0
git worktree list
git sparse-checkout init --cone
git sparse-checkout set src docs
Worktrees let you check out two branches in separate directories simultaneously—ideal for urgent hotfixes without disturbing your feature branch.
Recovery: reflog, reset, revert, cherry-pick
git reflog # where HEAD has been (local safety net)
git reset --soft HEAD~1 # undo commit; keep staged
git reset --mixed HEAD~1 # undo commit; unstage (default)
git reset --hard origin/main # match remote (destructive locally)
git revert abc1234 # new commit that undoes abc1234 (safe on main)
git cherry-pick def5678 # apply one commit elsewhere
On shared branches, prefer revert over reset + force push. Reflog expires (~90 days default); it is not a backup service.
Interactive rebase and commit hygiene
git rebase -i origin/main
# pick / squash / fixup / drop commits in editor
git commit --amend --no-edit
git commit --fixup=abc1234 && git rebase -i --autosquash origin/main
Finding bugs: bisect
git bisect start
git bisect bad # current commit is broken
git bisect good v1.0.0 # known good tag
# test each step → git bisect good|bad until culprit found
git bisect reset
Automate with git bisect run ./scripts/test.sh.
Cleaning, repairing, and deep maintenance
git clean -fdn # dry-run: remove untracked
git fsck --full
git gc --prune=now
git count-objects -vH
# Submodule (legacy but everywhere)
git submodule update --init --recursive
# Rerere: reuse recorded conflict resolutions
git config --global rerere.enabled true
For history rewriting at scale (remove large file from all history), use git filter-repo (preferred over deprecated filter-branch).
Plumbing worth knowing
git update-ref refs/heads/main HEAD~1 # move branch (careful)
git symbolic-ref HEAD
git merge-base main feature/x
git diff main...feature/x # changes on feature since diverged
git diff main..feature/x # all differences between tips
# Debug transport
GIT_TRACE=1 GIT_CURL_VERBOSE=1 git fetch origin
GIT_SSH_COMMAND="ssh -v" git fetch origin
Useful aliases (team templates)
git config --global alias.lg "log --oneline --graph --decorate -20"
git config --global alias.last "log -1 HEAD --stat"
git config --global alias.unstage "restore --staged"
git config --global alias.please "push --force-with-lease"
Merge conflicts: read the markers
<<<<<<< HEAD
our change
=======
their change
>>>>>>> feature/rate-limit
Edit to the intended result, git add resolved files, then continue merge or rebase. Tools: git mergetool, VS Code, IntelliJ. For complex repeats, enable rerere.
Signing, hooks, and policy
- SSH commit signing —
git config gpg.format sshand setuser.signingkey; GitHub shows “Verified.” - Hooks —
pre-commit(format/lint),commit-msg(conventional commits),pre-push(tests). Husky and pre-commit frameworks wrap these. - Conventional Commits —
feat:,fix:,chore:enable changelog automation and clearer history.
GitHub in depth: collaboration and delivery
GitHub’s core unit of review is the pull request (PR): propose merging a branch, discuss diffs, run checks, then merge with a strategy.
| Merge option | Result |
|---|---|
| Create a merge commit | Preserves branch topology; merge commit on base |
| Squash and merge | One commit on base; good for noisy feature branches |
| Rebase and merge | Linear history; replays commits (no merge commit) |
Branch protection enforces reviews, status checks, signed commits, and linear history. CODEOWNERS auto-requests reviewers by path. Environments gate deployments with required approvers and secrets scoped per environment.
Issues, projects, and labels
Issues track work; link PRs with Fixes #42 to auto-close. GitHub Projects (v2) are kanban/roadmap views across repos. Labels and milestones keep release trains visible.
GitHub Actions (CI/CD on events)
# .github/workflows/ci.yml (minimal pattern)
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: npm ci && npm test
Secrets live in repo or org settings—never commit them. Use OIDC to assume cloud roles without long-lived keys. Reusable workflows and composite actions DRY pipelines across repos. For a full treatment—workflows, environments, matrices, GHCR, branch protection, and GitOps handoff—see GitHub CI/CD in depth.
Security and supply chain
- Dependabot — dependency update PRs.
- Code scanning — CodeQL static analysis on PRs.
- Secret scanning — blocks or alerts on committed tokens.
- Rulesets — org-wide branch and tag policies (successor patterns to legacy branch protections).
GitHub CLI (gh)
gh auth login
gh repo clone org/my-app
gh pr create --fill
gh pr checkout 123
gh pr merge 123 --squash --delete-branch
gh run list --workflow=ci.yml
gh api repos/{owner}/{repo}/pulls/123 --jq .title
The CLI mirrors the UI for scripting reviews and releases from your terminal—especially useful in tmux or remote dev boxes.
SSH keys, tokens, and authentication
Prefer SSH keys for Git transport ([email protected]:org/repo.git). For HTTPS and API access, use fine-grained personal access tokens with least privilege. In CI, use GitHub Apps or OIDC—not a personal PAT tied to a human who might leave the company.
# ~/.ssh/config
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_github
IdentitiesOnly yes
Monorepos, submodules, and LFS
- Monorepo — one repo, many services; path filters in CI run only affected jobs.
- Submodules — pin another repo at a commit; powerful but easy to footgun (
git submodule update --init). - Git LFS — stores large binaries outside the main object graph; costs and clone behavior differ—know your host’s LFS billing.
Course lab progression (suggested)
- Create a repo, make ten commits on
main, practicegit log --oneline --graph. - Branch feature, open a PR on GitHub, squash merge, delete branch.
- Cause a merge conflict intentionally; resolve with
git mergeand again withgit rebase. - Use
git stashandgit worktreeduring a simulated hotfix. - “Lose” a commit, recover with
git reflog. - Run
git bisecton a small script with a known bad commit. - Add a GitHub Actions workflow that runs tests on every PR.
- Enable branch protection requiring one review and green CI.
- Configure
ghand merge a PR from the terminal. - Capstone: fork an open-source repo, fix a doc typo, open PR upstream.
Common pitfalls (exam and production)
- Committing secrets — rotate the credential; history rewrite is painful and public forks may retain copies.
- Force push on shared branches — breaks teammates; use revert or coordinated maintenance windows.
- Huge binaries in Git — bloats forever; use LFS or artifact storage.
- Merge vs rebase religion — pick a team rule; document it in CONTRIBUTING.md.
git pullwithout understanding — surprise merge commits; preferfetch+ explicit merge/rebase.- Ignoring CI on green button day — branch protection exists because humans skip tests under pressure.
- Submodule drift — CI checks out but forgets
submodule update --init.
How this connects to platform work
Git history is audit evidence. GitHub (or GitLab or Bitbucket) is where change management meets automation: PR reviews map to change advisory boards; CI (GitHub Actions, GitLab CI/CD, or Bitbucket Pipelines) maps to build pipelines; protected main maps to production gates. When you adopt GitOps, the same skills apply—only the artifacts in the repo are manifests instead of application source.
For infrastructure-as-code mechanics, continue with Terraform & IaC for everyone. For calm incident response when a bad deploy lands, see Incidents & disaster response.
Further reading
- Pro Git (Scott Chacon) — free online book; deep and clear
- Git documentation —
git help <command>and official reference - GitHub Docs — Actions, security, branch protection, rulesets
- Atlassian Git tutorials — visual branching lessons
- Conventional Commits — message format for automation
Blog index · GitHub CI/CD in depth · GitLab CI/CD in depth · GitOps principles · Terraform & IaC