Git 05 — Daily Workflow & Tooling
You now understand the graph, branching, remotes, and recovery. This last module is about turning that knowledge into a smooth daily habit — the loop you run without thinking, the commit messages your future self thanks you for, the tooling that removes friction. It ends with the one thing that makes it all real: this very site auto-deploys from git, and you’ll trace exactly how a git push becomes a live page.
The Goal
By the end of this module you can:
- Run a sane daily git loop on a feature branch without breaking
main - Write commit messages in imperative mood that say why, not just what
- Configure a
.gitignoreso junk and secrets never enter history - Open and review pull requests from the terminal with the
ghCLI - Set up aliases that cut your most-typed commands to two letters
- Trace how a push to this repo becomes a live deploy via GitHub Actions
The Lesson
The daily loop — the rhythm of real work
You almost never work directly on main. The everyday rhythm is: branch off, work in small commits, push, open a PR, merge, clean up. Here’s the full loop as a flow:
flowchart TD
A[git switch main] --> B[git pull to get latest]
B --> C[git switch -c feature-refunds]
C --> D[make a small change]
D --> E[git add + git commit]
E --> F{more to do}
F -->|yes| D
F -->|no| G[git push -u origin feature-refunds]
G --> H[open a PR with gh pr create]
H --> I[review, address comments]
I --> J[merge PR on green CI]
J --> K[git switch main, git pull, delete the branch]
K --> A
The whole loop in commands, the way you’ll actually type it:
git switch main
git pull # start from the latest shared state
git switch -c feature-refunds # your isolated workspace (a cheap label)
# ...work, committing in small logical chunks...
git add .
git commit -m "Add refund endpoint to expense controller"
git push -u origin feature-refunds # -u sets the tracking branch, once
gh pr create --fill # open the PR from the terminal
# ...CI runs, teammate reviews, you address comments with more commits...
gh pr merge --squash --delete-branch # merge when green, clean up the branch
git switch main; git pull # back to a fresh main
Two habits inside this loop separate smooth developers from chaotic ones: pull before you branch (so you start from current reality, not last week’s), and commit small (each commit one logical step, so reviews are readable and revert is surgical). You met why small commits matter in Undo & Recovery — every commit is a save point and a clean revert target.
Good commit messages — imperative mood, why not what
A commit message is a note to the human reading the history six months from now — often you. Two rules make the difference between a useful log and noise.
Rule 1: imperative mood. Write the subject as a command, as if completing the sentence “If applied, this commit will ___“:
| Bad | Good |
|---|---|
added refund endpoint | Add refund endpoint |
fixing the null bug | Fix null check on missing payer |
changes | Validate expense amount is positive |
Why imperative? It matches git’s own generated messages (Merge branch..., Revert...), it’s shorter, and it reads as “this commit does X” — which is what a log entry is for.
Rule 2: say why, not just what. The diff already shows what changed. Your message’s job is the why the diff can’t show. For anything non-trivial, add a body:
Cap expense splits at group member count
A user could split an expense across more people than the group has,
producing negative balances no one could settle. Reject splits where
share count exceeds active members, returning 422 with a clear message.
A future engineer (you) hunting why this rule exists reads that and stops digging. Subject line: imperative, ~50 chars, no full stop. Blank line. Body: wrap at ~72 chars, explain the why. This isn’t ceremony — it’s the difference between git log being documentation and being landfill.
.gitignore — keep junk and secrets out of history
Some files should never be committed: build output (regenerated anyway), dependency folders (huge, reinstallable), IDE settings (personal), and — critically — secrets. A .gitignore file lists patterns git refuses to track.
# Build output
target/
build/
dist/
# Dependencies
node_modules/
# IDE / OS noise
.idea/
.vscode/
.DS_Store
# Secrets — NEVER commit these
.env
*.pem
application-local.properties
Two things people get wrong:
.gitignoreonly affects untracked files. If you already committednode_modules/once, adding it to.gitignoredoes nothing — git is already tracking it. You mustgit rm -r --cached node_modulesto stop tracking it, then commit.- A secret committed once is in history forever — even if you delete it in a later commit, it’s recoverable from the old commit (and from anyone who cloned). The repo is public; treat any leaked key as compromised and rotate it immediately. This is the same never-commit-keys discipline you’ll carry into Spring Boot security. Prevention beats cleanup: add the pattern before the first commit.
GitHub maintains ready-made templates (github.com/github/gitignore) for every language — start from the Java or Node one, don’t write it from scratch.
The gh CLI — PRs and issues without leaving the terminal
gh is GitHub’s official command-line tool. It means you never break flow to click around the website for routine work. Authenticate once (gh auth login), then:
| Command | What it does |
|---|---|
gh pr create --fill | Open a PR for the current branch, using your commits to fill title/body |
gh pr list | List open PRs on the repo |
gh pr view --web | Open the current branch’s PR in the browser |
gh pr checkout 42 | Check out PR #42 locally to review/test it |
gh pr merge --squash --delete-branch | Merge and clean up, all at once |
gh issue create / gh issue list | File and browse issues from the terminal |
gh run list / gh run watch | See CI runs (the Actions tab) from the terminal |
gh pr checkout 42 is the underrated one: it pulls a teammate’s PR branch onto your machine so you can actually run their code before approving — reviewing a diff is not the same as running it.
Aliases — stop typing the same thing 50 times a day
You type git status and git checkout hundreds of times a week. Alias them:
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.sw switch
git config --global alias.br branch
git config --global alias.cm "commit -m"
git config --global alias.lg "log --oneline --graph --decorate --all"
Now git st, git co, git cm "msg". The standout is git lg: it draws the commit graph in your terminal — branches, merges, and HEAD as ASCII art. That’s the graph from the mental model, live, on demand. Run it constantly; seeing the graph is what keeps git un-scary.
Pre-commit hooks — automated gatekeepers, in one paragraph
A hook is a script git runs automatically at a point in its lifecycle. The most useful is the pre-commit hook: it runs before a commit is recorded and can reject it if checks fail — formatting, linting, “no console.log left in,” “no .env being committed.” It’s the same idea as CI, but on your machine, before the bad commit ever exists, so you catch problems in one second instead of three minutes after pushing. Teams usually manage these with a tool (e.g. the pre-commit framework or Husky for JS) so the hooks live in the repo and everyone gets them; the hook scripts themselves sit in .git/hooks/. You don’t need to set one up today — just know that “the commit was blocked because a check failed locally” is a hook doing its job, and it’s a good thing.
How this very site deploys from git — the payoff
Everything in this track converges here. This site lives in a GitHub repo. There is no “deploy” button anyone clicks — a git push to main is the deploy. Here’s the actual chain:
flowchart LR
A[git push to main] --> B[GitHub receives the push]
B --> C[GitHub Actions workflow triggers]
C --> D[runner installs deps and runs npm run build]
D --> E{build succeeds}
E -->|yes| F[publish to GitHub Pages, site live in about 70s]
E -->|no| G[deploy fails, site stays on last good version]
So when you finish a module and run git push, a GitHub Actions runner spins up, checks out your commit, runs npm run build, and if it’s green, publishes the result to GitHub Pages — live in roughly 70 seconds. If the build fails (a broken MDX link, a bad Mermaid label), the deploy stops and the old version stays up. Your push to git is the trigger, and CI is the safety gate. That’s why the build constraints in this site matter: a malformed <details> tag doesn’t just look wrong, it fails the deploy. This is your concrete, working example of the full loop — branch, commit, push, automated build, live — and it’s exactly what the CI/CD track generalizes. Git isn’t just where your code lives; it’s the button that ships it.
Real error, real repo: the
fetchhalf of the daily loop trips people constantly — see git fetch Updates Your Knowledge, Not Your Files for whygit statuscan lie before you’ve fetched.
Check The Concept
How This Shows Up At Work
- The reviewer who can’t review. A PR arrives as one giant commit titled “changes.” The reviewer can’t tell what’s intentional versus accidental, so review takes an hour and misses a bug. Small, well-messaged commits get faster, better reviews — and that reflects on you.
- The 2am
git blame. Production breaks; someone runsgit blameon the failing line, lands on your commit, and reads the message. If it says why the line exists, they fix it in minutes. If it says “changes,” they page you. Commit messages are documentation that gets read exactly when stakes are highest. - The leaked key incident. A
.envslips into a public repo. The on-call response is always: rotate the key first (it’s compromised the instant it’s pushed), then clean history. Engineers who set up.gitignorecorrectly up front never have this incident. - The interview question. “Walk me through your daily git workflow” — a real screening question. A clear answer (pull, branch, small commits, push, PR, CI gate, merge, clean up) signals you’ve worked on a real team, not just solo tutorials.
Build This / Try This
A throwaway repo and your real GitHub account. PowerShell on Windows. Install gh first if you haven’t: winget install GitHub.cli.
- Set up aliases and the graph view (global — does this for all repos):
git config --global alias.st status
git config --global alias.cm "commit -m"
git config --global alias.lg "log --oneline --graph --decorate --all"
git config --global alias.sw switch
git st # confirm: behaves exactly like git status
- Make a sandbox with a proper .gitignore:
mkdir workflow-lab; cd workflow-lab
git init
"target/`n.env`nnode_modules/" | Out-File -Encoding utf8 .gitignore
"SECRET_KEY=hunter2" | Out-File -Encoding utf8 .env
git add .
git status # .env is NOT listed — .gitignore is protecting you
Notice .env doesn’t appear in git status. The ignore worked.
- Run the daily loop with good messages:
git commit -m "Add gitignore for secrets and build output"
git switch -c feature-greeting
"hello" | Out-File -Encoding utf8 app.txt
git add app.txt
git cm "Add greeting file" # using your alias
git lg # see the graph: two commits, branch label, HEAD
- Push and open a real PR with gh (create an empty repo on GitHub first, or let gh create it):
gh repo create workflow-lab --public --source=. --push
git push -u origin feature-greeting
gh pr create --fill
gh pr view --web # your PR, opened from the terminal
- Watch CI / the deploy concept on a repo that has Actions (this very site, if you have it cloned):
gh run list # recent workflow runs
gh run watch # live-follow the latest run to completion
You’re watching the exact mechanism that turns a push into a live site.
- Break it on purpose #1 — the .gitignore-too-late trap. Commit a file, then try to ignore it:
"junk" | Out-File -Encoding utf8 tracked.log
git add tracked.log; git cm "Add a log file by mistake"
Add-Content .gitignore "`n*.log"
git status # tracked.log still shows up despite the rule
git rm --cached tracked.log; git cm "Stop tracking log file"
git status # now it's ignored
That’s the gotcha in your hands: ignore rules don’t apply to already-tracked files.
- Break it on purpose #2 — a bad commit message, then fix it. Make a vague commit, then amend it into a good one (you learned
--amendin Undo & Recovery):
"more" | Out-File -Encoding utf8 app.txt -Append
git add app.txt; git cm "stuff"
git commit --amend -m "Append farewell line to greeting"
git lg # the message now earns its place
Interview Practice
Asked at Indian product companies. Answer aloud, then check.
1. Walk me through your day-to-day git workflow.
Start on main and git pull for the latest. Branch off with git switch -c feature-x so I never work on main directly. Work in small logical commits with clear imperative messages. git push -u origin feature-x, then open a PR (gh pr create). CI runs automatically; a teammate reviews; I address comments with more commits. When CI is green and it’s approved, squash-merge, delete the branch, switch back to main, and pull. Two habits underpin it: pull before branching, and commit small so reviews are readable and reverts are surgical.
2. What makes a good commit message?
A subject line in imperative mood (“Add refund endpoint,” completing “if applied, this commit will ___”), under ~50 chars, no trailing full stop. Then, for anything non-trivial, a blank line and a body explaining the why — the diff already shows the what, so the message’s value is the reason the change exists and any context a future reader needs. Imperative matches git’s own messages, and a why-focused body is what makes git blame and git log actual documentation.
3. What is .gitignore, and what's the catch with it?
It lists file patterns git refuses to track — build output, dependencies, IDE files, and secrets like .env. The catch: it only affects untracked files. If a file was already committed, adding it to .gitignore does nothing; you must git rm --cached <file> to stop tracking it, then commit. So set it up before the first commit, especially for secrets.
4. You accidentally committed and pushed a secret to a repo. What do you do?
Rotate the secret immediately — it’s compromised the moment it hits the remote, and on a public repo, assume it’s already scraped. Deleting it in a new commit is not enough; it lives in history and is recoverable from the old commit and from anyone who cloned. After rotating, scrub history if required (e.g. git filter-repo / BFG) and force-push with the team’s awareness. Then add the pattern to .gitignore so it can’t recur. Prevention (ignore + a pre-commit hook) is the real fix.
5. What is a git hook? Give a useful example.
A hook is a script git runs automatically at a lifecycle point. The most common is pre-commit: it runs before a commit is recorded and can reject it if checks fail — linting, formatting, blocking a .env or a leftover debug print. It’s like CI but local and earlier, catching problems in a second before the bad commit exists. Teams manage them with tools (the pre-commit framework, Husky) so the hooks live in the repo and everyone shares them.
6. What's the difference between CI checking your code and a pre-commit hook?
Same idea, different timing and location. A pre-commit hook runs on your machine, before the commit is created — fastest feedback, but only if you have it installed. CI runs on a server, after you push — slower (build spins up), but it’s the shared gate everyone’s code must pass, and it can’t be skipped. Good teams use both: hooks for instant local feedback, CI as the authoritative gate that blocks merges.
7. How does a static site like this one get deployed from git?
A git push to main is the deploy trigger. GitHub Actions detects the push, spins up a runner, checks out the commit, installs dependencies, runs the build (npm run build), and if it succeeds, publishes the output to GitHub Pages — live in about 70 seconds. If the build fails (a broken link, bad config), the deploy aborts and the last good version stays up. No human clicks “deploy”; git push plus CI is the whole pipeline.
8. Why commit in small chunks instead of one big commit at the end?
Small commits make reviews readable (a reviewer can follow one logical step at a time), make git blame precise (the failing line maps to a focused change with a clear message), make revert surgical (undo exactly the bad step without losing good work), and make git bisect able to pinpoint which change broke something. One giant “changes” commit destroys all of that. Each commit is also a save point you can return to via the reflog.
Where to Practice
| Resource | What to do | How long |
|---|---|---|
| docs.github.com | Read the “GitHub CLI” quickstart, run gh auth login, open one real PR with gh pr create | 30 min |
| git-scm.com (Pro Git ch. 8.3 + 8.4) | Read “Git Aliases” and “Git Hooks” — the canonical reference, then set up your own aliases | 30 min |
| github.com/github/gitignore | Open the Java and Node templates; copy the one you’ll actually use into a project | 15 min |
| learngitbranching.js.org | Replay the branching/remote levels until the daily loop (branch → commit → push → merge) is muscle memory | 30 min |
Check Yourself
- List the daily git loop from
mainto merged-and-cleaned-up. - Rewrite “fixed login stuff” as a proper commit subject, and say what the body should contain.
- You added
build/to.gitignorebut it’s still tracked. Why, and what’s the fix? - Name three
ghcommands and what each does. - What is
git lg(the alias in this module) good for? - In one sentence, what does a pre-commit hook do and why is it useful?
- Trace what happens between
git pushto this repo’smainand the page being live. - A secret was pushed to a public repo. What’s the first action, and why isn’t deleting it enough?
Answers
git switch main→git pull→git switch -c feature-x→ work in small commits →git push -u origin feature-x→gh pr create→ review/CI → merge on green →git switch main→git pull→ delete the branch.- Subject:
Fix null pointer on missing login token(imperative, concise, no full stop). Body: why — the condition that triggered it and what the fix guarantees, the context the diff can’t show. .gitignoreonly affects untracked files;build/was already committed so git keeps tracking it. Fix:git rm -r --cached build, then commit; the rule applies from then on.- e.g.
gh pr create --fill(open a PR),gh pr checkout 42(pull a PR locally to test),gh pr merge --squash --delete-branch(merge and clean up). Alsogh run watch,gh issue create. - It draws the commit graph in the terminal — branches, merges, HEAD as ASCII art — so you can see the structure from the mental-model module on demand.
- It runs a check script before a commit is recorded and can block the commit if the check fails (lint, format, no secrets), catching problems locally in one second.
- GitHub receives the push → Actions workflow triggers → runner checks out the commit, installs deps, runs
npm run build→ if green, publishes to GitHub Pages (~70s); if it fails, the deploy aborts and the old version stays live. - Rotate the secret immediately (it’s compromised the moment it’s pushed, doubly so on a public repo). Deleting it later isn’t enough because it persists in the earlier commit in history and in anyone’s clone — it’s recoverable.
Explain it out loud: Describe your full daily git workflow to an empty chair, from git pull on main to a merged PR and a live deploy — naming where commit messages, .gitignore, gh, and CI each fit. If any step is fuzzy, that’s the section to re-read.
Why AI Can’t Do This For You
AI can generate a .gitignore and even draft a commit message. What it can’t do is decide why your change exists — the business reason, the bug you actually chased, the context only you have — which is exactly the part of a commit message that has value. A message AI writes describes the diff; a message you write explains the decision, and that’s what the engineer reading git blame at 2am needs.
The workflow itself is judgment, not syntax: when to branch, how small a commit should be, whether a check belongs in a hook or in CI, what must never enter history. Those calls protect a team’s shared repo and its live deploys. No prompt knows your team’s conventions or what’s compromising to leak — you do, and running this loop daily until it’s reflex is what makes you the person others trust with main.
Module done? Add it to today’s tracker