A Git repository is a database of changes. Three commands let you query it: git log lists commits, git diff shows what changed between two states, and git blame annotates a file with who-touched-each-line. These are the QA engineer's archaeology kit — the tools you reach for when a test starts failing and you need to know "what changed since yesterday?", "did this PR touch the search code?", or "who wrote this assertion and why?". This lesson covers the patterns you'll use a hundred times a week.
git log — the commit history
The bare command:
git logShows full-detail commits, newest first. Press q to quit. Useful but verbose. Most of the time you want the compact form:
git log --oneline4c48901 Add cart fixture for discount-code edge cases
8f31320 Update search index after Chapter 8 added
dc57b17 Add Chapter 8 lessons: Capstone Project
ebc3006 Add Chapter 7 lessons: Error Handling & Debugging
One commit per line — short hash, message. That's the form you'll read most.
Useful flags
git log --oneline -10 # last 10 commits
git log --oneline --graph # ASCII branch graph
git log --oneline --graph --all # graph including all branches
git log --author="Alice" # only Alice's commits
git log --since="1 week ago" # commits in the last week
git log --grep="flaky" # commits whose message contains "flaky"
git log -- cypress/e2e/login.spec.ts # commits that touched one file
git log -p -- cypress/e2e/login.spec.ts # ...with the actual diff for eachThe two dashes (--) before a path mean "everything after this is a file path, not a flag." Useful when filenames could be confused with options.
A QA workhorse: "what changed since the last release?"
git log v2.6.0..main --onelineTwo dots (..) mean "commits reachable from main but not from v2.6.0." Exactly the diff you'd put in your regression-test plan.
A real graph
Run git log --oneline --graph --all in a moderately busy repo and you see the branching shape:
* 9b3d2f1 Merge pull request #142 from acme/feature/payment-gateway
|\
| * 7a92f1e Add tests for declined-card flow
| * 3a7b2c1 Scaffold payment-gateway tests
* | e1a4c80 Update timeout config for slower staging
* | 5d8f3a2 Fix flaky search test
|/
* 4c48901 Add cart fixture for discount-code edge cases
The asterisks are commits, the lines are parent-child relationships, the diamond shape is a merge. Reading this in your terminal beats most GUI tools for raw situational awareness.
git diff — what's different
git diff answers "how do A and B compare?" The arguments determine A and B.
git diffShows changes in your working directory that aren't yet staged — i.e., what you've edited since the last git add.
git diff --staged(also git diff --cached) — what's staged but not yet committed. Run this right before git commit to see exactly what's about to be saved.
Compare two branches:
git diff main..feature/checkout-testsCompare two commits:
git diff abc1234 def5678The last N commits:
git diff HEAD~3HEAD is the current commit. HEAD~3 is "three commits before HEAD." So git diff HEAD~3 is "everything that's changed in my last three commits combined."
For a single file:
git diff HEAD~3 -- cypress/e2e/login.spec.tsWhat the diff output means
diff --git a/cypress/e2e/login.spec.ts b/cypress/e2e/login.spec.ts
index a8f2c91..3c5e8a4 100644
--- a/cypress/e2e/login.spec.ts
+++ b/cypress/e2e/login.spec.ts
@@ -12,7 +12,7 @@ describe('login', () => {
cy.get('input[name=email]').type('alice@example.com');
- cy.get('input[name=password]').type('correcthorsebatterystaple');
+ cy.get('input[name=password]').type(Cypress.env('TEST_PASSWORD'));
cy.get('button[type=submit]').click();
});
});
---= the before version.+++= the after version.- Lines starting with
-were removed; lines starting with+were added. @@ -12,7 +12,7 @@is the hunk header — the line range in the old (-12,7) and new (+12,7) file, plus the function/section context.
Two minutes of diff-reading practice pays off forever; every PR you'll review is just diffs in a prettier wrapper.
git blame — who wrote each line, and when
git blame annotates every line of a file with the commit, author, and date that last changed it.
git blame cypress/e2e/login.spec.ts^4c48901 (Vimal 2026-04-21 14:22) describe('login', () => {
^4c48901 (Vimal 2026-04-21 14:22) it('lets a valid user in', () => {
3a7b2c1 (Alice 2026-04-29 09:11) cy.visit('/login');
3a7b2c1 (Alice 2026-04-29 09:11) cy.get('input[name=email]').type(...);
9b3d2f1 (Alice 2026-05-02 16:47) cy.get('input[name=password]').type(Cypress.env('TEST_PASSWORD'));
^4c48901 (Vimal 2026-04-21 14:22) cy.get('button[type=submit]').click();
For each line: short hash, author, date, the line itself. To dig into a specific change:
git show 9b3d2f1You see the full commit — message, all files changed, the diff. Now you know not just who changed the password line, but why (assuming the commit message is decent).
git blame is mislabelled — it's almost always used not to assign fault but to find context. "Why does this assertion check for 200 OK instead of any 2xx?" is one git blame away from "the team had an incident in March; see commit 9b3d2f1."
A QA log+diff workflow
The tester's three-question routine when a test fails:
- What changed recently in this area?
git log --oneline -10 -- cypress/e2e/checkout/ - What exactly did that PR change?
git show <commit-hash>— read the diff carefully. - Who wrote this specific assertion?
git blame -L 80,90 cypress/e2e/checkout.spec.ts(only lines 80-90), thengit show <commit>for context.
Three commands, two minutes, almost always enough to triage "is this a real bug, a flake, or a missed-update?"
What a graph view tells you at a glance
Commits per branch in the last week (illustrative)
This kind of view (the same shape git log --all --oneline gives you in raw form) is how QA leads spot which areas got a lot of churn — the top of the list is also the top of the regression-test priority list for the next sprint.
git show — the deep dive on one commit
git show 4c48901Prints the commit message, author, date, and the full diff of every file changed in that commit. The most common follow-up to git log or git blame. You'll alias it in your head as "tell me everything about this commit."
⚠️ Common mistakes
- Reading raw
git logand getting overwhelmed. Without--oneline, log is a wall of text. Default to--oneline --graphfor situational awareness; reach for full log only when you need full bodies. - Confusing
git diffandgit diff --staged. They show different things — unstaged vs staged. Before every commit, run both:git diff(what's left out) andgit diff --staged(what's going in). Surprises are usually one of these two. - Trusting
git blamefor blame. The line you're looking at may have moved through a refactor that left the original logic intact but the author "Alice." Usegit log -p -- fileto read the deeper history;blameis a starting point, not a verdict.
🎯 Practice task
Spend 25-30 minutes turning your repo's history into something you can read fluently. Use any active repo with at least 50-100 commits.
- Run
git log --oneline -20. Read the last 20 commit messages. Are they descriptive enough that you could tell what happened? Note which ones aren't — your future self is the audience. - Run
git log --oneline --graph --all. Trace one merge commit visually — find the two parent lines that meet. - Pick any file you've worked on. Run
git log --oneline -- <path>. Pick a recent commit hash and rungit show <hash>— read the full change. - Run
git blame <same-file> | head -30. Identify the most recent change. Rungit show <its-commit>to see the surrounding context. - Compare two branches:
git diff main..feature/your-branch -- <file>. The diff is exactly what would merge if you opened a PR. - Stretch: add a Git alias for the graph view.
git config --global alias.lg "log --oneline --graph --all". Nowgit lgdoes it. Aliases are 30 seconds of setup that save you typing for the rest of your career.
The next lesson is the inverse of inspection — undoing things you wish hadn't happened.