Module 0.4: Git and GitHub for Your Portfolio
Phase: 0, Foundations
Level: Beginner
Estimated time: 4 to 6 hours
Prerequisites: Module 0.1 (you have git installed and have made a first commit).
Portfolio thread: turns eo-portfolio into a repository with a clean, professional history, a merged pull request, and a tagged release, which is what a hiring manager actually looks at.
Why this module
Module 0.1 got you committing and pushing. That is enough to save work, but not enough to look like an engineer or to collaborate on a team, and every job in this field assumes both. Recruiters and hiring managers open your GitHub before they read your resume, so a messy history of "stuff" commits, secrets checked in, or a single giant commit quietly costs you. This module makes you fluent: branches, pull requests, resolving conflicts, keeping data and secrets out, writing readable history, and undoing mistakes safely. It is short, but it is one of the highest-return hours in the whole course.
Learning outcomes
After this module you can:
- Work on a feature branch and merge it, and explain merge versus rebase.
- Open, review, and merge a pull request, and say why teams work this way.
- Create and resolve a merge conflict without panic.
- Keep data, secrets, and large files out of the repository with a good
.gitignore. - Write clear commit messages and undo changes safely (restore, revert, reset).
- Tag a release and understand forks for collaboration.
Concept lessons (about 2 hours)
Lesson 1: The model, quickly
Recall from Module 0.1: the working directory holds your files, you add changes to a staging area, commit them as a labeled snapshot, and push to GitHub. A repository is the whole history of those snapshots. Everything below builds on this. Two habits from the start: commit small and often (one logical change per commit), and write messages that a stranger, or future you, can read.
Lesson 2: Branches and merging
A branch is an independent line of work. You create a feature branch, make commits there, and merge it back into main when it is ready, which keeps main always working. The everyday flow:
git switch -c feature/cube-loader # create and switch to a branch
# ...edit, add, commit...
git switch main
git merge feature/cube-loader # bring the work into main
If main has not moved, git does a fast-forward (just moves the pointer). If both moved, git makes a merge commit. There is also rebase, which replays your commits on top of the latest main for a linear history; learn merge first and treat rebase as an intermediate tool, because rebasing shared branches can rewrite history others depend on.
Lesson 3: Pull requests and review
On GitHub you rarely push straight to main. You push your branch and open a pull request (PR): a proposal to merge, with a diff, a description, and space for review comments and automated checks (your CI from Module 11). Even working solo, using PRs on your own repository is worth it, because it shows collaboration habits and gives CI a place to run before code lands. Contributing to someone else's project uses the same idea via a fork: you copy their repo, branch, and open a PR back to them.
Lesson 4: Merge conflicts
A conflict happens when two branches change the same lines and git cannot decide which to keep. It is normal, not a failure. Git marks the spot:
<<<<<<< HEAD
your version
=======
their version
>>>>>>> feature/other
You edit the file to the correct final result, remove the markers, then add and commit. The skill is staying calm, reading both sides, and testing after resolving. Practicing this once removes most of the fear.
Lesson 5: Hygiene and safety
Three rules that separate professional repositories from amateur ones:
- Never commit data, secrets, or large files. Add a
.gitignorefordata/,__pycache__/,.ipynb_checkpoints/, and secret files (*.env, keys). Committed secrets are a security incident, and a large file bloats the repo forever. Keep big media external and reference it by link. - Write readable history. Prefer a convention like
feat:,fix:,docs:,test:prefixes; it makes the log scannable and is common in industry. - Undo safely. Use
git restoreto discard un-staged changes to a file,git revertto add a new commit that undoes an earlier one (safe on shared history), and reservegit resetfor local, un-pushed work. When in doubt, prefer revert, which never loses history.
And git tag v0.1.0 marks a release point you can return to.
Guided lab (about 2 to 3 hours): a professional eo-portfolio history
Step 1: A feature branch and a pull request
On your eo-portfolio, create a branch, make a small improvement (for example a README badge or a docstring), push it, and open a pull request on GitHub. Review your own diff, let CI run, and merge it. You now have a merged PR on your profile.
Step 2: Create and resolve a conflict
Deliberately edit the same README line on two branches, merge one, then merge the other to trigger a conflict, and resolve it. Doing this on purpose once is the best way to never fear it.
Step 3: A real .gitignore and tested helpers
Add a .gitignore, and add two small tested helpers to your repo that encode the hygiene rules:
"""gitutil.py: repository hygiene helpers (pure, testable)."""
from __future__ import annotations
import re
_CONVENTIONAL = re.compile(r"^(feat|fix|docs|test|refactor|chore|perf|build|ci)(\([\w.\-/]+\))?: .+")
def is_conventional_commit(message):
"""True if the first line follows the conventional-commit style."""
if not message or not message.strip():
return False
return bool(_CONVENTIONAL.match(message.strip().splitlines()[0]))
def gitignore_covers(gitignore_text, path):
"""Rough check that a path would be ignored by these .gitignore rules."""
for raw in gitignore_text.splitlines():
line = raw.strip()
if not line or line.startswith("#"):
continue
pat = line.rstrip("/")
if pat.startswith("*.") and path.endswith(pat[1:]):
return True
if path == pat or path.startswith(pat + "/") or ("/" + pat + "/") in ("/" + path):
return True
return False
# tests/test_gitutil.py
from eo_portfolio.gitutil import is_conventional_commit, gitignore_covers
def test_conventional_commit():
assert is_conventional_commit("feat: add cube loader")
assert is_conventional_commit("fix(cube): handle empty search")
assert not is_conventional_commit("updated stuff")
assert not is_conventional_commit("")
def test_gitignore_covers():
gi = "data/\n__pycache__/\n*.env\n"
assert gitignore_covers(gi, "data/scene.tif")
assert gitignore_covers(gi, "src/__pycache__/x.pyc")
assert gitignore_covers(gi, "secret.env")
assert not gitignore_covers(gi, "src/cube.py")
Step 4: Tag a release and commit
Tag the state at the end of Foundations, then push tags.
git tag -a v0.1.0 -m "Foundations complete"
git push --tags
git add .gitignore src/eo_portfolio/gitutil.py tests/test_gitutil.py
git commit -m "docs: add gitignore and repo hygiene helpers"
git push
Checkpoint
Self-check (answers below).
- Why work on a feature branch instead of committing straight to
main? - You committed an API key by mistake. Why is it not enough to just delete it in a new commit?
- What is the difference between
git revertandgit reset, and which is safe on shared history? - What belongs in
.gitignorefor an EO project, and why?
Interview-style questions (practice out loud).
- Walk me through your workflow from a new feature to it landing on
main. - How do you resolve a merge conflict, and how do you avoid them?
- How would you contribute a fix to an open-source library you use?
- What does a clean commit history tell a reviewer about you?
Answers. (1) A branch keeps main always working and lets you develop and run CI in isolation before merging.
(2) The key remains in the repository's history, so it must be treated as compromised and rotated; removing it in a later commit does not erase it from past commits.
(3) git revert adds a new commit that undoes a previous one and is safe on shared branches because it preserves history; git reset moves the branch pointer and can discard commits, so it is only safe on local, un-pushed work.
(4) Data (data/), caches (__pycache__/, .ipynb_checkpoints/), and secrets (*.env, keys), because data and large files bloat the repo and secrets are a security risk.
Deliverable
An eo-portfolio with a merged pull request, at least one deliberately created and resolved merge conflict, a real .gitignore, tested gitutil.py hygiene helpers, and a v0.1.0 tag. Your repository now reads as professional at a glance.
What a hiring manager sees
Your GitHub is opened before your resume. A clean, conventional commit history, a merged PR, no secrets or data in the tree, and a tagged release signal someone ready to work on a team, which for many reviewers matters as much as the models. Conversely, a repo with committed data, secrets, or a single monolithic commit raises doubts no matter how good the science is.
Currency note
Git itself is stable. The GitHub web interface and CLI (gh) evolve, and defaults change (for example git switch and git restore are the modern alternatives to older checkout usage), so confirm current commands and UI when you follow along. Pin any Python dev dependencies in your Module 0.1 environment file.