Git Basics for Testers
Git is a version control system: it records a project's history as a chain of snapshots called commits, each with a unique id, an author, a date, a message and a link to the commit before it. When a developer fixes the VAT rounding in a checkout and commits the change as 3f9c2ab, that commit becomes a permanent point in the history: anyone can see which lines changed, who changed them and when, and test exactly that version.
For a tester, Git is less about writing code than about knowing exactly what you are testing: which version, which change, and what else the change touched.
Why it matters
"Which build did you test?" is a question Git answers precisely. Without a commit id a failure is hard to reproduce, and nobody can prove that a retest ran on the version with the fix. "Fails on main at 3f9c2ab" can be reproduced by anyone on the team.
Git also shows what changed: the diff since the last tested version is direct input for choosing regression tests. And automated tests, test data and pipeline files live in the same repository as the product code, so maintaining them means committing, branching and merging like a developer.
Repository, commit and the three places a change lives
A repository is a project folder plus its full history, kept in a hidden .git directory. The team's shared copy, the remote, usually named origin, lives on a server; git clone copies it to your machine with the whole history. Later, git fetch downloads new commits and records where the remote's branches now point under names such as origin/main, without touching your files; git pull also brings your current branch up to date, and git push sends your own commits to origin.
A change passes through three places: the working tree (the files you edit), the staging area or index (the changes you select with git add) and the history (git commit records a snapshot of what is staged). Git names each commit by a hash, a fixed-length fingerprint calculated from the commit's content and history: 40 hexadecimal characters (SHA-1 in most repositories), usually shown shortened to the first seven or more, such as 3f9c2ab. Change one character and the id changes, so a commit id names exactly one version. The SHA-1 Generator shows this on any text; Git hashes a commit in its own internal format, so you will not reproduce a commit id with it.
$ git clone https://git.example.com/shop/web.git # the repository and its history
$ cd web
$ git fetch # later: get new commits from origin; files untouched
$ git pull # fetch, then bring your current branch up to date
$ git status # what is modified, staged or untracked
$ git add tests/checkout/vat.spec.ts
$ git commit -m "Add VAT test for a total ending in .005"
$ git push # send your new commits to originBranches, HEAD and merges
A branch is a movable name that points to one commit; each new commit on the branch moves the name forward. main (in older repositories often master) usually holds the integrated product, and developers create a short-lived branch per change, such as fix/vat-rounding, so unfinished work disturbs nobody. HEAD is what you have checked out, normally a branch.
A merge brings one branch's commits into another. If the target has not moved since the branch started, Git simply moves its name forward (a fast-forward); if both sides have new commits, it creates a merge commit with two parents. If both sides changed the same lines, Git stops with a merge conflict and a person writes the result. Even a clean merge combines changes that were never run together, so a merged build deserves its own test. Branching strategies and pull requests are the next lesson.
In git log --graph, each * is a commit and the lines lead to its parents; the names in brackets show where branches, tags and HEAD point.
fix/vat-rounding starts at release v2.3.0 and gets one commit while main gets two; merging it back creates merge commit 9e41d07 with two parents.mainthe integrated productfix/vat-roundingone change, kept apart
- 1
maininternally: Releasev2.3.0tagged at1d2e3f4The version the tester last tested - 2 From
mainTofix/vat-rounding: Branch created at the same commitgit switch -c fix/vat-rounding - 3
maininternally: Commit5b0a9f4: minimum order 50.00 SAR Colleagues keep committing tomain - 4
maininternally: Commitc7d2e18: Arabic invoice labels - 5
fix/vat-roundinginternally: Commit3f9c2ab: VAT rounding fix So far only this branch has it - 6 From
fix/vat-roundingTomain: Merged: merge commit9e41d07Two parents:c7d2e18and3f9c2abgit merge fix/vat-rounding - 7
maininternally: The tester lists what is new Four commits sincev2.3.0: the regression scopegit log --oneline v2.3.0..main
Time runs downwards. Each lane is a branch name that moves forward with every new commit.
$ git switch -c fix/vat-rounding # new branch at the current commit, switch to it
$ git commit -am "Fix VAT rounding for totals ending in .005" # -a stages edited files
$ git switch main
$ git pull # fast-forward: two new commits from colleagues
$ git merge fix/vat-rounding # both sides moved on: Git makes a merge commit
$ git log --oneline --graph -5
* 9e41d07 (HEAD -> main) Merge branch 'fix/vat-rounding' into main
|\
| * 3f9c2ab (fix/vat-rounding) Fix VAT rounding for totals ending in .005
* | c7d2e18 (origin/main) Add Arabic labels to the invoice PDF
* | 5b0a9f4 Update minimum order total to 50.00 SAR
|/
* 1d2e3f4 (tag: v2.3.0) Release 2.3.0Reading history with log, show and diff
git log lists commits, newest first; git show displays one commit and its changes; git diff compares any two versions. A tag is a fixed name for one commit, typically a release such as v2.3.0.
In the diff below, --- and +++ name the old and the new file, lines starting with - were removed, lines starting with + were added, and @@ -9,5 +9,5 @@ means the excerpt starts at line 9 and spans five lines in both versions; the text after the second @@ is only an earlier line of the file, shown for orientation.
git log --oneline v2.3.0..main: everything inmainthat is not in releasev2.3.0.git log --oneline -- src/checkout/: only the commits that touched the checkout folder.git diff --stat v2.3.0 main: the changed files and line counts between two versions.git tag --contains 3f9c2ab: the releases that already include a fix.
$ git diff v2.3.0 main -- src/checkout/vat.ts
diff --git a/src/checkout/vat.ts b/src/checkout/vat.ts
index 4b1e2c0..9d7a3f1 100644
--- a/src/checkout/vat.ts
+++ b/src/checkout/vat.ts
@@ -9,5 +9,5 @@ import { roundHalfUp } from './money';
// VAT rate in this example: 15 %
export function vatFor(net: number): number {
const rate = 0.15;
- return Math.round(net * rate * 100) / 100;
+ return roundHalfUp(net * rate, 2);
}Worked example: what changed since the last release
You tested release v2.3.0 last week; today you are asked for a regression pass on the latest main. Before opening the application, you ask Git what changed; the commands and their output are at the end of this section. Read them file by file:
vat.ts: one line of VAT rounding changed. Test amounts whose VAT ends in half a halala, such as a net price of 1.10 SAR (VAT 0.165).git grepshows that totals and refunds callvatFortoo, so refunds join the scope.min-order.ts: the minimum order is now 50.00 SAR. Test 49.99 and 50.00, as in boundary value analysis; if no ticket or release note mentions the change, ask before testing.labels.ar.json: new Arabic invoice labels. Check the Arabic PDF invoice and its right-to-left layout.vat.spec.ts: new unit tests. Values they cover need less manual effort; values they miss go on your list.- Write "Regression on
mainat9e41d07, changes sincev2.3.0" in the test report and in every bug report.
$ git fetch origin
$ git log --oneline v2.3.0..origin/main
9e41d07 (origin/main) Merge branch 'fix/vat-rounding' into main
3f9c2ab Fix VAT rounding for totals ending in .005
c7d2e18 Add Arabic labels to the invoice PDF
5b0a9f4 Update minimum order total to 50.00 SAR
$ git diff --stat v2.3.0 origin/main
src/checkout/min-order.ts | 4 ++--
src/checkout/vat.ts | 2 +-
src/invoice/labels.ar.json | 18 ++++++++++++++++++
tests/checkout/vat.spec.ts | 21 +++++++++++++++++++++
4 files changed, 42 insertions(+), 3 deletions(-)
$ git grep -n "vatFor(" origin/main -- src
origin/main:src/checkout/total.ts:27: const vat = vatFor(net);
origin/main:src/checkout/vat.ts:10:export function vatFor(net: number): number {
origin/main:src/refunds/refund.ts:41: return amount + vatFor(amount);Common mistakes
- Reporting against "the latest build" instead of a commit id or tag: nobody can reproduce the failure or prove that the retest ran on the fix.
- Testing a stale copy:
git statusknows aboutoriginonly as of your lastgit fetch. - Trusting commit messages without reading the diff: a "refactor" can still change a business rule.
- Committing secrets,
.envfiles, test reports or screenshots. Use.gitignore; a secret deleted later stays in the history, so report it at once so it can be revoked. - Making large commits with messages such as "fix": the history no longer tells anyone what changed or why, and one change cannot be found or undone on its own.
The QA perspective
Testers use a small part of Git every day: they record the exact version under test, read the log and diff since the last tested version to choose regression tests, check out a commit to reproduce a failure, and keep automated tests in the repository, where CI starts on Git events such as a push or a merge. The ISTQB Foundation syllabus calls this configuration management: test items and testware are uniquely identified and version controlled.
Ask for the commit or pull request that fixes each defect and link it in the defect record; before confirmation testing, check that the fix is in your build. You do not need every line of a diff: file names, function names and the size of a change are enough to ask "what else uses this?", the question that finds regressions.
Practice exercise
Read a release history and plan the retest
You last tested release v1.8.0 of a shop application. Using the output below, find the new commits, the branch and the merge commit, list the areas to test, and decide how you will identify the tested version.
$ git log --oneline --graph v1.8.0..main
* a41f9c0 (HEAD -> main) Merge branch 'feature/saved-cards' into main
|\
| * 7be2d15 Mask card number except the last 4 digits
| * 0c93e6a Add saved cards page
* | e5d8b72 Fix account lockout after 5 failed logins
|/
* 52aa1f3 Change password reset link expiry to 30 minutes- Read the graph from the bottom up: where did the branch start, and where was it merged?
- Group the new commits by product area.
- Write two or three test ideas per area, using techniques from earlier lessons.
- Write the version line for your report and one question for the team.
Show the model answer
Five commits are new. feature/saved-cards started at 52aa1f3 and holds 0c93e6a and 7be2d15; meanwhile e5d8b72 was committed on main.
a41f9c0 is the merge commit: its first parent is e5d8b72, the previous tip of main, and its second parent is 7be2d15, the tip of the branch. The two lines may never have been tested together, so the merged build needs its own pass.
Authentication: reset links just before and just after 30 minutes, and a link used twice; 4, 5 and 6 failed logins. Payments: adding and listing saved cards, with only the last 4 digits visible on the page, in API responses, emails and logs.
Version line: "Regression on main at a41f9c0, changes since v1.8.0". Question: does the 30-minute expiry come from a ticket, and does it apply to links sent before the release?
Frequently asked questions
Do I need Git if I only test manually?
You need the reading part: commit ids, tags, log and diff, so that you know which version you tested and what changed in it. Hosting services show the same history in the browser, so you can start there. Writing commits and resolving conflicts become necessary once you maintain automated tests.
What is the difference between Git and GitHub?
Git is the version control tool; it runs on your machine, and most of its commands work without a network. GitHub, GitLab and Bitbucket are services that host remote repositories and add pull requests, issue tracking and CI pipelines on top of Git.
What is the difference between git fetch and git pull?
git fetch downloads new commits and updates remote-tracking branches such as origin/main, but leaves your files and your branch alone. git pull runs a fetch and then updates your current branch: with no new commits of your own it fast-forwards; if both sides have new commits, it must either merge them or rebase (replay your commits on top of the new ones), and recent Git versions stop and ask you to choose with --no-rebase, --rebase or the pull.rebase setting.
Is it safe to check out an old commit to reproduce a bug?
Yes. git checkout 3f9c2ab (or git switch --detach 3f9c2ab) puts that version in your working tree in a "detached HEAD" state: you can build and test it, and git switch main takes you back. Commit your own changes first, or set them aside with git stash, because Git refuses to switch when they would be overwritten.
References
- Reference — GitThe official reference for every Git command and its options, grouped by task — setup, branching, sharing, patching, inspection and debugging.
- Pro Git, 2nd Edition (Scott Chacon and Ben Straub)The Pro Git book, free to read online: Git basics, branching and merging, distributed workflows and how Git stores its data.
- ISTQB Certified Tester Foundation Level (CTFL) v4.0The foundation syllabus of software testing: test levels and types, test design techniques, test management and tools.
Practise with Mutqan tools
Compare two texts line by line or word by word and highlight differences.
Generate regression scenarios from a change description and a list of affected and related modules.
Generate SHA-1 hashes of text using the browser WebCrypto API.
Go deeper
Your progress on this topic
Not started
Mark the topic complete when you have studied it and done the exercise.
Sign in to track your progress
Mark topics complete, continue where you left off and unlock specialisation exams.