Git
Git
Sub-chapter 1 of Git & CI/CD · Collaboration
Git is not source control. Git is the protocol your team uses to communicate intent through code. The commit message is a memo to your future colleagues. The branch is a proposal. The PR is a conversation. The merge is a decision.
If you treat Git as a save button, you will write commits no one can review, branches no one can rebase, and history no one can debug. If you treat it as communication, every other engineering practice - code review, CI/CD, incident forensics - gets dramatically easier.
Outline
- The three trees - working directory, index (staging), HEAD
- Commits, branches, refs - what they actually are
- Local workflow -
add,commit,status,diff,log - Branching -
branch,checkout/switch,merge,rebase - Remotes -
clone,fetch,pull,push, tracking branches - Rewriting history -
commit --amend, interactive rebase,reset - Recovering from disaster -
reflog,cherry-pick, "undo" patterns - GitHub workflow - PRs, reviews,
ghCLI - Hygiene -
.gitignore, commit message conventions, signed commits - Welzin conventions - branch names, commit format, PR template
101 Primer
The model: three trees and a graph
Git is just two things:
- A content-addressed store of files (blobs), folders (trees), and snapshots (commits).
- A graph of commits, where each commit points to its parents.
A branch is just a pointer to a commit. HEAD is a pointer to a branch (or to a commit directly, "detached"). Once you see Git this way, every command stops being magic.
work tree → index → HEAD
(edit) (stage) (commit)
↓ ↓ ↓
vim foo git add foo git commit
Configure once, properly
git config --global user.name "Your Name"
git config --global user.email "you@welzin.ai"
git config --global init.defaultBranch main
git config --global pull.rebase true # no merge bubbles on pull
git config --global rebase.autoStash true # let rebase shelve uncommitted work
Generate an SSH key per machine, add it to GitHub:
ssh-keygen -t ed25519 -C "you@welzin.ai"
cat ~/.ssh/id_ed25519.pub # paste this into GitHub → SSH keys
The daily loop
git switch -c fix/login-redirect # new branch
# ...edit...
git status # what changed
git diff # what changed in detail
git add -p # stage interactively, NOT git add .
git commit -m "fix: redirect to /app after SSO callback"
git push -u origin fix/login-redirect
gh pr create --fill # opens a PR
The -p (patch) on git add is non-negotiable. It forces you to look at every hunk before staging it. You will catch a stray console.log every week.
Commit messages that don't embarrass you
Format we use at Welzin:
<type>: <imperative summary, ≤72 chars>
<optional body explaining WHY, wrapped at 72>
<reference to issue or context if any>
Types: feat, fix, refactor, docs, test, chore, perf. The why in the body is the part future-you will thank present-you for. The diff already shows the what.
Bad: update stuff, fix bug, wip.
Good: fix: avoid race when two SSO callbacks race for the same nonce.
Branches and rebasing
A branch is cheap. Make one per unit of change. If a PR ends up bundling "the feature + a refactor + a typo fix in unrelated code," split the branch.
Rebase your branch onto main before opening a PR:
git fetch origin
git rebase origin/main
This replays your commits on top of the latest main, giving you a linear history and surfacing conflicts on your time, not the reviewer's.
Never rebase a shared branch (one other people have pulled). Rule of thumb: rebase your own feature branches, merge main into long-lived release branches.
Interactive rebase = the superpower
git rebase -i HEAD~5
In the editor you can pick, squash, reword, edit, drop, or reorder your last five commits. Use it to clean up your branch before review - collapse "fix typo" + "actually fix typo" + "real fix" into a single coherent commit.
When you panic
Almost nothing in Git is actually lost. git reflog shows every position HEAD has been in for the last 90 days. If you "deleted everything" with a bad reset --hard, you can almost always recover:
git reflog # find the SHA you want back
git reset --hard <sha> # back to that state
Only git push --force to a shared branch and git clean -fdx truly destroy work. Treat both with respect.
Pull requests, not pushes to main
main is protected. You ship by opening a PR. The reviewer is not a gatekeeper - they're a second pair of eyes. Make their job easy:
- Title summarises the change, not the task.
- Description has why, what, how to test, risk.
- Diff is < 400 lines if at all possible. Split if larger.
- CI is green before requesting review.
gh pr create --title "feat: chunked upload for >100MB files" --body "$(cat <<'EOF'
## Why
Customers on cellular uploading site walkthroughs were timing out on the single-POST path.
## What
- Split uploads >100MB into 5MB chunks with resumable signed URLs.
- Background reassembly job on the server.
## Test plan
- [ ] Upload a 250MB file from throttled 3G in DevTools.
- [ ] Kill the tab mid-upload, reload, confirm resume.
EOF
)"
Hands-on Checkpoints
- Clone any repo, make a branch, commit with a proper message, push, open a PR via
gh. - Do a 3-commit interactive rebase: squash two, reword one.
- Resolve a merge conflict by hand (don't accept either side blindly - read both).
- Recover a "deleted" commit via
git reflog. - Configure a
~/.ssh/configentry forgithub.comwith your key. - Set up a
.gitignorethat excludes.env,node_modules,*.logfor a fresh repo.
Further reading
- Pro Git by Scott Chacon - git-scm.com/book - free, definitive
- Oh Shit, Git!?! - recovery recipes
- Conventional Commits - the spec we follow loosely
ghCLI manual -gh help
Welzin opinion: A messy Git history is a code smell. We rebase. We squash. We write commit messages we'd be proud to read aloud in a post-mortem. If you can't explain what your commit does in one sentence, the commit is wrong, not the sentence.