The smallest Git workflow that prevents disasters
Six rules for a team of two to eight developers, the settings that make the server enforce them, and what to do when a password lands in the repository.
It is Thursday, a little after six in the evening. A developer pushes one last fix straight to the main branch before the weekend and uploads it to the server. The client's site starts returning errors. The developer is on the way home, and nobody else knows what changed.
Teams of two to eight often skip process because it sounds like something for companies with fifty engineers. The workflow below has six rules, usually enough for a team this size, and the Git host enforces most of them once the settings are switched on.
The four disasters this prevents
- Work overwritten. Two people change the same files, one force-pushes or copies a folder over the server, and the other's day of work is gone.
- A broken main branch on Thursday evening. An untested change goes straight in, and the next person to pull inherits it, or the next deployment does.
- A password committed to the repository. A database password or an API key sits in a configuration file, in the history and in every copy.
- A release nobody can reproduce. The version in production was built on one laptop from changes that were never committed, so nobody can say which code is live.
The workflow in six rules
- The main branch is always deployable. Whatever is on
maincould go to production now. Unfinished work lives elsewhere. - Work happens on short-lived branches. One branch per task, created from
main, merged back within a day or two, then deleted. - Every change reaches main through a pull request. No direct pushes, including from the lead developer.
- One other person reviews it. Nobody approves their own change. In a team of two, you review each other.
- Automated checks run on every pull request. Start with what you have: the build, a linter, a few tests. Even a check that only confirms the project still builds catches a class of Thursday-evening mistakes.
- The main branch is protected. The server refuses anything that breaks rules 3 to 5, so the workflow does not depend on anyone's memory.
Each rule and the setting that enforces it
Names differ between Git hosts, but most have these settings. Switch them on once, for main.
| Rule | Setting that enforces it |
|---|---|
| Changes reach main only through a pull request | Branch protection on main: direct pushes blocked, for administrators too |
| One other person reviews | At least one required approval, and the author's own does not count |
| Checks pass before merging | Required status checks: build, linter, tests |
| The history of main is never rewritten | Force pushes and deletion blocked on main |
| Branches stay short-lived | Merged branches deleted automatically |
| Releases come from tags | The pipeline deploys only on a version tag, and only maintainers can create one |
| No secrets in the repository | .gitignore for local files, plus a secret-scanning check on each push where the host offers it |
| Access is per person | Individual accounts and keys, no shared login, member list reviewed when someone leaves |
Keep pull requests small
A reviewer given a change that fits on one screen reads every line. A reviewer given a change that touches thirty files skims it, finds nothing and approves.
Small pull requests also merge sooner, conflict less with others' work, and are easy to undo. To keep them small, give each one a single purpose, separate tidying from changes in behaviour, and merge unfinished features behind a switch that keeps them off instead of holding a branch open for three weeks. In the description, say what changed, why, and how you tested it.
Code that a person did not type takes the same path. If your team uses an AI assistant in the editor, its proposals go through a pull request and a human review like everything else.
Deploy from the pipeline, from a tagged commit
A deployment from a laptop ships whatever is on that laptop: uncommitted edits, a different library version, a local configuration file. Move the deployment into the pipeline and trigger it with a tag on main:
On an up-to-date main: git tag v1.8.0 then git push origin v1.8.0
The pipeline builds that exact commit and deploys it. Because a tag can point at any commit, have the pipeline refuse one that is not on main. "What is running in production?" then has a one-word answer, and rolling back the code means deploying the previous tag. Once the pipeline works, remove the deployment credentials from developers' machines so the old route is closed.
Secrets and access
Passwords, API keys and private keys never go into the repository. Keep them in the pipeline's secret store or in environment variables on the server, list local files such as .env in .gitignore, and commit an example file with the names and no values.
If a secret is committed anyway, the order matters:
- Rotate it first. Change the password or revoke the key at the service it belongs to, and update the secret store. Treat it as exposed from the moment it was pushed, and check the service's logs for unfamiliar use.
- Then clean up. Remove it from the code. Purging the history is optional: it takes a coordinated force push and fresh clones for everyone.
Removing the commit is not enough. The secret remains in every copy cloned or pulled in the meantime and in backups of the repository, and you cannot know who has read it.
Access follows the same idea: one account and one key per person, no shared login, and access removed the day someone leaves, with the tokens and deploy keys they created. The organisation that holds the repositories should belong to the company and not to a developer's personal account, for the reasons in where your code lives is a business decision.
Hotfixes take the same path, faster
When production is down, the temptation is to edit the file on the server or push to main. Take the same path: a branch from main, the smallest change that fixes the problem, a pull request, a reviewer phoned to look at it now, the checks, a tag, a deployment. It is faster because the change is tiny and the review jumps the queue, and every step leaves a record. If the checks are too slow for an emergency, speed them up for every day.
What to leave for later
Elaborate branching models with separate develop, release and hotfix branches, and long-lived release branches, solve problems a team this size rarely has. They add merges, and more places for work to get lost. They earn their place when you maintain several released versions at once, for example a product installed at different clients on different versions.
To start, switch on the first three rows of the table on the host you already use. For the review and check rules, PearlGit, the Git hosting service for teams from PearlFibers, offers pull requests with inline discussion, required reviewers enforced per branch, status checks that block the merge until they pass, and pipelines that run on every push and pull request and report on the request itself. Its interface is available in Arabic and English. The details are on the PearlGit page.