Browse the Knowledge Hub211 resources
Git review for testers: workflow, alternatives and pitfalls
Git is a free, open-source distributed version control system: every clone holds the full history, and commits, branches and merges happen locally before they are shared. It underpins GitHub, GitLab and Bitbucket, and for test teams it is where test code, test data and pipeline configuration are versioned and reviewed alongside the application they verify. Git 2.56 was released on 28 September 2026.
QAble runs this tool on client engagements. The assessment below is from that experience.
- last reviewed
- 13 October 2026
- version covered
- Git 2.56
- licence
- Open source
At a glance
Git at a glance
- Vendor
- Git project
- Category
- Version control
- Licence
- Open source (GPL 2.0 only)
- Platforms
- Cross-platform
- Integrates with
- GitHub, GitLab, Bitbucket, Jenkins
- First released
- 2005
- Version reviewed
- Git 2.56
Verdict
Our verdict on Git
Strengths
- Every clone holds full history, so log, blame and diff work offline and immediately
- Branches are cheap pointers, so each test change can be isolated and reviewed on its own
- git bisect run finds the commit that broke a test automatically, using the test as the judge
- Worktrees check out several branches at once, so a suite can run against two versions side by side
- Free under GPL 2.0 on every platform, and the format every hosting service and CI system expects
Weaknesses
- Binary assets do not diff or merge, and bloat history unless stored with Git LFS
- Overlapping commands such as checkout, switch and restore make tutorials disagree
- Force pushes can overwrite colleagues’ work unless hosting rules block them
- Commits, the index and refs take real effort for people who are not developers
Best for
Everything that is text: test code, page objects, fixtures, API collections exported as JSON, pipeline YAML and the Dockerfiles that define test environments. The decision that matters for a QA team is not whether to use Git but where test assets live. We put them in the application’s repository wherever the team allows it, so a product change and the matching test change are reviewed and merged together, and a tag marks exactly which suite verified which release.
Not for
Large or frequently changing binary files, such as recorded videos, database dumps, UFT object repositories or device images. Git keeps every version of every file in history, compressed binaries do not delta well, and none of them can be diffed or merged. Git LFS moves the content to separate storage and adds file locking, which helps, but tools that store test assets in binary formats stay awkward to collaborate on whatever you do.
Setup cost
Installing takes minutes; understanding takes weeks. Commits, the index, branches as movable pointers and the difference between merge and rebase need real effort, and many people work from memorised commands for years. The setup worth doing on day one for a test repository is a .gitignore that keeps reports, screenshots and dependency folders out, a .gitattributes that fixes line endings across Windows and macOS, and an agreed branch naming and commit message convention.
Maintenance burden
Close to none for the tool, since releases within Git 2 are backwards compatible. The ongoing cost is process: a branching strategy nobody follows, long-lived test branches that drift until merging them is a project, and repositories that grow because someone committed a report folder. Keep an eye on Git 3.0, which will make SHA-256 and a main branch the defaults for new repositories, although the project has set no release date.
Honest limitations
Binary files do not diff or merge. Large repositories slow down without partial clone, sparse checkout or LFS. The command line is inconsistent, with several commands doing overlapping jobs, which is a real cost when testers are not full-time developers. History can be rewritten, so force pushes to shared branches must be blocked on the hosting side. And Git records what changed, not why: only commit messages and pull request descriptions carry the reason, and only if the team writes them.
How it works
How Git works
Snapshots in a content-addressed store
Git stores content as objects named by a hash of their contents: blobs for file contents, trees for directories, and commits that point to a tree and to their parent commits. A commit is a snapshot of the whole project, not a diff, and identical files are stored once however many commits contain them. Because names come from content, history cannot change silently: editing an old commit changes its hash and the hash of every commit after it. That is why a rebase produces new commits, why a rebased branch needs a force push, and why everyone who already pulled the old commits ends up with diverged history.
Branches are pointers that move
A branch is a name pointing at one commit, and HEAD records which branch you are on. Creating a branch costs nothing, and committing moves the current branch forward to the new commit. A merge either fast-forwards the pointer or creates a commit with two parents; a rebase replays your commits on top of another branch. Remote-tracking branches such as origin/main record what the remote looked like at your last fetch, which explains the most common confusion: Git does not know a colleague pushed until you fetch, so “up to date” in git status means up to date with your last fetch.
The index sits between your files and a commit
Git commits what is in the index, the staging area, not what is in your working files. git add copies the current content of a file into the index; editing the file again afterwards changes nothing that will be committed until you add it again. That is what makes selective commits possible, such as committing a page object fix without the debugging lines next to it. git diff compares your files with the index and git diff --staged compares the index with the last commit, so reading the staged diff before committing is the habit that keeps stray changes out of test commits.
Every clone is a full repository
A clone copies every object, so committing, branching, viewing history and diffing need no server. Push and fetch exchange objects between repositories, and the hosting service is simply a shared repository with access control, pull requests and CI attached. CI often cuts corners here: actions/checkout fetches a single commit by default, which is fast but leaves no history, so anything that compares against the base branch, describes a version from tags or blames lines needs a deeper fetch. Partial clone, with --filter=blob:none, is the middle ground for large repositories: full history, with file contents downloaded only when needed.
Strengths
What it does well
Finding the breaking commit with bisect
git bisect performs a binary search through history between a known good and a known bad commit. With git bisect run and a script, it checks out each candidate and runs the script; exit code 0 marks good, 1 to 127 except 125 marks bad, and 125 skips a commit that cannot be tested. A failing test becomes a pointer to the exact commit that introduced the regression, and because each step halves the range, about ten steps cover a thousand commits.
Several checkouts at once with worktrees
git worktree add checks out another branch into a separate directory that shares the same repository. A tester can keep main and a feature branch side by side, run the suite against both and compare, without stashing work or cloning twice. It is also the clean way to reproduce a failure on a release branch while a half-finished test change stays where it is.
History archaeology for flaky tests
git log -S finds commits that added or removed a string, such as a selector or a timeout value, and git log -p on a single file shows how a test evolved. git blame shows who last changed each line, and --ignore-revs-file skips bulk reformatting commits so blame points at the meaningful change. Together they answer the question every flaky test raises: what changed, and when.
Partial clone and sparse checkout
For large monorepos, git clone --filter=blob:none downloads history but fetches file contents on demand, and git sparse-checkout limits the working directory to the folders you name. A test job that only needs tests and API definitions can avoid checking out the whole product, which shortens clone time on every CI run.
Attributes for line endings, diffs and binaries
A committed .gitattributes file applies the same rules for everyone, regardless of personal settings. It can normalise line endings so a Windows tester and a macOS developer stop producing whole-file diffs, mark generated or binary files so Git stops trying to diff them, and route chosen patterns through Git LFS. It is one of the cheapest fixes for noisy test diffs.
Hooks for fast local checks
Git runs scripts from hooks at points such as pre-commit and pre-push, which teams use to lint test code, block focused tests such as .only from being committed, or run a quick smoke check. Hooks are local and can be skipped with --no-verify, so treat them as a convenience for the author and enforce the same checks in CI.
Pricing
Git pricing and licensing
Git is free and open source under version 2 of the GNU General Public License, with no paid edition and no feature held back. Money enters through the services around it: hosting such as GitHub, GitLab or Bitbucket, each with a free tier and paid plans, storage for large files through Git LFS, and CI minutes spent cloning big repositories. The largest real cost is training, because the time people spend recovering from Git mistakes is rarely counted.
- Licence: GPL 2.0 only, as stated in the project’s COPYING file. Using Git to version proprietary code places no obligations on that code.
- No paid tiers: every feature, including worktrees, bisect, partial clone and sparse checkout, is in the free release for Windows, macOS and Linux.
- Git LFS: the client is open source under MIT, and the storage is billed by your host. On 13 October 2026 GitHub’s pricing page listed a Git LFS pack at $5 a month for 50 GB of bandwidth and 50 GB of storage.
- Hosting: pull requests, branch protection and code owners come from the hosting service, not from Git, and their availability on private repositories depends on the plan.
- Hidden costs: CI time cloning large repositories, which shallow or partial clones reduce, and the time spent untangling bad merges and rewritten history.
Checked against the vendor’s own pages on 13 October 2026. Pricing changes; confirm on the official site before buying.
Getting started
Git tutorial: getting started
Setup, step by step
- Install Git with your platform’s package manager or the installer from git-scm.com, and check it with git --version.
- Set your name and email once, and set init.defaultBranch to main so new repositories match the hosting services.
- Clone the application repository and create a branch for your test change with git switch -c.
- Make the change, stage only the files you mean to commit, and read the staged diff before committing.
- Fetch, rebase onto the latest main, push the branch and open a pull request.
Install
# Windows
winget install --id Git.Git -e --source winget
# macOS
brew install git
# Debian or Ubuntu
sudo apt-get install git
git --versionA first example
# One-time identity and defaults
git config --global user.name "Ada Tester"
git config --global user.email "[email protected]"
git config --global init.defaultBranch main
# Start a change to the tests on its own branch
git clone https://github.com/example/shop.git
cd shop
git switch -c test/checkout-coupon-expiry
# Stage deliberately, then read exactly what will be committed
git status
git add tests/checkout/coupon.spec.ts
git diff --staged
git commit -m "Add coupon expiry checks to the checkout suite"
# Catch up with main before asking for review
git fetch origin
git rebase origin/main
git push -u origin test/checkout-coupon-expiryWhat trips people up first
Committing things that should never be in the repository. The first test commit often sweeps in node_modules, a test-results folder, screenshots, a virtual environment or a .env file with credentials, because git add . adds everything that is not ignored. Write the .gitignore before the first commit. If a folder is already tracked, git rm -r --cached on it stops tracking it without deleting your local copy. If a secret was committed, treat it as leaked and rotate it: removing the file in a later commit leaves it in history, and anyone who cloned the repository already has it.
In practice
Using Git in practice
Setting up a test repository properly
Whether tests live in the application repository or their own, two committed files prevent most of the noise. The .gitignore keeps generated output out: reports, traces, screenshots, videos, downloaded browsers and dependency folders. The .gitattributes normalises line endings, so a suite edited on Windows and macOS does not show every line as changed, and marks binary fixtures so Git does not attempt to diff them. Keep test data that must be versioned small and text-based where possible, such as JSON or CSV fixtures, and generate large data sets in setup instead. A short CONTRIBUTING file with the branch naming and commit message convention saves repeating the same review comments.
# .gitignore: generated output never belongs in history
node_modules/
test-results/
playwright-report/
allure-results/
screenshots/
*.log
.env
# .gitattributes: one set of rules for everyone
* text=auto eol=lf
*.bat text eol=crlf
*.png binary
*.mp4 filter=lfs diff=lfs merge=lfs -textBranching and review for test code
Short-lived branches merged into main within a day or two cause the fewest problems for test code, because tests depend on the application’s current behaviour and a test branch that lives for weeks is written against an application that no longer exists. Name branches by intent, such as test/ for test-only work and fix/ for test fixes, keep each pull request to one concern, and, where the team allows it, ship the test change in the same pull request as the feature it covers. Rebase your branch onto main before review so the reviewer sees only your changes, but never rebase a branch someone else is also working on; merge main into it instead. When you must force push your own branch, use --force-with-lease, which refuses if the remote has commits you have not seen.
Running only the tests a pull request touched
actions/checkout fetches only the triggering commit by default, so any step that compares a branch with its base fails or silently compares nothing. Set fetch-depth: 0 when a job needs history: selecting changed specs, generating a changelog, or deriving a version with git describe. For pull requests, GITHUB_BASE_REF names the target branch, and the three-dot diff compares your branch with the point where it left the base, so changes merged to main since then are not included. Use this to run the specs a change touched first for fast feedback, and keep the full suite on main or nightly so nothing slips through.
# .github/workflows/changed-specs.yml
name: changed-specs
on: [pull_request]
jobs:
changed:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0 # full history, so the base branch is available
- uses: actions/setup-node@v7
with:
node-version: 24
cache: npm
- run: npm ci && npx playwright install --with-deps chromium
- name: Run the specs this pull request added or changed
run: |
# --diff-filter=d leaves out deleted files
CHANGED=$(git diff --name-only --diff-filter=d "origin/$GITHUB_BASE_REF...HEAD" -- 'tests/*.spec.ts')
if [ -n "$CHANGED" ]; then npx playwright test $CHANGED; else echo "No spec changes"; fiFinding the commit that broke a test
When a test that used to pass now fails and nobody knows why, bisect is faster than reading the log. Mark the current commit bad and the last known passing release good, then hand Git a command that exits 0 when the test passes and 1 when it fails. Git checks out the midpoint, runs the command and halves the range until one commit remains. Build or install failures should exit 125 so Git skips those commits rather than blaming them. Run only the one failing test, not the suite, so each step takes seconds, and make sure the test is deterministic first: a flaky test sends bisect to a random commit.
# Bisect between the last good release and the current main
git bisect start
git bisect bad main
git bisect good v4.12.0
# 0 = good, 1 to 127 = bad, 125 = cannot test this commit (skip)
git bisect run sh -c 'npm ci --silent || exit 125; npx playwright test tests/checkout/coupon.spec.ts'
# Git prints the first bad commit; return to where you started
git bisect resetLarge files and large repositories
Decide how binary test assets are stored before they accumulate, because removing them from history later means rewriting it for everyone. Git LFS replaces large files in the repository with small pointer files and stores the content on the hosting service, which keeps clones fast; git lfs lock gives exclusive editing of a file, which is the only practical way to share binary assets that cannot be merged. Every machine and CI job needs Git LFS installed, or it checks out pointer files instead of the real content. For repositories that are large because of size and history rather than binaries, a partial clone with a sparse checkout lets a test job fetch only what it needs.
# Store videos and recordings through Git LFS (once per machine: git lfs install)
git lfs install
git lfs track "*.mp4" "*.har"
git add .gitattributes
git lfs lock fixtures/onboarding-flow.mp4 # exclusive edit for binary assets
# Large monorepo: history now, file contents on demand, two folders on disk
git clone --filter=blob:none --sparse https://github.com/example/platform.git
cd platform
git sparse-checkout set tests api-specsPitfalls
Common pitfalls
Credentials committed in test configuration
Why it happens: Test configs collect base URLs, test accounts and API keys, and the quickest way to share them is to commit them. Deleting the file later leaves the secret in every clone’s history.
The fix: Rotate any secret that was ever committed. Keep secrets in environment variables and CI secret stores, commit an example file with placeholders, and turn on your host’s secret scanning where it is available.
Long-lived test branches
Why it happens: A suite developed on a branch for weeks is written against an application that keeps changing on main, so the eventual merge brings conflicts and tests that no longer match the product.
The fix: Merge small, working increments daily, hide unfinished tests behind skip annotations with a ticket reference, and rebase or merge main into any branch that must live longer.
Force pushing over a colleague’s commits
Why it happens: git push --force replaces the remote branch with yours, discarding commits pushed by anyone else since your last fetch, and the loss is noticed only when their work disappears.
The fix: Use --force-with-lease, which refuses when the remote has moved, block force pushes to main and release branches in the hosting service, and avoid rebasing branches others work on.
Whole-file diffs from line endings
Why it happens: Windows tools write CRLF line endings and macOS and Linux write LF. Without shared rules, opening and saving a file on the other platform changes every line, burying the real change in review.
The fix: Commit a .gitattributes with text=auto and explicit eol rules, then renormalise once with git add --renormalize . in a dedicated commit.
CI steps that need history running on a shallow clone
Why it happens: actions/checkout fetches only one commit by default, so diffs against the base branch, git describe and changed-file test selection fail or return nothing without an obvious error.
The fix: Set fetch-depth: 0 on jobs that need history, or a small fixed depth when you only need recent commits, and keep the default elsewhere for speed.
Following tutorials written for older Git habits
Why it happens: Older guides use git checkout for everything and assume master as the default branch. git switch and git restore, added in Git 2.23, split those jobs, and Git 3.0 plans to make main the default branch and SHA-256 the default hash for new repositories.
The fix: Use git switch for branches and git restore for files, set init.defaultBranch explicitly, and check scripts that parse exit codes: Git 2.56 now exits 0 instead of 129 when -h or --help is requested.
Compared
Git alternatives compared
| Tool | Model | Licence | Commit without the server | Latest release |
|---|---|---|---|---|
| Git | Distributed, full history in every clone | GPL 2.0 | Yes | 2.56.0, 28 September 2026 |
| Subversion | Centralised server with working copies | Apache 2.0 | No, commits go to the server | 1.15.0, 8 October 2026 |
| Mercurial | Distributed, full history in every clone | GPL 2.0 or later | Yes | 7.2, 29 January 2026 |
FAQ
Frequently asked questions
What are the best Git alternatives?
The alternatives most often weighed against Git are Subversion, Mercurial, GitHub and GitLab. The comparison on this page sets them side by side on model, licence, commit without the server and latest release, and the verdict above says where Git is the wrong choice and which of them to pick instead.
Is Git free?
Yes. Git is free and open source under version 2 of the GNU General Public License, with no paid edition, for Windows, macOS and Linux. The licence covers Git itself and places no obligations on the code you version with it. Costs come from services around Git, such as paid hosting plans, Git LFS storage and CI minutes, not from Git.
What is the difference between Git and GitHub?
Git is the version control tool that runs on your machine and stores history in a repository. GitHub is a hosting service built around Git repositories, adding pull requests, code review, issues, Actions for CI and access control. GitLab and Bitbucket play the same role. You can use Git with no hosting at all, but pull requests and branch protection come from the host.
Should test code live in the same repository as the application?
Usually, yes. In one repository a feature and its tests are reviewed and merged together, the suite always matches the code it tests, and tags mark which tests verified which release. A separate repository suits suites that test several applications, or teams that cannot get write access to the product repository; then pin which application version each test commit targets.
Merge or rebase for test branches?
Rebase your own short-lived branch onto main before review, so history stays linear and the reviewer sees only your change. Merge when a branch is shared with other people, because rebasing rewrites commits they already have. Hosting services also offer squash merging of pull requests, which keeps main readable whichever method was used on the branch.
How do I store large test files, such as videos or HAR files, in Git?
Use Git LFS. It stores large files on the hosting service and keeps small pointer files in the repository, so clones stay fast, and git lfs lock prevents two people editing the same binary. Every machine and CI job needs Git LFS installed. Better still, avoid committing large generated artefacts at all and publish them as CI artefacts.
What changed in Git 2.56?
Git 2.56 was released on 28 September 2026. It adds create, delete, update and rename subcommands to git refs, a drop subcommand to the experimental git history command, typo advice for commands such as git push origin/main, better git log --follow handling of renames across branches, and retries when two processes lock a configuration file at once. Scripts should note that -h and --help now exit with 0 rather than 129.
What is changing in Git 3.0?
The Git project’s breaking-changes document plans SHA-256 as the default hash and reftable as the default reference storage for new repositories, main as the default branch name, Rust as a required build dependency and a safer default for discovering bare repositories, along with removing long-deprecated features such as grafts. There is no planned release date, and existing repositories are not converted.
Alternatives
Git alternatives worth weighing
GitHub
Git hosting platform with pull request review, Actions for CI/CD, issues and projects, Packages, security scanning and Copilot.
GitLab
Single-platform DevOps suite spanning source, CI/CD, security scanning and deployment.
Resources
Where to go next
Picking the wrong tool costs more than picking late.
QAble advises on toolchain decisions and then implements them, with engineers who have run these tools on real delivery. Start with a free QA audit.