Git & Version Control

Version control is a time machine for your work and the foundation of every collaborative codebase. This page teaches the mental model from scratch, then shows it running for real - the exact branch, review, and ship workflow behind this website, and the security rules that keep secrets out of history. Whole-site source is public; you can read every commit.

· · Vendor-neutral · View source on GitHub

The honest version: version control is a database of every change ever made to a set of files, with the power to compare, undo, branch, and merge that history. Git is the tool that won - a fast, distributed version control system where everyone has a full copy of the history. GitHub (or GitLab, or Bitbucket) is a website that hosts git repositories and adds collaboration on top: pull requests, reviews, issues, automation. People conflate "git" and "GitHub," but they're different layers - git is the engine, GitHub is one place to park it. This page teaches the engine, then shows the workflow we actually run on it.

What you'll need: git installed and a free GitHub account. A terminal helps, but you can do a surprising amount in the GitHub web UI alone.

On this page

  1. What version control actually is
  2. Why it's essential (and where it bites)
  3. Git's mental model: four places your code lives
  4. The commands you'll actually use
  5. Branches & pull requests
  6. How CSOH actually uses git
  7. Concepts that bite newcomers
  8. Security baseline
  9. Further reading

What version control actually is

A version control system (VCS) records snapshots of your files over time. Instead of report-final.doc, report-final-v2.doc, report-FINAL-actually.doc, you keep one set of files and the system remembers every state they've ever been in. That unlocks four superpowers:

Centralized vs distributed

Older systems (CVS, Subversion) were centralized: one server held the history, and you checked files out from it. Git is distributed - when you clone a repository, you get the entire history on your machine. You can commit, branch, view history, and diff completely offline; you only need the network to share. That design (plus its speed) is why git is now the near-universal default, and why "version control" in practice usually means "git."

A quick vocabulary you'll see everywhere:

Why it's essential (and where it bites)

What's good

Where it bites

Git's mental model: four places your code lives

Almost every confusing git moment comes from not knowing which of four places a file is in. Learn this diagram and git stops being magic.

The four areas of git and the commands that move work between them Working directory to staging area via git add, staging to local repository via git commit, local to remote via git push, and remote back to working directory via git pull or clone. Where your work lives - and the commands that move it Working directory your files, as edited right now on disk Staging area changes chosen for the next commit Local repository committed history on your machine Remote (GitHub) shared copy everyone pulls from add commit push git pull / git clone (remote → your working copy)
You add edits to staging, commit staging into local history, and push local history to the remote. pull (or clone) brings others' work back down. A commit is a snapshot; a branch is just a sticky note pointing at one.

The staging area is the bit newcomers find odd and pros find indispensable: it lets you commit some of your changes and not others, so each commit is one coherent idea. "Edit everything, then assemble tidy commits" is a normal and good workflow.

The commands you'll actually use

You can get a long way with about a dozen commands. Here's the daily core:

# Start: copy a repo to your machine (you get the full history)
git clone https://github.com/CloudSecurityOfficeHours/csoh.org
cd csoh.org

# Look around - run status constantly; it tells you exactly where things stand
git status                 # what's changed, staged, and on which branch
git log --oneline -10      # recent history, one line each
git diff                   # what you've changed but not yet staged

# The core loop
git switch -c fix-typo     # create + move to a new branch (older: git checkout -b)
# ...edit files...
git add faq.html           # stage a specific file (git add -p to stage by chunk)
git commit -m "Fix typo in FAQ answer about CSPM"
git push -u origin fix-typo   # publish your branch to the remote

# Staying current
git switch main
git pull                   # fetch + merge the latest main from origin

Two habits that save beginners the most pain: run git status obsessively (it even suggests the command you probably want next), and write commit messages that finish the sentence "If applied, this commit will…" - imperative and specific, like the real ones in our history.

Branches & pull requests

A branch is an isolated line of work. You branch off main, make commits, and when the change is ready you ask for it to be merged back. On a host like GitHub, that request is a pull request (PR) - the unit of collaboration and review.

A PR is where the value of version control becomes obvious:

The everyday flow is: branch → commit → push → open PR → review → merge → delete branch. Small, focused PRs get reviewed fast; giant ones sit for days. Keep each PR to one idea.

How CSOH actually uses git

This whole website lives in one public repository. Reading it is a fine way to learn - real commits, real PRs, real history. Here's the workflow it runs on.

If you're contributing: fork → branch → PR

You don't need write access to help. The standard open-source flow (documented in our CONTRIBUTING.md) is:

# 1. Fork the repo on GitHub (creates your own copy)
# 2. Clone YOUR fork
git clone https://github.com/YOUR-USERNAME/csoh.org.git
cd csoh.org

# 3. Branch for your change
git switch -c add-news-source

# 4. Edit, stage, commit
git add update_news.py
git commit -m "Add TechCrunch security feed"

# 5. Push to your fork and open a pull request on GitHub
git push -u origin add-news-source

For a one-line fix you don't even need a terminal: GitHub's pencil icon lets you edit a file in the browser, and "Commit changes" offers to open the PR for you. Either way, a maintainer reviews it.

If you're a maintainer: small changes, straight to main

Maintainers with write access can commit to main directly for routine, low-risk edits - which immediately triggers the build-and-deploy pipeline. That speed is exactly why the safety net below matters: the protections and automated checks are what make a direct push to production safe to do.

What happens the moment something lands on main

A merge (or push) to main is the deploy trigger. Two GitHub Actions workflows fire automatically:

  1. Housekeeping (site-update-deploy.yml) - regenerates Subresource Integrity hashes, checks URL safety, normalizes URLs, builds preview images and the sitemap, and commits the results back to main.
  2. Deploy (deploy.yml) - builds the site once and publishes it active/active to three clouds (AWS, GCP, Azure) behind Cloudflare, authenticating to each with keyless OIDC.

So a two-character typo fix in a PR becomes a fully-tested, multi-cloud production deploy with no manual steps. That pipeline is the entire reason we keep changes small and reviewable - git is what makes "ship 20 times a day, safely" possible. Full details live in How We Deploy.

The PR checklist we actually use

Before a change merges, it should clear the bar in our contributor guide: the change is clear and focused (one idea per PR), spelling and links check out, it looks right in both light and dark mode, the commit message is descriptive, and CI is green - the lint workflow (actionlint, ruff, yamllint) and the W3C HTML validator both pass. Green checks are a merge requirement, not a suggestion.

Concepts that bite newcomers

1. Merge conflicts are normal

When two branches change the same lines, git can't guess who's right, so it marks the spot with <<<<<<< / ======= / >>>>>>> and asks you to choose. Edit the file to the version you want, delete the markers, git add it, and continue. That's the whole ritual - no data is lost, and small frequent merges keep conflicts tiny.

2. merge vs rebase

git merge joins two branches and records a merge commit - true history, occasionally messy. git rebase replays your commits on top of the latest base for a linear, tidy history - but it rewrites your commits (new hashes). Rule of thumb: rebase your own un-pushed work to tidy it; don't rebase commits other people have already pulled.

3. git pull --rebase beats a wall of merge commits

If main moved while you were working, git pull --rebase replays your local commits on top of the new main instead of creating a merge bubble. Our CI uses exactly this pattern when a workflow needs to commit back to a branch that may have moved: git push || (git pull --rebase && git push).

4. Undo: revert (safe) vs reset (sharp)

git revert <commit> makes a new commit that undoes an old one - safe on shared branches because it doesn't rewrite history. git reset moves the branch pointer backward and can discard commits (--hard also throws away working changes). Reach for revert on anything already pushed; keep reset --hard for local-only cleanup you're sure about.

5. .gitignore: keep junk and secrets out

A .gitignore file lists paths git should never track - build output, dependency folders, local config, and (critically) anything secret. Our repo ignores things like .env, the Python .venv/, and Terraform's .terraform/ and state. Set this up before your first commit; un-committing something later is harder than never committing it.

6. Committed a secret? The history remembers - rotate it

This is the one that catches people. Deleting a secret in a new commit does not remove it from history - it's still in the previous commit, forever, for anyone who clones the repo. If you commit a key, assume it's compromised: rotate (invalidate) it immediately, then scrub history with BFG or git filter-repo. Prevention beats cleanup - see the security baseline next.

7. Detached HEAD isn't broken

Checking out a specific commit instead of a branch puts you in "detached HEAD" - you're looking at history, not on a branch. Look around all you like; if you want to keep changes made here, create a branch (git switch -c new-branch) before moving away.

Git history is permanent by design. That's a superpower for auditing and a trap for secrets - a key committed once is a key leaked forever. Rotate, don't just delete. - the rule we drill into every contributor

Security baseline

Version control is a security boundary - it decides what reaches production and leaves an audit trail of who changed what. As a cloud-security community, this is the part we care most about:

  1. Never commit secrets. No API keys, tokens, passwords, or private keys in the repo. Use environment variables or a secrets manager, and a .gitignore that covers .env and friends. If one slips in, rotate it - history is forever.
  2. Scan for secrets automatically. Turn on GitHub secret scanning + push protection, and add a pre-commit hook with gitleaks or TruffleHog so a key is caught before it's ever pushed.
  3. Protect main. Branch protection rules - require a PR, require review, require green status checks, block force-pushes - turn "don't break production" into something the platform enforces, not something you hope for.
  4. Sign your commits. GPG- or SSH-signed commits earn a "Verified" badge and prove a commit really came from you, defeating spoofed author names. Require signed commits on protected branches for a high-assurance history.
  5. Turn on 2FA, and use least-privilege tokens. Hardware or app-based 2FA on your GitHub account; prefer fine-grained, short-lived personal access tokens scoped to one repo over classic all-powerful PATs.
  6. Treat third-party PRs as untrusted input. Review forked PRs carefully and never let them run with your secrets. The pull_request_target footgun has leaked credentials on major projects - see our breach kill chains and the GitHub Actions security section.
  7. Pin and review your CI. The automation that runs on every push is itself attackable; pin Actions to commit SHAs and give workflows least-privilege permissions. (We cover this in depth on the Actions page.)

Further reading

Questions?

Bring them to Friday Zoom. Whether you're stuck on your first merge conflict or designing a branch-protection policy for a regulated environment, someone in the room has been there. The best way to learn git is to use it on something real - so contribute to this site and we'll review your first PR together.