Every Git change passes through three stages — working directory → staging area → repository — and travels to your team via a fourth step: push. Master that four-beat rhythm (edit → add → commit → push) and you have the daily heartbeat of professional Git use. This lesson runs the entire flow end-to-end with real commands and real terminal output, then teaches the part beginners get wrong: writing commit messages that don't make your future self cry.
The three local stages
Git keeps three views of your project at once:
- Working directory — your files as you see them in your editor. Anything you save here is visible immediately, but Git hasn't recorded it yet.
- Staging area (also called the index) — a holding area for changes you want to include in the next commit. Think of it as a draft of your next save point. You can stage some changes and leave others for later.
- Repository — the committed history. Permanent, named, recoverable snapshots.
You move changes between stages with git add (working → staging) and git commit (staging → repository). After a commit, git push uploads it to the remote (GitHub) so your team can see it.
Beat 1: edit a file
Working in the repo from the previous lesson:
cd webapp-tests
echo "describe('login', () => { it('works', () => {}) })" > tests/login.spec.js
git statusOn branch main
Untracked files:
(use "git add <file>..." to include in what will be committed)
tests/login.spec.js
nothing added to commit but untracked files present (use "git add" to track)
The new file exists in the working directory. Git sees it but isn't tracking it yet.
Beat 2: stage with git add
Stage the file:
git add tests/login.spec.js
git statusOn branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: tests/login.spec.js
The file moved from "untracked" to "changes to be committed." It's staged.
Two flavours of git add you'll use constantly:
git add tests/login.spec.js # stage one specific file
git add tests/ # stage everything in a folder
git add . # stage all changes in the current directorygit add . is convenient but blunt — it stages everything, including files you may not have meant to commit. Until you have a .gitignore set up (Chapter 5, Lesson 1), prefer naming files explicitly.
Why staging exists at all
Beginners ask: why two steps? Why not just commit directly? The answer is focused commits. Imagine you fixed a flaky test and updated a config file at the same time. Two unrelated changes in one commit is hard to review and hard to revert. Staging lets you commit them separately — git add tests/login.spec.js && git commit -m "fix flaky login test", then git add config.yml && git commit -m "bump CI timeout". Two clean commits. Two clean reverts. Two clean review conversations.
Beat 3: snapshot with git commit
A commit is a permanent, named snapshot of whatever's currently staged:
git commit -m "Add login test for invalid password scenario"[main 4c48901] Add login test for invalid password scenario
1 file changed, 1 insertion(+)
create mode 100644 tests/login.spec.js
The string in quotes is the commit message — the most important part. Git also recorded the author (your user.name / user.email from Lesson 2), a timestamp, and a unique hash (4c48901) so this exact snapshot can be referenced forever.
Run git status again — clean:
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
nothing to commit, working tree clean
And git log to see what you just made:
git log --oneline4c48901 Add login test for invalid password scenario
8f31320 Update search index and glossary cross-links
dc57b17 Add Chapter 8 lessons: Capstone Project
Beat 4: ship with git push
Until now, the commit lives only on your laptop. To share it with your team:
git pushEnumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 412 bytes | 412.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), local objects: 0
To github.com:acme/webapp-tests.git
8f31320..4c48901 main -> main
The new commit is now on GitHub. Teammates can git pull to receive it. CI/CD pipelines pick it up automatically and run your tests against it.
Writing commit messages that don't embarrass you
The single highest-leverage habit a junior QA engineer can build. Compare:
- ❌ "update"
- ❌ "fix"
- ❌ "stuff"
- ❌ "asdfgh"
- ✅ "Add login test for invalid password scenario"
- ✅ "Fix flaky checkout test by waiting for network idle"
- ✅ "Update fixtures to cover the new tax-rate edge case"
The rules:
- Start with a verb in the imperative. "Add", "Fix", "Update", "Remove", "Refactor". Read it as "If applied, this commit will _____." It will Add login test. It will Fix flaky checkout test.
- Be specific. What did you change, and why is it visible at a glance? "Fix test" is not specific. "Fix flaky checkout test by waiting for network idle" is.
- Keep the first line under ~70 characters. Long messages can have a body —
git commit(without-m) opens your editor for multi-line messages — but the first line is the headline.
A year from now you (or someone else) will run git blame on a broken test, see your commit, and either thank you or curse you. Write messages your future self will thank you for.
Useful inspection commands
While you work:
git diff # what's changed but not yet staged
git diff --staged # what's staged but not yet committed
git log --oneline # condensed history
git log -p tests/login.spec.js # full history of changes to one filegit diff before staging is your "do these changes look right?" check. git diff --staged is your "is this what I'm about to commit?" check.
The full workflow
Step 1 of 4
Edit
Change files in your working directory. Save in your editor. Git sees the changes but doesn't track them yet.
A complete QA scenario
You've been asked to add three new tests for the search feature. Sequence:
cd webapp-tests
git status # confirm clean start
# ...write the three test files in your editor...
git diff # eyeball your changes
git add tests/search/ # stage just the search folder
git status # confirm what's staged
git commit -m "Add search tests for empty, special-char, and pagination cases"
git pushFive commands. Three new tests on GitHub. CI fires automatically and runs them. Your team can now review, merge, and ship.
⚠️ Common mistakes
- Committing without staging anything.
git commit -m "..."with nothing staged prints "nothing to commit." Beginners panic and re-edit the file — the fix isgit addfirst. (Orgit commit -am "..."to add-and-commit modified tracked files in one step. It does NOT pick up brand-new files.) - Using
git add .blindly. This stages every change in your current directory, including secrets, build outputs, and screenshots you didn't mean to ship. Until your.gitignoreis solid, list files explicitly:git add tests/login.spec.js. - Forgetting to push. A common moment of horror: laptop dies, three days of commits gone with it because they only ever lived locally. Push at the end of every working session — even on a personal branch — so the remote always has a copy.
🎯 Practice task
A complete add → commit → push cycle. 20-25 minutes. Use the qa-sandbox repo from the previous lesson, or any practice repo of your own.
- In your
qa-sandboxrepo, create three files:README.md,tests/login.spec.js, andtests/search.spec.js. Put one line of placeholder content in each. - Run
git status. Confirm all three appear as untracked. - Stage only the README:
git add README.md. Rungit statusagain — note that the README is staged but the two test files are still untracked. This is the fine-grained control staging gives you. - Commit:
git commit -m "Add README". Rungit log --onelineto confirm the commit exists. - Stage and commit the test files in a second commit:
git add tests/ && git commit -m "Add login and search test scaffolds". Rungit log --oneline— you now have two clean, focused commits. - Stretch (push step): if you have a GitHub account, create an empty repo there, then locally run
git remote add origin <url>andgit push -u origin main. Refresh the GitHub page — your two commits are live. (The next chapter coversgit remoteand SSH keys in detail; this is just a preview.)
You now know the daily rhythm of working in Git. Chapter 2 introduces branches — the part that turns Git from "save points" into "parallel workstreams."