Log in
Progress0 / 30 pages0%
1.
Git & CI/CD · Sub-chapter 1 · 4 min read

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

  1. The three trees - working directory, index (staging), HEAD
  2. Commits, branches, refs - what they actually are
  3. Local workflow - add, commit, status, diff, log
  4. Branching - branch, checkout/switch, merge, rebase
  5. Remotes - clone, fetch, pull, push, tracking branches
  6. Rewriting history - commit --amend, interactive rebase, reset
  7. Recovering from disaster - reflog, cherry-pick, "undo" patterns
  8. GitHub workflow - PRs, reviews, gh CLI
  9. Hygiene - .gitignore, commit message conventions, signed commits
  10. Welzin conventions - branch names, commit format, PR template

101 Primer

The model: three trees and a graph

Git is just two things:

  1. A content-addressed store of files (blobs), folders (trees), and snapshots (commits).
  2. 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.

text
 work tree   →   index   →   HEAD
 (edit)         (stage)      (commit)
   ↓              ↓             ↓
  vim foo       git add foo   git commit

Configure once, properly

bash
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:

bash
ssh-keygen -t ed25519 -C "you@welzin.ai"
cat ~/.ssh/id_ed25519.pub      # paste this into GitHub → SSH keys

The daily loop

bash
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:

text
<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:

bash
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

bash
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:

bash
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.
bash
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/config entry for github.com with your key.
  • Set up a .gitignore that excludes .env, node_modules, *.log for a fresh repo.

Further reading

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.

Knowledge check

Pass 80% to unlock
0/1 answered
What does `git rebase` give you that a merge commit does not?