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
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:
- History - see exactly what changed, when, by whom, and (if commit messages are good) why.
- Undo - return any file, or the whole project, to any past state.
- Branching - work on a change in isolation without disturbing the working version, then merge it back.
- Collaboration - many people change the same project at once, and the VCS reconciles the work.
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:
- repository (repo) - the project plus its entire history.
- commit - one saved snapshot, with an author, timestamp, message, and a unique hash like
27e293d7. - branch - a movable pointer to a line of commits (e.g.
main). Cheap to create; the heart of git's workflow. - remote - a copy of the repo hosted elsewhere (e.g. on GitHub), conventionally named
origin. - clone / fork - copy a repo to your machine / copy it to your own account on the host.
Why it's essential (and where it bites)
What's good
- Nothing is ever lost. Every committed state is recoverable. "I broke it" becomes a five-second revert, not a crisis.
- Blame is a feature, not an accusation.
git blametells you which commit last touched a line - invaluable for "why is this here?" and for incident forensics. - Branches make experiments free. Try a risky change on a branch; if it doesn't work out, delete the branch and it's gone.
mainnever saw it. - It's the substrate for everything else. Code review, CI/CD, infrastructure-as-code, and automated deploys all hang off git. Our GitHub Actions and Terraform only work because every change is a reviewable commit.
- It's a security control. A signed, reviewed, immutable history is an audit trail. Branch protection turns "who can change production?" into an enforceable policy.
Where it bites
- The learning curve is real. The mental model (below) is the part people skip, and skipping it is why git feels like memorized incantations. Ten minutes on the model saves ten hours of confusion.
- Merge conflicts feel scary the first time. They're just git saying "two people changed the same lines; you decide." Normal, and fixable.
- History can be rewritten - dangerously.
rebase,--force, andreset --hardare powerful and can lose work or rewrite shared history. Respect them. - Git is bad at big binaries. It's built for text. Large media bloats the repo forever (see Git LFS, and "secrets in history" below - the "forever" cuts both ways).
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.
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:
- It shows the exact diff - every added and removed line - so a reviewer sees precisely what would change.
- It runs automated checks (CI) before anything merges - linting, tests, our HTML validator and link checker.
- It's a conversation - reviewers comment on specific lines, you push fixes, the PR updates.
- When approved and green, it merges to
main- and on our repo, that triggers the deploy.
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:
- 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 tomain. - 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:
- Never commit secrets. No API keys, tokens, passwords, or private keys in the repo. Use environment variables or a secrets manager, and a
.gitignorethat covers.envand friends. If one slips in, rotate it - history is forever. - 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.
- 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. - 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.
- 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.
- Treat third-party PRs as untrusted input. Review forked PRs carefully and never let them run with your secrets. The
pull_request_targetfootgun has leaked credentials on major projects - see our breach kill chains and the GitHub Actions security section. - 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
- Pro Git - the free, official book. The first three chapters are the best git investment you can make.
- Learn Git Branching - an interactive visual playground for branches, merge, and rebase.
- GitHub docs: Getting started and the pull requests guide.
- GitHub git cheat sheet (PDF) - the daily commands on one page.
- Conventional Commits - a simple convention for commit messages (the
feat:/fix:/docs:style you'll see in our log). - gitleaks - scan a repo (and its history) for committed secrets.
- On this site: How We Use GitHub Actions, How We Use Terraform, How We Deploy, and the contributor guide.
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.